同时开几个 CLI,让不同的模型各干一部分,这件事不难。难的是收尾。窗口里显示任务已经结束,可去找它该交的文件,常常发现文件不在、不全,或者写到了别的地方。到这一步,那句“已完成”什么也证明不了。
为了管住这种情况,我在本地做了一层任务管理,叫 agent-tasks。后来我把其中的核心控制器抽了出来,开源成 TaskDock。
去哪拿,拿到的是什么
- 仓库:Tim-1e/taskdock,MIT 许可
- 适配器协议:ADAPTER.md
- 适配器示例:examples/echo_adapter.py
TaskDock 是一个面向 agent CLI 的小型任务控制器,核心是三个 Rust 文件。它负责持久保存任务和会话、监管进程,并通过 MCP 暴露四个工具。跑什么任务、用哪个后端和模型、结果能不能接受,都由调用它的主控 Agent 决定。
它不包含任何模型厂商的 SDK,不带凭据,没有模型路由,也没有现成的多渠道适配器。你要接哪个 CLI,就自己写一个适配器,或者让你的 coding agent 照着 ADAPTER.md 来写。目前只支持 Windows。
公开仓库和我本地的环境,区别在这里:
| 公开的 TaskDock | 我本地的环境 | |
|---|---|---|
| 任务存储、进程监管、四个 MCP 工具 | 有 | 有,是同一套核心 |
| CLI 适配器 | 只有一个 echo 示例 | 接了几个日常在用的 CLI,适配器是我自己写的,不在公开仓库里 |
| 登录、凭据、模型选择 | 不包含,属于你自己的集成 | 沿用各 CLI 自己的登录和配置 |
| 测试 | 仓库自带的测试用假 worker | 另外做过真实小任务验证和假 CLI 压测(见后文) |
先跑一个不调用模型的 demo
前提是 Windows、Rust 1.89+、Python 3.10+。在仓库目录里运行:
cargo build --release --locked
python examples/demo.py --binary target/release/taskdock.exedemo 会在仓库外新建一个状态目录,注册 echo 适配器,然后走完一遍流程:提交任务,检查重复提交会不会被识别,续接一个合成会话,读回两轮结果,最后接受结果。成功时打印 PASS 和状态目录的位置。
echo 适配器只是把输入原样返回,没有模型,也没有对话记忆。demo 跑通,只能说明任务流程和状态机工作正常,不代表任何模型能力。
要接进自己的 MCP 客户端,用二进制的绝对路径,再加上 demo 打印出来的状态目录:
{
"mcpServers": {
"taskdock": {
"command": "C:/path/to/taskdock.exe",
"args": ["--home", "C:/path/to/state", "serve"]
}
}
}接好以后,主控能用这四个工具:
| 工具 | 作用 |
|---|---|
agent_backends | 列出已配置的适配器 |
agent_submit | 保存任务,条件满足时启动;同 ID、同输入重复提交不会生成第二份 |
agent_query | 列出和查看任务,读结果和事件,或者等待(读取不会消耗结果) |
agent_control | 启动、续接、取消、接受,或者核对一轮执行 |
默认走 stdio。仓库里还有一个可选的 HTTP 网关,只面向单用户,第一次用建议先从 stdio 开始。
一个任务怎么走完
示意图 · TaskDock 的任务生命周期,用来说明流程,不是测试结果。
图里最关键的是 completed 和 accepted 之间那一步。completed 只说明这一轮执行正常结束了;主控去核对实际产物,文件在不在、内容对不对,确认没问题后再显式标记 accepted,这才算验收通过。核对不过,就在同一个会话里续接一轮,让它接着改。
依赖也是按验收来算的。下游任务依赖的是上游最新一轮被接受的结果,不是“跑完了”的结果。就算依赖都满足了,排队中的任务也不会自己开始,要由主控显式 start。TaskDock 没有后台调度队列,不会替你决定下一步做什么。
图右边的旁支同样重要。如果监管中断,或者远端执行有没有成功无法确认,任务会进入 unknown。这时要做的是先查原来那个任务、核对清楚再决定,绝不要换一个新 ID 重新提交来绕开这种不确定。不然同一份工作可能被跑两遍,产物也会混在一起。
还有几条具体规则:
- 每个控制操作都要带上
expected_run,针对旧轮次的操作会被拒绝。 - 续接会话时开新的一轮,并带上唯一的
request_id。后端、模型、工作目录和选项沿用原来的设置,重复的请求号不会触发再次生成。这只是 TaskDock 这一层的去重,外部工具的所有副作用并不因此都只发生一次。 - “取消请求已发出”和“任务确实已经停止”要分开确认。
接上你自己的 CLI
README 里给了一段可以直接交给 coding agent 的请求,大意是:读 AGENTS.md 和 ADAPTER.md,为你选定的 CLI 和具体模型写一个适配器;使用这个 CLI 当前版本文档里写明的非交互接口,保留它原生的会话 ID 用于续接,显式处理权限、取消和失败;先用假事件测试,再做一次有边界的真实运行;凭据和本机配置不放进仓库,也不为了迁就适配器去改核心协议。
适配器用什么语言写都可以,Python 只是示例的选择。登录、模型发现、远程传输、worktree 管理和 CLI 的权限策略,都属于你自己的集成。
这里要分清三层,不然容易混:
skill ── 规则:什么时候委派、简报怎么写、怎么验收
MCP ── 接口:TaskDock 提供的四个工具
CLI ── 执行:真正调用模型的命令行程序,用它自己的登录和配置只装一个 skill,不会凭空多出工具,也不会替你登录任何服务。真正调用模型的,始终是你已经装好、已经登录的 CLI。MCP 的基本概念可以看官方的架构说明。
TaskDock 不是安全沙箱。适配器以你的系统账号权限运行,状态目录和日志里可能有提示词、输出和路径,所以状态目录要放在私密的位置,不要纳入版本控制,也只把可信的工作交给可信的适配器。
我本地实际跑过什么
下面这些验证是在我本地的 agent-tasks 上做的,用的是我自己写的那几个 CLI 适配器。TaskDock 就是从这套环境里抽出来的核心。
10 月 22 日的测试分两部分。一部分是真实的小任务,覆盖了多个 CLI 的读取、会话续接、文件产物,以及通过 MCP 调度。另一部分是用假 CLI 跑的 25 分钟持续压测,测取消、超时和去重,这部分没有调用任何模型。
所以能说的是:流程在小任务上跑通了,压测下状态机的行为也符合预期。它还没有经历过大量真实模型的协作;我也没有在同一个项目上做过对照,证明它能省钱、提速或提高质量。
共享工作区的规矩
几个参与者在同一个仓库里干活时,看到某个文件被改了,不能凭印象断定是谁覆盖了谁。先看 diff,再查是谁改的,用事实说话。
交接时讲清楚三件事:工具在哪,输入是什么格式,对方能写哪些文件。能各自独立产出的部分就并行;耦合紧的部分只留一个负责人,慢一点也值得。
任务简报模板
委派任务时,可以用下面这份模板,复制过去就能用:
目标:这次要交付什么,一句话说清
材料:需要读的文件、文档、上游任务的产物
允许改动:可以新建或修改的文件和目录
禁止改动:不能碰的文件、配置,以及他人负责的部分
验收:怎样算完成——要有哪些文件、要通过哪些检查
返回:产物文件清单 + 未完成项和原因“返回”里的未完成项最常被漏掉,偏偏也最有用。有了它,主控验收时就知道该去哪里找缺口,不用对着一句“已完成”去猜。这也正好对应 completed 和 accepted 之间的那一步。