跳转到内容

Headless mode

chord headless 是 Chord 的轻量控制面入口,适合 bot、gateway、自动化脚本接入。

  • 无 TUI
  • 通过 stdio 交互
  • 输入是 JSON 命令(每行一条)
  • 输出是 JSON envelope(每行一条)

适合做外层集成,但自带浏览器前端、多租户隔离、完整权限托管。

协议稳定性: Chord 还在 1.0 之前,headless 协议在版本之间可能变化。请把未知的 envelope 字段和事件类型当作不透明数据处理,固定集成所测试过的 Chord 版本,并在升级前查看变更记录

Terminal window
chord headless
# 或
go run ./cmd/chord/ headless

CLI 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,Chord 默认会转发所有可订阅事件。 一旦发送 subscribe,默认行为就会被替换成显式 allowlist。

{"type": "subscribe", "events": ["activity", "assistant_message", "idle", "done_completion"]}

响应:

{"type": "subscribe_response", "payload": {"events": ["activity", "assistant_message", "idle", "done_completion"]}}

可订阅事件类型:activityassistant_messageidleconfirm_requestquestion_requesthandoff_requesterroragent_startedagent_notifyagent_doneinfotoastdone_completionlocal_shell_resultassistant_rollbacktodos

请求当前后端状态快照。

{"type": "status"}

响应:

{
"type": "status_response",
"payload": {
"session_id": "20260508120000000",
"busy": false,
"phase": "",
"phase_detail": "",
"pending_confirm": null,
"pending_question": null,
"pending_handoff": null,
"last_error": "",
"last_outcome": "completed",
"updated_at": "2026-05-08T12:00:00Z"
}
}

向 agent 发送用户消息。slash 命令的行为与 TUI 一致;裸 /models 会被当作 /models status,因为 headless 没有 TUI overlay。

{"type": "send", "content": "请总结一下项目结构。"}

如果当前有待处理的 confirm_requestquestion_requesthandoff_request,而用户发送了普通消息(不是下面的 confirmquestionhandoff),Chord 会先自动关闭该待处理交互,再消费这条新消息。

查看或切换模型池。

{"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_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 要与模型/运行时提供的选项一致(如 allowdenyallow_once 等)。可选的 rule_pattern + rule_scopesession / project / user_global)会在这次答复的同时安装一条权限规则;两者都省略时表示一次性决策。session 只在当前会话内生效;project 写入当前项目的 .chord/agents/<role>.yamluser_global 写入用户配置目录的 agents/<role>.yaml(默认 ~/.config/chord/agents/<role>.yaml)。

回答一个待决的 question_request

{"type": "question", "request_id": "r-…", "answers": ["yes"], "cancelled": false}

多选题时可在 answers 里传多个字符串。若只想关闭问题而不作答,传 "cancelled": true

处理一个待决的 handoff_request。批准会用选定 agent 执行已保存的 plan;拒绝会把拒绝原因追加到对话上下文,并让 planner 基于该上下文继续。

{"type": "handoff", "request_id": "handoff-…", "action": "accept", "agent": "builder", "pool": "thinking"}
{"type": "handoff", "request_id": "handoff-…", "action": "deny", "deny_reason": "请先补充发布步骤。"}

action 可用 accept / allow(或空 action)表示批准,deny / reject / cancel 表示拒绝。agent 默认使用请求里的默认 agent;可选的 pool 会在执行前切换该 agent 的模型池。

从 headless client 侧执行本地 shell 命令,并收到一个 local_shell_result 事件。该命令主要用于 gateway 暴露 ! 风格本地命令的场景。

{"type": "local_shell", "command": "git status --short"}

也可以用 content 字段作为后备命令内容。输出会合并 stdout 和 stderr,并带有输出上限和超时。

安全说明:local_shell 会在 chord headless 进程环境中执行 bash -c。它是直接的本地命令执行协议能力,不是模型工具请求,也不能替代沙箱。任何向用户暴露该能力的 gateway 都必须自行实现认证、授权、审计、命令过滤和租户隔离。

取消当前 turn(等价于在 TUI 里按两次 Esc)。

{"type": "cancel"}

你会在 stdout 收到这些事件。下表覆盖了默认发出的响应类事件,以及可订阅的推送事件。对未知字段请保持宽容,把它们当作未来扩展字段,不要因为新字段而让客户端崩掉。

类型 何时出现 主要 payload 字段
ready 服务启动完成,可以接受命令 session_id,以及可选 worktree 信息:namebranchpathrepo_root
subscribe_response 响应 subscribe events
status_response 响应 status status
models_response 响应 models okmessagestatus
error 命令解析或执行错误 message,可选 code(例如 stdin_line_too_long
类型 何时出现 主要 payload 字段
activity Agent 进入新阶段 agent_idtype(如 connectingstreamingcompacting) 、detail
assistant_message 一条完整 assistant 消息可供消费 agent_idtask_idagent_typeparent_agent_idtexttool_calls;main agent 的委托字段为空
idle 主 agent 与所有 SubAgent 均已全局静默,可再次接收输入 last_outcomecompleted / cancelled / error
done_completion 非 loop 模式下 Done 工具完成并给出最终报告 call_idreportreasonstatusagent_idmode
confirm_request 某个工具需要显式确认 request_idtool_nameargs_jsonneeds_approvalalready_allowedneeds_approval_rulesalready_allowed_rulestimeout_ms
question_request 模型向用户提问 request_idtool_namequestionoptionsoption_detailsdefault_answermultipletimeout_ms
handoff_request planner 已保存 handoff plan,需要 client 批准或拒绝执行 request_idplan_pathplan_textplan_erroragents[],元素包含 {name, default, model_pools, current_model_pool}
local_shell_result local_shell 命令的执行结果 commandoutputfailederror
agent_started 某个委托的 SubAgent runtime 开始运行(包括 parked task 的按需 rehydrate) agent_idprevious_agent_id(rehydrate 时存在)、task_idagent_typedescriptionparent_agent_idparent_task_id
agent_notify 某个 agent 向 owner 或指定委派工作流发送非阻塞更新 agent_idtask_idagent_typeparent_agent_idparent_task_idtarget_agent_idtarget_task_idkindmessage
agent_done 某个 SubAgent 完成任务 agent_idtask_idagent_typeparent_agent_idparent_task_idsummary
assistant_rollback 丢弃尚未提交的流式 assistant 输出 agent_idreason
info 运行时信息消息 agent_idmessage
toast TUI 中的瞬时通知;headless 可以忽略 agent_idmessagelevelinfo / warn / error
todos 替换当前 todo 列表 todos[],元素结构为 {id, content, status, active_form};启用 todo_write 时,多个独立且正在处理的工作流可以同时为 in_progress,但必须使用唯一的 active_form
error 运行时错误 agent_idmessage,可选 code

如果 stdin 上的单行输入超过协议行长度限制,Chord 会输出带 code: "stdin_line_too_long"error envelope,并继续读取后续行。集成方应在存在 code 时用它做错误分类,把 message 作为面向人的诊断信息。

纯工具调用轮次(包括 SubAgent 调用 Complete)的 assistant_message.text 可能为空。Chord 会记 warning 便于观测;gateway 集成应跳过空消息,并以 agent_done.summary 作为权威的 SubAgent 完成内容。

进入静止状态的 SubAgent 可能释放 live runtime,但 task 与 transcript 会持久保留。后续获授权的定向通知可用新的 agent_id rehydrate 该任务;集成方应使用稳定的 task_id 路由,并根据 agent_started.previous_agent_id 替换 runtime 级标签。

idle 是全局静默信号,不是单次请求完成信号。只要任一 agent 仍在运行、内部事件或需要处理的 mailbox 消息仍在排队,或某个 SubAgent 还有等待下一请求消费的输入,Chord 就不会发出 idle。目标 busy 时,排队消息会在下一个请求边界处理;可恢复但未运行的目标会先被唤醒。纯 progress mailbox 只表达信息,本身不会阻止全局 idle。

为方便接入只有单一文本输入的聊天表面,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 模式可用”。

import json
import subprocess
import 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 回答)和 handoff_request(通过 handoff 回答);在它们得到答复前,agent 会阻塞等待。

chord-gateway:推荐的 headless 消费方式

Section titled “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