Headless mode
chord headless 是 Chord 的轻量控制面入口,适合 bot、gateway、自动化脚本接入。
它是什么
- 无 TUI
- 通过 stdio 交互
- 输入是 JSON 命令(每行一条)
- 输出是 JSON envelope(每行一条)
适合做外层集成,但不自带浏览器前端、多租户隔离、完整权限托管。
启动
chord headless# 或go run ./cmd/chord/ headlessCLI flag:-d/--session-dir、-c/--continue、-r/--resume、-w/--worktree。详见 CLI — chord headless。
协议格式
- stdin:每行一条 JSON 命令
- stdout:每行一条 JSON envelope。其他诊断输出走 stderr,不要把 stderr 当协议解析。
每个出站 envelope 的结构:
{ "type": "<event-type>", "payload": { ... } }你收到的第一行一定是 {"type": "ready", ...};在它之前不要发送其他命令。
命令
向 stdin 发送以下命令。未知命令会收到 error envelope。
subscribe
选择你想接收的推送事件类型。如果从未发送 subscribe,Chord 默认会转发所有可订阅事件。 一旦发送 subscribe,默认行为就会被替换成显式 allowlist。
{"type": "subscribe", "events": ["activity", "assistant_message", "idle", "done_completion"]}响应:
{"type": "subscribe_response", "payload": {"events": ["activity", "assistant_message", "idle", "done_completion"]}}可订阅事件类型:activity、assistant_message、idle、confirm_request、question_request、error、agent_done、info、toast、done_completion、assistant_rollback、todos。
status
请求当前后端状态快照。
{"type": "status"}响应:
{ "type": "status_response", "payload": { "session_id": "20260508120000000", "busy": false, "phase": "", "phase_detail": "", "pending_confirm": null, "pending_question": null, "last_error": "", "last_outcome": "completed", "updated_at": "2026-05-08T12:00:00Z" }}send
向 agent 发送用户消息。slash 命令的行为与 TUI 一致;裸 /models 会被当作 /models status,因为 headless 没有 TUI overlay。
{"type": "send", "content": "请总结一下项目结构。"}如果当前有待处理的 confirm_request 或 question_request,而用户发送了普通消息(不是下面的 confirm / question),Chord 会先自动关闭该待处理交互,再消费这条新消息。
models
查看或切换模型池。
{"type": "models", "action": "status"}{"type": "models", "action": "set_current_model_pool", "pool": "thinking"}响应:
{ "type": "models_response", "payload": { "ok": true, "status": "Model pool: thinking\n..." }}status 是与 /models status 一致的纯文本快照。
confirm
处理一个待决的 confirm_request。使用请求里的 request_id。
{ "type": "confirm", "request_id": "r-…", "action": "allow", "final_args_json": "{\"path\":\"...\"}", "edit_summary": "", "deny_reason": "", "rule_pattern": "Shell:^git status$", "rule_scope": "session"}action 要与模型/运行时提供的选项一致(如 allow、deny、allow_once 等)。可选的 rule_pattern + rule_scope(session / project / user_global)会在这次答复的同时安装一条权限规则;两者都省略时表示一次性决策。
question
回答一个待决的 question_request。
{"type": "question", "request_id": "r-…", "answers": ["yes"], "cancelled": false}多选题时可在 answers 里传多个字符串。若只想关闭问题而不作答,传 "cancelled": true。
cancel
取消当前 turn(等价于在 TUI 里按两次 Esc)。
{"type": "cancel"}事件
你会在 stdout 收到这些事件。下表覆盖了默认发出的响应类事件,以及可订阅的推送事件。对未知字段请保持宽容,把它们当作未来扩展字段,不要因为新字段而让客户端崩掉。
总是会发出(不需要订阅)
| 类型 | 何时出现 | 主要 payload 字段 |
|---|---|---|
ready | 服务启动完成,可以接受命令 | session_id,以及可选 worktree 信息:name、branch、path、repo_root |
subscribe_response | 响应 subscribe | events |
status_response | 响应 status | 见 status |
models_response | 响应 models | ok、message、status |
error | 命令解析或执行错误 | message |
可订阅推送事件
| 类型 | 何时出现 | 主要 payload 字段 |
|---|---|---|
activity | Agent 进入新阶段 | agent_id、type(如 connecting、streaming、compacting) 、detail |
assistant_message | 一条完整 assistant 消息可供消费 | agent_id、text、tool_calls |
idle | Agent 再次可接收输入 | last_outcome(completed / cancelled / error) |
done_completion | 非 loop 模式下 Done 工具完成并给出最终报告 | call_id、report、reason、status、agent_id、mode |
confirm_request | 某个工具需要显式确认 | request_id、tool_name、args_json、needs_approval、already_allowed、timeout_ms |
question_request | 模型向用户提问 | request_id、tool_name、question、options、option_details、default_answer、multiple、timeout_ms |
agent_done | 某个 SubAgent 完成任务 | agent_id、task_id、summary |
assistant_rollback | 丢弃尚未提交的流式 assistant 输出 | agent_id、reason |
info | 运行时信息消息 | agent_id、message |
toast | TUI 中的瞬时通知;headless 可以忽略 | agent_id、message、level(info / warn / error) |
todos | 替换当前 todo 列表 | todos[],元素结构为 {id, content, status, active_form} |
error | 运行时错误 | agent_id、message |
assistant_message.text 只有在非常异常的情况下才会为空。Chord 遇到这种情况会记 warning;gateway 集成通常应跳过空消息,而不是继续向下游转发空文本。
通过 send 兼容 slash 命令
为方便接入只有单一文本输入的聊天表面,headless 也支持通过 send 发送这些 slash 命令:
/models status、/models <pool>、/models --agent <name> <pool>/help、/stats、/compact、/loop on、/loop off(仅当当前 MainAgent 角色可使用Done工具时)
裸 /models 会被当作 /models status。部分 slash 命令是 TUI 专用的(例如 /new、/resume 需要交互式 picker);在 headless 模式下尝试调用时,会返回 error envelope,说明“X 仅在本地 TUI 模式可用”。
最小 Python 客户端示例
import jsonimport subprocessimport threading
proc = subprocess.Popen( ["chord", "headless", "-d", "/path/to/project"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=1, text=True,)
def reader(): for line in proc.stdout: ev = json.loads(line) print("<-", ev["type"], ev.get("payload"))
threading.Thread(target=reader, daemon=True).start()
def send(cmd: dict) -> None: proc.stdin.write(json.dumps(cmd) + "\n") proc.stdin.flush()
# 等待 ready(第一行一定是 ready),然后订阅并发送消息。send({"type": "subscribe", "events": ["activity", "assistant_message", "idle", "done_completion"]})send({"type": "send", "content": "Summarize the project structure."})生产环境中还需要处理 confirm_request(通过 confirm 回答)和 question_request(通过 question 回答);在它们得到答复前,agent 会阻塞等待。
chord-gateway:推荐的 headless 消费方式
如果你想把 Chord 接到聊天表面(飞书、微信等)或搭建多用户 gateway,通常不需要自己从零实现 headless 协议。配套项目 keakon/chord-gateway 已经对其做了封装,并补上了协议刻意留给外层处理的部分:
- 进程生命周期:按 session 拉起 / 重启
chord headless,并回收空闲进程。 - 多租户隔离:按用户隔离工作目录、审计日志、限流。
- 聊天平台适配:飞书 / 微信 webhook、消息分段、图片转发。
- 权限交互:把
confirm_request/question_request渲染成聊天回复,再映射回confirm/question命令。 - 基于以上 wire format 的重连辅助。
本页描述的是更底层的协议契约,适合那些需要 chord-gateway 之外能力的集成方。如果你的目标是“让人能在手机上和 Chord 对话”,优先从 chord-gateway 开始;只有在你有明确理由时,再直接下沉到 headless 协议。
适合的用法
- 让外层 gateway 管理进程生命周期。
- 让外层系统决定哪些事件展示给最终用户。
- 在外层实现工作目录、权限、审计、多租户边界控制。
它不替代什么
chord headless 不是:
- 浏览器应用
- 多租户安全边界
- 完整权限沙箱
更高层的部署方式,见 chord-gateway。