猫咖
首页博客工具
搜索
语言
切换网站风格
选择主题颜色
点击特效
主题

猫咖 · 持续更新中,源码见 GitHub。

Index 20: Comprehensive Agent —— 全机制集成(收口)

2026年8月13日
AI智能体Kotlin后端

系列

使用Kotlin从0开发一个ClaudeCode

系列

使用Kotlin从0开发一个ClaudeCode

进度 20 / 21

使用Kotlin从0开发一个ClaudeCode

上一篇

Index 19: MCP Plugin —— 多传输 / 通道路由 / 工具池组装

下一篇

Index 21: Terminal UX —— Claude Code 风格终端交互

0. 二十个 Index 的终点

这是 Cat-Code 复刻 Claude Code 20 个核心机制的最后一个。s01 的消息循环到 s19 的 MCP 插件,20 个独立子系统已经各自闭环;s20 做的是两件「收口」的事:把 s17 自治认领 和 s18 worktree 隔离这两个「本该连在一起却刻意留到最后的」机制焊上,再给整台机器装 一个「一眼看全」的仪表盘。

TEXT
阶段一 最小闭环        s01 Agent Loop ── s02 Tool Use ── s03 Permission ── s04 Hooks
阶段二 智能体能力      s05 TodoWrite ── s06 Subagent ── s07 Skill ── s08 Compact ── s09 Memory
阶段三 系统可靠性      s10 SystemPrompt ── s11 Recovery ── s12 Task ── s13 Background ── s14 Cron
阶段四 多智能体协作    s15 Teams ── s16 Protocols ── s17 Autonomous ── s18 Worktree ── s19 MCP
                       └──────────────► s20 Comprehensive Agent ◄──────────────┘
                                       (把散件焊成一台机器)

1. 目标

  1. 打通 s17 自治认领 ↔ s18 worktree 隔离:自治循环派发任务时创建隔离目录、 完成时释放——这是 s18 spec §7 白纸黑字留给 s20 的接线。
  2. 提供 /status 统一视图:五个子系统(任务/团队/worktree/MCP/自治)一个命令看全。

完成后效果:

TEXT
> /status
Tasks:      2 ready · 1 in progress · 3 completed (6 total)
Team:       1 idle · 1 running (2 total)
Worktrees:  1 active · 3 released
MCP:        1 connected server · 1 tool registered
Autonomous: 1 active claim

在此之前,自治循环派发任务时,队友和主 agent 挤在同一个工作目录里;在此之后,每个 自治任务有自己的隔离目录 + 分支,且整台机器的状态一处可见。

2. 为什么需要

2.1 现实问题:两块拼图没拼上

s01-s19 每个机制都独立建好了,但有两块拼图始终悬着:

  1. s17 不认识 s18。AutoClaimer.tick() 派发任务时只发一条文本消息 ("Autonomous task t1: ...\nWork on this task..."),队友和主 agent 在同一个 目录里干活。s18 建好了隔离目录的能力(WorktreeManager.create/release),但 自治循环从没用过它——隔离能力是「死」的,没有被任何生产路径触发。

  2. 没有全局视图。排查一个自治任务从创建到完成,现在要横跨 /task、/team、 /worktree、/mcp、/auto 五个命令拼图。缺一个「一眼看到全貌」的入口——这既是 运维痛点,也是「这 20 个机制到底是不是一台机器」的验收标准。

2.2 设计原则(取舍依据)

  1. 默认参数向后兼容。AutoClaimer 加 worktreeManager: WorktreeManager? = null (AutoClaimer.kt:52),不传则行为与 s17 完全一致,现有 8 个 s17 测试零改动。 worktree 是可插拔增强,不是硬依赖。这延续了 s18「本 Index 不改 AutoClaimer」留下的 边界——s20 是唯一被授权改它的 Index(spec 里事先声明过)。

  2. 隔离生命周期三阶段语义。派发 create(幂等)、完成 release、回滚到 PENDING 保留(任务可能被重新认领,s18 幂等 create 直接复用)。worktree 的存活期 = 任务从 IN_PROGRESS 到终态的窗口。这是把「隔离目录」从一次性资源升级为「任务级 工作区」的关键语义。

  3. ComprehensiveAgent 是协调者,不是重写者。它组合既有的 TaskStore/TeamStore/ WorktreeManager/McpToolPool/AutoClaimer,做两件没人做的事:富化的周期摘要 (claimed→worktree path)+ 跨子系统统一状态。不重写 ReplLoop、不复制 s17 的 tick 逻辑——它调用 AutoClaimer 而非替代之(ComprehensiveAgent.kt:36)。

  4. 状态视图只读。/status 只读快照,不触发任何副作用(不 tick、不 connect、 不 create)。命令层的铁律:查询命令不改变系统状态——这反过来源自 s14 cron 查询、 s15 team 查询一路建立起来的惯例。

  5. 单一数据源。status() 的每个数字都来自对应子系统的既有查询方法 (TaskStore.loadAll()、TeamStore.all()、WorktreeManager.all()、 McpToolPool.status()),不另建缓存/计数——避免双份真相,这是 s18 博客里 「RELEASED 记录不删除」原则的延伸:审计数据只该有一份。

3. 核心设计与实现

3.1 架构全景

TEXT
AutoClaimer.tick()  ──(s20 接线)──►  WorktreeManager.create / release
       ▲                                    ▲
       │ 组合                                │ 注入
ComprehensiveAgent ──────────────────────────┤
  ├─ runAutonomousCycle() → ComprehensiveCycle(claimed→worktree path)
  └─ status()             → ComprehensiveStatus(五维统一视图)
       ▲
       │ ReplLoop 注入 + /status 命令

依赖方向仍是单向:comprehensive 依赖 autonomous/task/team/worktree/mcp, 后者都不反向依赖它。这是 20 个 Index 一路坚持的包依赖单向原则(CLAUDE.md 关键设计 决策 #5)在最后一站的收口体现。

3.2 AutoClaimer 的 worktree 接线(改 autonomous/AutoClaimer.kt)

改动分三处,全部是「注入 + 分支」,不触碰原有逻辑的骨架:

① 构造注入(AutoClaimer.kt:46-53):

KOTLIN
class AutoClaimer(
    private val taskStore: TaskStore,
    private val teamStore: TeamStore,
    private val messageBus: MessageBus,
    private val tracker: ClaimTracker = ClaimTracker(),
    private val config: AutonomousConfig = AutonomousConfig(),
    private val worktreeManager: WorktreeManager? = null   // s20 新增,默认 null
) {
    private val worktrees = ConcurrentHashMap<String, WorktreeRecord>()

worktrees 是「在途任务 → worktree 绑定」的内存映射,线程安全(ConcurrentHashMap, 与 s17 ClaimTracker 同款——IdleLoop 后台 tick 写,/auto tasks 主线程读)。

② 派发创建(AutoClaimer.kt:103-109):

KOTLIN
            // s20: 派发前创建 worktree(幂等;失败 warn 不阻断派发,消息退化为无路径)
            val worktree = worktreeManager?.let { wm ->
                try { wm.create(task.id).also { worktrees[task.id] = it } }
                catch (e: Exception) { logger.warn(e) { "worktree create failed for ${task.id}" }; null }
            }

关键语义:create 失败不阻断派发。worktree 是隔离增强,不是任务执行的前提—— 非 git 目录、磁盘满等场景下,任务照常派发,只是消息里没有 Worktree: 行(退化为 s17 行为)。这是 s11 fail-open 哲学在 s17↔s18 边界上的延续。

③ 完成释放(AutoClaimer.kt:75-78):

KOTLIN
                    // s20: 任务完成 → 释放隔离目录(s18 release 自愈优先,几乎不失败)
                    worktreeManager?.release(taskId)
                    worktrees.remove(taskId)

回滚分支(队友被停 → 任务回 PENDING)不动 worktrees——保留映射,下次认领时 幂等 create 复用同一目录/分支(s18 的 rev-parse --verify 分支复用路径)。

消息正文(AutoClaimer.kt:125-129):

KOTLIN
    private fun formatTask(task: TaskRecord, worktree: WorktreeRecord? = null): String {
        val base = "Autonomous task ${task.id}: ${task.title}\nWork on this task and report your results."
        return if (worktree != null) "$base\nWorktree: ${worktree.path}" else base
    }

队友收到消息后,文件工具以 Worktree: 行里的路径为工作根——协议不用变,一行文本 完成「隔离目录的传递」。这是 s18 博客里「派发消息携带 worktree 路径」衔接设计的具体 兑现。

新增观测点 worktreeOf(taskId)(AutoClaimer.kt:122)——供 /auto tasks 与 ComprehensiveAgent 查询在途任务的隔离目录。

3.3 ComprehensiveStatus —— 五维统一快照(ComprehensiveStatus.kt)

KOTLIN
data class ComprehensiveStatus(
    val tasks: TaskSummary,       // s12
    val team: TeamSummary,        // s15
    val worktrees: WorktreeSummary, // s18
    val mcp: McpSummary,          // s19
    val autonomous: AutonomousSummary // s17
)

每个 summary 都是「计数 + 就绪」的纯数据类。字段名直接对应五个 Index——这是 20 机制 的「目录页」,读一遍就知道各子系统是谁。

ComprehensiveCycle + ClaimedTask 把 s17 的 ClaimResult(claimed: List<String>) 富化为 List<ClaimedTask(taskId, worktreePath)>——claimed 从「一个 id」变成「一个 id + 它在哪个隔离目录里干活」。

3.4 ComprehensiveAgent —— 协调者(ComprehensiveAgent.kt:27-73)

KOTLIN
class ComprehensiveAgent(
    val taskStore: TaskStore,
    val teamStore: TeamStore,
    private val messageBus: MessageBus,
    private val worktreeManager: WorktreeManager,
    private val mcpPool: McpToolPool,
    private val autoClaimer: AutoClaimer
) {
    fun runAutonomousCycle(): ComprehensiveCycle {
        val result = autoClaimer.tick()
        val claimedTasks = result.claimed.map { id ->
            ClaimedTask(id, autoClaimer.worktreeOf(id)?.path)
        }
        return ComprehensiveCycle(claimedTasks, result.completed)
    }

    fun status(): ComprehensiveStatus { /* 五维聚合,见下 */ }
}

taskStore/teamStore 是 public——供测试与集成方预置状态(这是测试驱动出来的决策: ComprehensiveAgentTest 需要直接塞任务/队友才能驱动 runAutonomousCycle)。其余四个 依赖 private,暴露面最小化。

status() 的五维聚合(ComprehensiveAgent.kt:45-73)——每一维都是「既有查询 + 计数」:

KOTLIN
        val ready = TaskScheduler.readyTasks(tasks).count { it.status == TaskStatus.PENDING }
        val mcp = mcpPool.status()
        return ComprehensiveStatus(
            tasks = TaskSummary(ready, inProgressCount, completedCount, total),
            team = TeamSummary(idleCount, runningCount, total),
            worktrees = WorktreeSummary(activeCount, releasedCount),
            mcp = McpSummary(connectedServersCount, mcpPool.registeredTools().size),
            autonomous = AutonomousSummary(autoClaimer.claims().size)
        )

一个值得注意的设计:TaskSummary.ready 用的是 TaskScheduler.readyTasks(...)(s12 的「依赖已满足」语义),而不是简单的 PENDING 计数——PENDING 但被阻塞的任务不算 就绪。这个语义精度是 s12 埋下的,s20 只是正确地复用它。

3.5 /status 命令(StatusCommand.kt)

KOTLIN
class StatusCommand : ReplCommand {
    override val name = "/status"
    override val description = "Show unified status across all subsystems"

    override suspend fun execute(args: String, ctx: ReplContext): ReplCommandResult {
        val s = ctx.comprehensive.status()
        println("Tasks:      ${s.tasks.ready} ready · ${s.tasks.inProgress} in progress · ...")
        println("Team:       ${s.team.idle} idle · ${s.team.running} running (...)")
        println("Worktrees:  ${s.worktrees.active} active · ${s.worktrees.released} released")
        println("MCP:        ${s.mcp.connectedServers} connected server(s) · ${s.mcp.registeredTools} tool(s) registered")
        println("Autonomous: ${s.autonomous.activeClaims} active claim(s)")
        return ReplCommandResult.Continue
    }
}

无子命令、无状态、无副作用——三行 execute,纯粹读取 + 打印。这是 20 个 Index 里 最简单的命令之一,但它的价值在于是唯一把五个子系统并排呈现的入口。

3.6 ReplLoop 接线(两处顺序调整 + 一处注入)

ReplLoop 的改动最微妙的部分是构造顺序:原代码 autoClaimer 在 worktreeManager 之前构造(AutoClaimer(taskStore, teamStore, messageBus) 早于 worktree 的 init)。s20 把 worktree 构造提前到 AutoClaimer 之前(ReplLoop.kt s18 段移到 s17 段前),然后:

KOTLIN
        autoClaimer = AutoClaimer(taskStore, teamStore, messageBus, worktreeManager = worktreeManager)
        ...
        comprehensiveAgent = ComprehensiveAgent(
            taskStore, teamStore, messageBus, worktreeManager, mcpPool, autoClaimer
        )

这个顺序调整本身就是一个设计信号:s20 的接线让 s17 开始依赖 s18,而这是 20 个 Index 里第一次出现「阶段四内部的前后依赖」——s15→s16→s17→s18 本是一路往前的,s20 把它们最后两环往回焊了一下,形成闭环。

3.7 端到端:一个自治任务的完整隔离生命周期

在真实临时 git 仓库里,走一遍「任务 → 隔离目录 → 完成 → 释放」:

① 准备:仓库 master 分支,一个 PENDING 任务 t1,一个空闲队友 r1。

② 认领 tick:AutoClaimer.tick() 匹配 t1 → r1:

  • worktreeManager.create("t1") → git worktree add .cat-code/worktrees/t1 -b task/t1 HEAD
  • 队友 r1 收到消息:Autonomous task t1: ...\nWork on this task...\nWorktree: /repo/.cat-code/worktrees/t1

③ 队友工作:r1 在隔离目录里改文件、提交到 task/t1 分支。主仓库 master 分支 零感知(s18 的 .git/info/exclude 保证 git status 干净)。

④ 完成 tick:队友回到 IDLE,AutoClaimer.tick() 完成检测:

  • taskStore.update(t1 → COMPLETED)
  • worktreeManager.release("t1") → 删目录 + 删 task/t1 分支
  • worktrees.remove("t1") → 观测点清除

⑤ 异常路径(队友被停):队友 markCompleted → tick 检测到终态 → 任务回 PENDING, 但 worktrees 保留——下次认领时 create("t1") 走幂等分支复用同一目录/分支(s18 的 rev-parse --verify 探测到分支已存在,不带 -b 直接检出)。

⑥ 全程可观测:任一时点 /status 都能看到 t1 在哪个状态、worktree active 数、 自治 claim 数。

这就是「一个任务从就绪到完成」在 cat-code 里的完整闭环——s12 的持久化任务、s15 的 团队派发、s17 的自治循环、s18 的隔离目录、s19 的 MCP 工具,全部在这一条链路里各司 其职,s20 把它们焊成一台机器。

3.8 一个任务穿过 20 个机制(全景追踪)

「全部机制归到一个循环」到底是什么意思?用一个自治任务 t1 从创建到完成的完整 旅程,把 20 个 Index 逐一点名——这就是 s20 的收口验收:

步骤触发涉及的 Index
任务被 TaskStore 持久化用户 /task add t1 或 s05 todo 提升s05 TodoWrite · s12 Task
任务依赖被解析(就绪判定)TaskScheduler.readyTasks 查 blockedBys12 Task
空闲循环检测到就绪IdleLoop.tick(后台协程,忙态门控)s13 Background · s17 Autonomous
权限/审批管线就绪派发前无人类干预(自治),但工具调用走管线s03 Permission · s16 Protocols
worktree 创建WorktreeManager.create(git worktree add)s18 Worktree
任务派发消息携带路径MessageBus.send → teammate 收件箱s15 Teams
teammate 消费消息续跑AgentLoop 一轮 run(子智能体上下文隔离)s01 AgentLoop · s06 Subagent
teammate 调用工具ToolRegistry 分派(内置 + MCP 适配)s02 Tool · s19 MCP
工具调用前后 Hook 触发PreToolUse/PostToolUses04 Hooks
上下文超限压缩四层压缩(摘要/记忆/截断)s08 Compact · s09 Memory
System Prompt 分段拼接base + skills + memorys07 Skill · s10 SystemPrompt
工具失败重试/降级网络抖动自动重试、fallback 模型s11 Recovery
teammate 回到 IDLE消费协程结束,状态机 markIdles15 Teams
完成检测 → 释放 worktreeworktreeManager.releases17 Autonomous · s18 Worktree
任务置 COMPLETEDtaskStore.updates12 Task
统一状态可见/status 五维快照s20 Comprehensive

这一张表就是 s20 存在的意义:20 个机制不是并列的 20 个功能,而是一条链上的 20 个 齿轮。s20 让这条链第一次完整转动起来,且每一环都有观测点。

3.9 隔离生命周期的三阶段语义(详解)

worktree 在自治任务里的存活期,是 s20 最微妙的设计点之一。三个阶段:

阶段触发worktree 动作理由
创建认领(PENDING → IN_PROGRESS)create(幂等)隔离目录 + 分支就绪
保留队友 RUNNING / 回滚 PENDING不释放任务还在进行 / 可能重新认领
释放完成(teammate IDLE)release(删目录+分支)隔离不再需要,收尾

「回滚保留」是三个语义里最容易想错的:队友被停(终态)时任务回 PENDING,为什么不 释放 worktree?因为任务还没死——它只是换了/还没找到执行者。如果此时释放,下次 认领时 create 要重新建目录和分支,丢掉之前队友在隔离目录里留下的半成品。保留它, s18 的幂等 create(rev-parse --verify 探测分支已存在 → 不带 -b 检出)直接复用, 半成品不丢。

这个语义把「隔离目录」从「一次性资源」升级为「任务级工作区」——worktree 的生命周期 绑定的是任务的生命周期,不是「一次派发的生命周期」。这是 s20 对 s18 语义的一次 精确化,而非简单接线。

3.10 终端实操:before/after 对比

s20 之前,排查自治任务要横跨五个命令,且看不到 worktree 隔离:

TEXT
> /task list
  t1  IN_PROGRESS  fix login          ← 任务在跑
> /team
  r1  IDLE                           ← 队友闲了?
> /worktree list
  No active worktrees.               ← 但没有任何隔离目录!

三个命令拼起来才看出一个矛盾:任务 IN_PROGRESS、队友 IDLE、却没有 worktree—— 意味着队友在主工作区里干的活,隔离能力根本没被用上。

s20 之后,一个命令看全,且隔离目录真正生效:

TEXT
> /status
Tasks:      0 ready · 1 in progress · 0 completed (1 total)
Team:       0 idle · 0 running (1 total)      ← r1 处理完回到 IDLE 之前
Worktrees:  1 active · 0 released
MCP:        0 connected server(s) · 0 tool(s) registered
Autonomous: 1 active claim

> /auto tasks
Active claims:
  t1 -> r1  (worktree .cat-code/worktrees/t1)   ← 隔离目录路径直接可见

任务完成后:

TEXT
> /status
Tasks:      0 ready · 0 in progress · 1 completed (1 total)
Worktrees:  0 active · 1 released
Autonomous: 0 active claim

worktree 从 1 active 到 1 released——隔离目录随任务生命周期闭合。这就是 s20 的 验收:一处看全 + 隔离真正生效。

4. 错误处理总表

场景行为代码位置
worktreeManager 为 null(未接线)消息不带 Worktree 行,行为同 s17AutoClaimer.kt:105
派发时 create 失败(非 git 目录等)warn,任务仍派发(消息无路径),不阻断自治AutoClaimer.kt:105-108
完成时 release 失败warn,任务仍 COMPLETED(s18 release 自愈优先,几乎不失败)AutoClaimer.kt:75
回滚到 PENDING保留 worktree,仅映射不清除(复用留给下次认领)AutoClaimer.kt:79-83(else 分支不动 worktrees)
status() 查询纯只读,无副作用,不抛(各子系统查询本就 fail-open)ComprehensiveAgent.kt:45

5. 测试策略

测试类用例数覆盖点
AutoClaimerWorktreeTest4派发创建 + 消息含路径 / 完成释放 / 回滚保留 / 无 manager 行为同 s17
ComprehensiveAgentTest2runAutonomousCycle 富化 claimed→path / status 五维数字正确
StatusCommandTest1五维打印

worktree 相关测试复用 s18 的 WorktreeManager + 真实临时 git 仓库(initRepo() helper 与 s18 同款);无 worktree 场景复用 s17 的 TaskStore+TeamStore+MessageBus 直接 构造。合计新增 7 个用例,加上为 ReplContext.comprehensive 字段修补的 9 个既有命令 测试,全量 ./gradlew test 绿。

测试设计上的一个决策记录

ComprehensiveAgentTest 需要驱动 runAutonomousCycle() 触发认领——这意味着 ComprehensiveAgent 必须暴露 taskStore/teamStore(测试要预置 PENDING 任务和 IDLE 队友)。于是 taskStore/teamStore 从 private 改为 public val。这是一个「测试驱动 出 API」的例子:与其写一个复杂的内部 setter,不如把「这个协调者持有这些子系统」这一 事实本身暴露出来——它既是真的(协调者确实持有它们),也是集成方(测试、未来的 s20 使用者)真正需要的。

6. 开发过程记录

  1. worktree 构造顺序是唯一的「隐形改动」。ReplLoop 原代码 AutoClaimer 在 worktreeManager 之前构造,s20 必须把 worktree 构造提前。这个顺序调整看起来是 机械的,但它标志着 s17 开始依赖 s18——20 个 Index 里第一次出现阶段四内部的前后 依赖,闭环由此开始。

  2. 测试断言 fix-login 撞上 t1 的教训。AutoClaimerWorktreeTest 第一条用例 最初断言消息含 fix-login,但 worktree 目录名来自任务 id(t1)而非标题 (fix login)——s18 的 sanitize(taskId) 用的是 id。改法是把任务 id 设为 fix-login,让断言匹配。这是一个「实现语义优先于测试直觉」的再次验证(s18 的 sanitize 空串也有同款)。

  3. ComprehensiveAgent 的 public taskStore 是测试驱动的。见 5.1——把「协调者持有 子系统」这一事实从 private 改为 public,比造内部 setter 诚实。

  4. TDD 节奏:3 个实现 commit(AutoClaimer 接线 → ComprehensiveAgent → /status + 接线),全部先红后绿。spec/plan 2 个 docs commit。最终全量测试无回归。

6.5 设计原则逐条复盘

把 2.2 的五个原则在实现后逐条对账——这是收口 Index 该有的诚实:

原则 1「默认参数向后兼容」 —— 兑现。AutoClaimer 的 worktreeManager 默认 null, 8 个 s17 测试零改动。这条原则的可贵在于:它让「给旧代码加增强」不必「改旧代码的 所有调用方」。这是 20 个 Index 里反复出现、却总被低估的工程纪律。

原则 2「三阶段生命周期」 —— 兑现,且在 3.9 展开。最难的是「回滚保留」,它的 语义依据是 s18 幂等 create 的 rev-parse --verify 分支复用。如果 s18 没有做幂等 create,s20 的「回滚保留」就无从谈起——这是前序 Index 的设计为后续 Index 铺路的 又一个例子。

原则 3「协调者不重写」 —— 兑现。ComprehensiveAgent 只有 74 行,其中一半是 status() 的五维聚合。它调用 AutoClaimer.tick(),不复制 tick 逻辑。如果它重写了 tick,就会产生「两处 tick 逻辑」的维护债务——这是 20 个 Index 一路用「组合优于继承、 调用优于复制」换来的干净边界。

原则 4「只读状态」 —— 兑现。/status 三行 execute,无副作用。这条原则反过 来源自 s14 cron 查询、s15 team 查询、s19 /mcp 一路建立的惯例——查询命令不改变状态, 是命令层的一贯铁律。

原则 5「单一数据源」 —— 兑现。status() 的每个数字来自对应子系统的既有查询。 代价是 5 次独立的查询调用(而非一次 join),但换来「永无双份真相」。这是 s18 博客 「RELEASED 记录不删除」原则的延伸:审计数据只该有一份,查询只是读它,不是复制它。

6.6 测试替身的二十 Index 演进(回顾)

s20 的测试只有 7 个用例,但它背后是 20 个 Index 积累下来的测试替身方法论。回顾这条 演进线,能看到项目测试哲学的成熟:

Index测试替身解决的问题
s01-s02FakeLLMProvider / MockEngineLLM API 与工具注册的确定性
s06子智能体 Fake store上下文隔离不用真起协程
s08-s09手写 fake(不引 mock 框架)压缩/记忆的纯逻辑
s12-s14临时目录 + 真磁盘文件持久化/后台/cron 的真 IO
s15-s17TeamStore + MessageBus 直连团队/自治的状态机
s18真实 git 临时仓库worktree 行为的真验证
s19bash fixture / MockEngine / fake transport三种替身对应三层
s20复用 s17 Fake + s18 真 git接线语义的复合验证

这条线的共同原则(CLAUDE.md 测试规范):纯逻辑用 Fake,真 IO 用临时文件/临时仓库, 外部协议用 MockEngine,进程用真子进程。s20 的测试是这条原则的自然终点——它不需要 发明新替身,只需复用前 19 站造好的轮子。

7. 二十个 Index 的收口声明

7.1 二十机制清单与它在 s20 循环里的位置

#Index机制在 s20 闭环里的角色
s01Agent Loop消息循环 + LLM 抽象teammate 消费任务消息的运行时
s02Tool Use工具注册表 + 并发调用派发后 teammate 干活的工具箱
s03Permission审批管线工具调用前的允许/拒绝/询问
s04HooksPre/PostToolUse 扩展点工具调用前后的横切面
s05TodoWrite先计划后执行任务标题的来源(提升)
s06Subagent上下文隔离子智能体teammate 的运行时基础
s07Skill Loading按需注入System Prompt 的技能片段
s08Context Compact四层压缩长任务不爆上下文
s09Memory跨会话记忆管线压缩摘要 + 记忆注入
s10System Prompt运行时分段拼接base+skills+memory 组装
s11Error Recovery重试/fallback工具失败不中断任务
s12Task System磁盘持久化任务的持久化 + 依赖调度
s13Background Tasks线程执行IdleLoop 后台协程 + 通知队列
s14Cron Scheduler定时调度(未直接进闭环,独立时间轴)
s15Agent TeamsMessageBus + 收件箱任务派发与消息路由
s16Team Protocols关机握手/计划审批派发前的人类审批门
s17Autonomous Agents空闲循环就绪任务的自动认领
s18Worktree Isolation任务-目录绑定隔离目录 + 分支
s19MCP Plugin多传输工具池外部工具注入注册表
s20Comprehensive Agent全机制集成收口:接线 + 统一视图

s14 cron 是唯一「不在自治闭环里」的机制——它有自己的时间轴(定时触发),不经过 任务认领/派发链路。这是正确的:不是所有机制都要塞进同一条链,s20 收口的是自治 任务的闭环,cron 是并行的另一条时间轴。收口不是大一统,是「该连的连上,该独立的 独立」。

7.2 四阶段交付总览

s20 完成后,四阶段全部交付:

阶段交付关键产出
一 最小闭环s01-s04AgentLoop / ToolRegistry / Permission / Hooks
二 智能体能力s05-s09Todo / Subagent / Skill / Compact / Memory
三 系统可靠性s10-s14SystemPrompt / Recovery / Task / Background / Cron
四 多智能体协作s15-s20Teams / Protocols / Autonomous / Worktree / MCP / Comprehensive

Cat-Code 从 s01 的一个消息循环,长成了一台完整的多智能体 CLI 平台:它能派生团队 (s15)、审批计划(s16)、自治派发(s17)、隔离工作(s18)、外接工具(s19),最后 用一张仪表盘(s20)把这一切收束进一个可观测的闭环。

贯穿 20 个 Index 的三条主线在此清晰可见:

  1. 包依赖单向(s01 埋下,s20 收官)——每个新包只依赖已有包,从不反向;
  2. fail-open 哲学(s08 压缩到 s20 接线)——局部失败不拖垮全局,warn 后继续;
  3. 接口隔离的回报(s02 Tool / s01 LLMProvider)——s19 适配 MCP 工具、s20 组合 协调者,都零侵入既有代码。

这是 Cat-Code 的终点,也是一个完整可扩展智能体平台的起点。

8. 三条主线:贯穿 20 个 Index 的工程哲学

收官之际,回看 20 个 Index,有三条主线清晰可见——它们不是某个 Index 的设计决策, 而是整个项目反复出现、彼此强化的工程哲学。逐条用具体例子复盘:

主线一:包依赖单向(s01 埋下 → s20 收官)

CLAUDE.md 关键设计决策 #5:「agent 包不依赖 permission/hooks/task/team,增强层可插拔」。

这条原则在 20 个 Index 里被反复验证:

  • s02 的 Tool 接口让 s19 能适配 MCP 工具而不改一行内置工具代码;
  • s06 让 ToolRegistry 从全局单例改为 class,因为子智能体需要独立实例——这是 「依赖单向」第一次被迫调整;
  • s18 刻意不 import task 包(taskId 只做 String 引用),让 worktree 独立于任务系统;
  • s20 的 comprehensive 依赖五个子系统,五个子系统都不反向依赖它。

依赖单向的回报在 s20 达到顶点:ComprehensiveAgent 组合五个子系统时,不需要理解 它们任何一个的内部,只需要理解它们暴露的查询接口——这是「能改内部而不破坏消费者」 的教科书级体现。

主线二:fail-open 哲学(s08 压缩 → s20 接线)

「局部失败不拖垮全局,warn 后继续」贯穿全程:

  • s08 压缩失败时降级为不压缩(宁可超限也不崩);
  • s11 网络抖动自动重试、降级 fallback 模型;
  • s12/s18/s19 存储损坏一律 fail-open 为空(文件损坏不阻止启动);
  • s17 单个 teammate 失败任务回 PENDING 可重新认领;
  • s20 worktree create 失败不阻断任务派发(消息退化为无路径)。

这条哲学在 s20 的 AutoClaimer.kt:105-108 得到最精炼的表达:try { create } catch { warn; null }——增强失败就退化为不增强,而不是让增强绑架核心功能。

主线三:接口隔离的复利(s01 LLMProvider → s19 MCP → s20 组合)

「面向接口编程」不是一次性的抽象练习,是贯穿 20 站的复利投资:

  • s01 的 LLMProvider 接口让所有后续测试用 FakeLLMProvider 注入;
  • s02 的 Tool 接口让 s19 用 McpToolAdapter 把远端工具适配成本地工具;
  • s19 的 McpTransport 接口让 stdio/HTTP 两种传输可替换、可独立测试;
  • s20 的 ComprehensiveAgent 组合的是「接口背后的既有实现」,而非复制逻辑。

复利的本质:早期花 10 分钟定义的接口,后期省下 10 次「改所有调用方」的重构。 s20 之所以能 74 行收口,正是因为前 19 站的接口边界都画在了正确的位置。

9. 已知限制与未来

收口不代表完美。s20 的诚实清单:

  1. MCP 异步注册的竞态(s19 已知):REPL 启动后首条消息发出时,MCP 远端工具 可能尚未就绪。当前用「下一轮重试」缓解,未做「等待注册完成」的启动门。s19 spec 已声明为可接受竞态。
  2. SSE 响应不支持(s19 非目标):Streamable HTTP 只做请求-响应,服务器流式 推送(如进度、sampling)未实现。
  3. worktree 合并不自动(s18 预留):任务完成释放目录后,task/<id> 分支默认 删除,未做「merge 回主干」的策略(ff-only?人工审?)——WorktreeRecord.branch 为它留了钩子,s20 未启用。
  4. cron 未进自治闭环(见 7.1):定时任务是并行时间轴,未与任务认领/派发链路 打通——这是有意为之的边界,不是遗漏。

这些限制的共性:它们都是「能力边界」,不是「实现缺陷」——每个都在对应的 spec 里被明确列为非目标或已知竞态。收口 Index 的职责是画清边界,不是假装没有边界。

10. 结语

从 s01 的一个 AgentLoop,到 s20 的一台完整机器,Cat-Code 走完了复刻 Claude Code 20 个核心机制的旅程。这台机器现在能:派生团队、审批计划、自治派发、隔离工作、 外接工具、一张仪表盘看全局——而支撑这一切的,是 20 个 Index 里反复出现的三条 工程主线:包依赖单向、fail-open 哲学、接口隔离的复利。

s20 是终点,但机器的边界就是它未来的起点——worktree 合并策略、SSE 流式、MCP 注册 启动门,都在等下一个「Index 21」。

目录

当前章节:0. 二十个 Index 的终点

  • 1. 0. 二十个 Index 的终点
  • 2. 1. 目标
  • 3. 2. 为什么需要
  • 4. 2.1 现实问题:两块拼图没拼上
  • 5. 2.2 设计原则(取舍依据)
  • 6. 3. 核心设计与实现
  • 7. 3.1 架构全景
  • 8. 3.2 AutoClaimer 的 worktree 接线(改 autonomous/AutoClaimer.kt)
  • 9. 3.3 ComprehensiveStatus —— 五维统一快照(ComprehensiveStatus.kt)
  • 10. 3.4 ComprehensiveAgent —— 协调者(ComprehensiveAgent.kt:27-73)
  • 11. 3.5 /status 命令(StatusCommand.kt)
  • 12. 3.6 ReplLoop 接线(两处顺序调整 + 一处注入)
  • 13. 3.7 端到端:一个自治任务的完整隔离生命周期
  • 14. 3.8 一个任务穿过 20 个机制(全景追踪)
  • 15. 3.9 隔离生命周期的三阶段语义(详解)
  • 16. 3.10 终端实操:before/after 对比
  • 17. 4. 错误处理总表
  • 18. 5. 测试策略
  • 19. 测试设计上的一个决策记录
  • 20. 6. 开发过程记录
  • 21. 6.5 设计原则逐条复盘
  • 22. 6.6 测试替身的二十 Index 演进(回顾)
  • 23. 7. 二十个 Index 的收口声明
  • 24. 7.1 二十机制清单与它在 s20 循环里的位置
  • 25. 7.2 四阶段交付总览
  • 26. 8. 三条主线:贯穿 20 个 Index 的工程哲学
  • 27. 主线一:包依赖单向(s01 埋下 → s20 收官)
  • 28. 主线二:fail-open 哲学(s08 压缩 → s20 接线)
  • 29. 主线三:接口隔离的复利(s01 LLMProvider → s19 MCP → s20 组合)
  • 30. 9. 已知限制与未来
  • 31. 10. 结语
回到顶部

相关推荐

查看全部文章
Index 21: Terminal UX —— Claude Code 风格终端交互

Index 21: Terminal UX —— Claude Code 风格终端交互

2026年8月21日

Index 21 将 cat-code 终端交互升级为 Claude Code 风格,解决旧 REPL 黑箱、无中断及输入体验差的问题。通过 JLine3 与 Mordant 实现历史补全、实时工具可见性、Spinner 状态及 Esc 中断。核心采用 UI 与 AgentLoop 解耦的事件流架构,支持内联权限菜单与优雅降级。同时配置 logback 收敛控制台日志,确保 TUI 清爽且功能无损,显著提升可用性。

Index 19: MCP Plugin —— 多传输 / 通道路由 / 工具池组装

Index 19: MCP Plugin —— 多传输 / 通道路由 / 工具池组装

2026年8月13日

引入 MCP(Model Context Protocol)插件机制,通过多传输适配与通道路由,把外部 MCP 服务器的工具动态接入智能体的工具池。工具注册从静态编译期扩展为运行时动态组装,让智能体能力随外部服务即插即用。

Index 18: Worktree Isolation —— 任务-目录绑定

Index 18: Worktree Isolation —— 任务-目录绑定

2026年8月13日

引入工作区隔离机制,把每个任务绑定到独立的 git worktree 目录,让并行执行的智能体互不干扰。通过任务-目录绑定与隔离环境的自动创建回收,多智能体可以在各自的工作副本中安全并行开发。