Blog kỹ thuật

GOHA AVS: một timeline chung cho người dựng và AI agent

Cách mình thiết kế GOHA AVS: một source of truth, vocabulary đóng gồm 11 operation và oplog, để người và agent cùng chỉnh một episode dài mà không đạp lên nhau.

Nguyễn Hiếu··4 phút đọc
GOHA AVS: một timeline chung cho người dựng và AI agent

GOHA AVS (Agent-Native Video Studio) là một studio chạy localhost để xem, sửa và render các episode video dài, với một AI agent nằm ngay trong vòng lặp làm việc. Bài này kể vì sao mình xây nó, và quan trọng hơn: những quyết định thiết kế nào khiến người và agent cùng làm việc được trên một timeline.

GOHA AVS — preview, panel cảnh đang chọn, nhật ký sửa (Edit log) và ô giao việc cho Agent
GOHA AVS — preview, panel cảnh đang chọn, nhật ký sửa (Edit log) và ô giao việc cho Agent

Vấn đề: agent không nhìn thấy thứ nó vừa làm ra

Trước AVS, quy trình của mình khá thô:

  • Muốn xem thử một episode (~20 phút) phải bake toàn bộ bằng ffmpeg. Đó là job nặng, không chạy song song được.
  • Sai một ảnh ở beat 137 thì rebake từ đầu.
  • Muốn sửa, mình phải mô tả bằng lời cho agent: "đoạn gần giữa, cắt bớt chút". Mơ hồ và tốn lượt.
  • Tệ nhất: agent không bao giờ thấy output của chính nó. Nó sửa mù.

Gốc rễ là không có một nơi chung để cả người lẫn agent nhìn và chỉnh cùng một trạng thái.

Một source of truth: edit_manifest.json

Kiến trúc GOHA AVS: UI người dựng, manifest-server với oplog, MCP server và Claude Code; nhánh render qua hàng đợi ffmpeg
Kiến trúc GOHA AVS: UI người dựng, manifest-server với oplog, MCP server và Claude Code; nhánh render qua hàng đợi ffmpeg

Giải pháp là một file duy nhất, edit_manifest.json, do một manifest server sở hữu (Node + Hono + WebSocket). Manifest nằm trong bộ nhớ, ghi xuống đĩa theo kiểu atomic (ghi file tạm rồi rename), và mọi thay đổi được broadcast qua WebSocket tới các client. Người dùng trình duyệt và agent đều chỉ là client của server này.

Không ai sửa file trực tiếp. Mọi thứ đi qua server.

Operation có tên, vocabulary đóng

Đây là quyết định mình coi là cốt lõi. Mỗi thay đổi là một operation có tên: trim, swap_asset, reorder, ... Bản v1 có 11 op. Mỗi op có:

  • một cái tên cố định,
  • một câu mô tả cho con người đọc,
  • khả năng đảo ngược (reversible).

Và mình cố ý không có patch_json, set_path hay apply_diff. Lý do: cái "moat" không mất đi vì vocabulary lớn, mà mất vì có một cửa sau cho phép patch tùy ý. Chỉ cần một cửa sau, mọi bảo đảm về ngữ nghĩa, undo và khả năng đọc hiểu đều vỡ.

apply_ops([
  { op: "trim", beat: 42, tail_frames: -18, base_rev: 311 }
])

Dải thời gian nhiều lớp: logo, phụ đề, hiệu ứng, cảnh, lời đọc, nhạc nền
Dải thời gian nhiều lớp: logo, phụ đề, hiệu ứng, cảnh, lời đọc, nhạc nền

Oplog và diff_since: agent đọc được "chuyện gì vừa xảy ra"

Mỗi op được ghi vào oplog với rev++ và actor (người hay agent). Agent gọi diff_since(rev) và nhận về thứ như "human vừa trim beat 42 bớt 18 frame", thay vì một đống JSON diff thô.

Nếu session của agent chết giữa chừng, nó chỉ cần gọi diff_since(last_rev) để bắt kịp. Không cần nhồi lại toàn bộ lịch sử vào prompt.

Concurrency: đơn giản có chủ đích

Mỗi op mang theo base_rev. Nếu base_rev đã cũ, server từ chối; client fetch lại trạng thái rồi thử lại. Không CRDT. Với một studio localhost, một người và một agent, cách này đủ và dễ suy luận hơn nhiều.

Preview và render tách đôi

  • Preview trong trình duyệt chỉ cần đạt khoảng 75–80% độ trung thực. Mục đích là kiểm tra nhịp và lựa chọn asset, không phải duyệt màu.
  • Master render là ffmpeg thật, đi qua một hàng đợi duy nhất (GPU slot = 1).

Nhờ vậy sửa-xem không còn phải chờ bake cả episode.

MCP: đưa tool thẳng cho Claude Code

Server expose các tool qua MCP (diff_since, apply_ops, ...) để Claude Code thao tác trực tiếp lên timeline. Agent dùng đúng vocabulary mà UI dùng, nên thứ nó làm, người thấy được và undo được.

Schema từ dữ liệu thật, validator offline

Schema được chắt ra từ 28 manifest thật (6.339 segment), không phải vẽ trên giấy. Đi kèm là một validator offline kiểm tra schema và các invariant.

Luật sản phẩm

  • Không sinh asset trong sản phẩm (không TTS, không image gen). AVS là nơi dựng, không phải nơi tạo.
  • Dependency chỉ nhận license MIT/BSD/Apache-2.0/ISC.
  • ffmpeg được gọi như subprocess, không bundle (vì GPLv3).

Hướng v2

Ngày 2026-08-20 mình chốt hướng v2: một NLE chuyên nghiệp với timeline tự do, số layer không giới hạn, và chat hai chiều ngay trong app, nhưng vẫn giữ cái moat semantic-op.

Bài học

  • Cho agent một hợp đồng (vocabulary đóng) thay vì quyền truy cập tự do.
  • Cho người và agent chung một nguồn sự thật, rồi để server là người gác cổng duy nhất.
  • Trả lời "chuyện gì vừa đổi" bằng ngôn ngữ của nghiệp vụ, không bằng JSON diff.
  • Chọn cơ chế concurrency vừa đủ cho bài toán, đừng mượn độ phức tạp của sản phẩm khác.