Cat Blog
HomeBlogTools
Search
Language
Choose site style
Choose accent color
Click Effect
Theme

Cat Blog · Updated regularly. Source code is available on GitHub.

Index 06: Subagent — 异步后台子智能体

July 30th, 2026
AI智能体Kotlin后端

Series

使用Kotlin从0开发一个ClaudeCode技术文章

Series

使用Kotlin从0开发一个ClaudeCode

Progress 6 / 21

使用Kotlin从0开发一个ClaudeCode

Previous in series

Index 05: TodoWrite — 让智能体先计划后执行

Next in series

Index 07: Skill Loading — 按需注入式技能系统

目标

s01 到 s05 让智能体具备了工具调用、权限管控、Hook 扩展、TodoWrite 规划能力。但接到"重构 UserService 和 OrderService 两个独立文件"这类任务时,它仍然串行处理——先做完 A,再做 B。主智能体在等 A 完成期间什么也做不了。

Index 06 的目标是引入异步后台子智能体:

  1. task 工具派发任务——主智能体调用 task 工具,立即拿到 task_id,后台 launch 一个独立子智能体执行
  2. query_task 工具查询结果——主智能体继续推理,后续用 query_task 轮询任务状态/获取结果
  3. 完全隔离的依赖图——子智能体持有独立的 messagesHistory、ToolRegistry、TodoStore、HookManager
  4. 结构化并发——ReplLoop 持有 CoroutineScope(SupervisorJob + Dispatchers.Default),退出时 cancel() 取消所有子任务

完成后的效果——主智能体可以并行派发多个独立任务:

TEXT
> 帮我重构 UserService.kt 和 OrderService.kt 两个文件

[主智能体调用 task 派发任务 1: 重构 UserService]
Task dispatched: task_a1b2c3...
Use query_task to check status.

[主智能体调用 task 派发任务 2: 重构 OrderService]
Task dispatched: task_d4e5f6...
Use query_task to check status.

[主智能体调用 query_task 查询任务 1]
Task task_a1b2c3 COMPLETED.

Result:
UserService.kt 已重构:抽取了 validateUserId...

[主智能体调用 query_task 查询任务 2]
Task task_d4e5f6 COMPLETED.

Result:
OrderService.kt 已重构:抽取了 validateOrderId...

两个子智能体在后台并行跑,主智能体只负责派发和收集结果。


架构:异步后台模型

Index 06 在 s05 的基础上增加了一个"后台任务系统"。与 TodoWrite 不同的是,它引入了并发——主智能体不再阻塞在单个 AgentLoop.run() 里:

TEXT
                用户输入 "重构 UserService 和 OrderService"
                              │
                              ▼
                    ┌──────────────────┐
                    │   主 AgentLoop   │
                    │  messagesHistory │
                    └──┬─────────────┘
                       │ LLM 调用 task 工具
                       ▼
            ┌──────────────────────────────────┐
            │   TaskTool.execute (写工具)        │
            │                                    │
            │  ① Permission (s03) ──── 放行     │
            │  ② PreToolUse Hook (s04) ─ 放行    │
            │  ③ SubagentStore.createRecord     │
            │     + markRunning (PENDING→RUNNING)│
            │  ④ scope.launch {                  │
            │       subagent.run()  ← 后台协程    │
            │     }                              │
            │  ⑤ 返回 "Task dispatched: task_xxx"│
            │  ⑥ PostToolUse Hook (s04) ─ 透传   │
            └──────────────────────────────────┘
                       │
                       ▼ tool_result 进入主 AgentLoop.messagesHistory
                       │
                       ▼ 主智能体继续推理(可能再次 task 或 query_task)
                       │
                       ▼ ...后续轮询 query_task 拿到 COMPLETED + finalText

关键洞察:TaskTool 与其他工具一样是普通 Tool,穿过同一个 Permission 管线和 Pre/Post hooks。它不修改 AgentLoop 的核心循环逻辑,只在 execute 里 launch 一个后台协程。子智能体内部跑的是另一个独立的 AgentLoop 实例。

层Index 05 状态Index 06 变更
配置AgentConfig 4 字段+maxConcurrentSubagents(默认 5)
工具ToolRegistry 是单例 objectobject → class,构造注入
待办TodoStore 是单例 objectobject → class,构造注入
智能体AgentLoop 构造 4 参数+toolRegistry 参数(构造注入)
权限ToolCategoryRule 无状态+registry 构造参数
子智能体—新建 subagent/ 包,6 个主类 + 2 个工具
REPL主循环 while(true)+CoroutineScope + try/finally cancel()

第一层:重构两个单例

为什么必须先重构?

子智能体要"完全隔离"——独立 messagesHistory、独立工具集、独立 todo 列表。如果 ToolRegistry 和 TodoStore 还是全局单例,主智能体和子智能体会共享同一份状态,隔离形同虚设。

ToolRegistry:object → class

KOTLIN
// 改造前(s02-s05)
object ToolRegistry {
    private val tools = mutableMapOf<String, Tool>()
    fun register(tool: Tool) { tools[tool.name] = tool }
    fun get(name: String): Tool = tools[name] ?: throw UnknownToolException(name)
    // ...
}

// 改造后(s06)
class ToolRegistry {
    private val tools = mutableMapOf<String, Tool>()
    fun register(tool: Tool) { tools[tool.name] = tool }
    fun get(name: String): Tool = tools[name] ?: throw UnknownToolException(name)
    // ...签名完全不变
}

签名零变化,只是从 object 改为 class。KDoc 早在 s02 就预埋了演进说明("Index 06 Subagent 需要多实例时再改为 class"),这次正是兑现承诺。

AgentLoop 构造函数注入

KOTLIN
class AgentLoop(
    private val llmProvider: LLMProvider,
    private val systemPrompt: String,
    private val config: AgentConfig = AgentConfig(),
    private val hooks: AgentLoopHooks = AgentLoopHooks(),
    private val toolRegistry: ToolRegistry = ToolRegistry()  // 新增,默认值保向后兼容
)

3 处单例引用点改为实例引用:

  • L64: ToolRegistry.getDefinitions() → toolRegistry.getDefinitions()
  • L138: ToolRegistry.get(tc.name).isReadOnly → toolRegistry.get(tc.name).isReadOnly
  • L201: ToolRegistry.get(toolCall.name) → toolRegistry.get(toolCall.name)

隐藏耦合点:ToolCategoryRule

重构时容易漏掉权限规则——ToolCategoryRule 也硬编码依赖 ToolRegistry 单例:

KOTLIN
// 改造前
class ToolCategoryRule : PermissionRule {
    override suspend fun evaluate(toolCall: ToolCall): PermissionResult {
        val isReadOnly = try {
            ToolRegistry.get(toolCall.name).isReadOnly  // ← 隐藏耦合
        } catch (_: UnknownToolException) {
            return PermissionResult.ASK
        }
        return if (isReadOnly) PermissionResult.ALLOW else PermissionResult.ASK
    }
}

// 改造后
class ToolCategoryRule(private val registry: ToolRegistry) : PermissionRule {
    override suspend fun evaluate(toolCall: ToolCall): PermissionResult {
        val isReadOnly = try {
            registry.get(toolCall.name).isReadOnly  // 注入实例
        } catch (_: UnknownToolException) {
            return PermissionResult.ASK
        }
        return if (isReadOnly) PermissionResult.ALLOW else PermissionResult.ASK
    }
}

TodoStore:同样的演进

TodoStore 从 object 改为 class,TodoWriteTool 增加构造参数 store: TodoStore。顺手消掉一个日志耦合——原实现用 TodoStore.completedCount() / size() 做日志统计,改造后直接用本地 parsed 列表统计:

KOTLIN
// 改造前
TodoStore.replaceAll(parsed)
logger.info { "Todo list updated: ${TodoStore.completedCount()}/${TodoStore.size()} done" }

// 改造后
store.replaceAll(parsed)
logger.info {
    "Todo list updated: ${parsed.count { it.status == TodoStatus.COMPLETED }}/${parsed.size} done"
}

工具刚写入的数据,没必要再查一次 store 来做日志统计——本地 parsed 就是同一份真相。

测试改造模式

所有依赖单例的测试统一改造:删 beforeTest/afterTest { clear() },每用例 new 局部实例。

KOTLIN
// 改造前
beforeTest { ToolRegistry.clear() }
"register adds tool" {
    val registry = ToolRegistry  // 误把 object 当类型
    registry.register(FakeEchoTool())
}

// 改造后
"register adds tool" {
    val registry = ToolRegistry()  // 局部实例
    registry.register(FakeEchoTool())
}

AgentLoopTest 中 18+ 处 AgentLoop(...) 实例化都新增 toolRegistry = registry 参数。TodoWrite 相关 3 个用例额外注入 val todoStore = TodoStore(),断言改用 todoStore.size()。


第二层:子智能体数据模型

SubagentStatus——状态机

KOTLIN
enum class SubagentStatus { PENDING, RUNNING, COMPLETED, FAILED }

状态机:

TEXT
PENDING → RUNNING → { COMPLETED | FAILED }
  • PENDING:TaskTool 刚创建记录,尚未 launch
  • RUNNING:协程已 launch,子智能体 AgentLoop 正在执行
  • COMPLETED:成功结束,finalText 已填充
  • FAILED:抛出异常(非 CancellationException),error 已填充

SubagentRecord——不可变快照

KOTLIN
data class SubagentRecord(
    val id: String,              // "task_${UUID}"
    val description: String,     // 简短描述
    val prompt: String,          // 任务正文
    val status: SubagentStatus,
    val finalText: String? = null,    // COMPLETED 时填充
    val error: String? = null,        // FAILED 时填充
    val createdAt: Long,
    val completedAt: Long? = null
)

每次状态流转通过 copy 生成新实例替换旧值(见 SubagentStore),保证读取方拿到的一定是某一时刻的完整快照——典型的"不可变值对象 + 可变引用"模式。


第三层:Subagent 与 SubagentFactory

Subagent——持有独立 AgentLoop

KOTLIN
class Subagent(
    private val loop: AgentLoop,
    val description: String,
    private val prompt: String
) {
    suspend fun run(): String = loop.run(prompt)
}

极其简单的包装——子智能体就是"一个 AgentLoop + 任务 prompt"。但它持有的 AgentLoop 实例的所有依赖(messagesHistory、ToolRegistry、TodoStore、HookManager)都是 SubagentFactory.create 时新建的独立实例。

SubagentFactory——组装隔离的依赖图

KOTLIN
class SubagentFactory(
    private val llmProvider: LLMProvider,
    private val config: AgentConfig
) {
    fun create(description: String, prompt: String): Subagent {
        val todoStore = TodoStore()                         // 独立 todo
        val toolRegistry = ToolRegistry().apply {
            register(ReadFileTool())
            register(WriteFileTool())
            register(BashTool())
            register(TodoWriteTool(todoStore))
            // 不注册 Task/QueryTask——禁止递归派发孙智能体
        }
        val hookManager = HookManager().apply {
            LoggingHook().registerTo(this)
            // 不注册 TimingHook/TodoDisplayHook——避免干扰主 REPL 输出
        }
        val loop = AgentLoop(
            llmProvider = llmProvider,
            systemPrompt = SubagentPrompts.DEFAULT,
            config = config,
            hooks = AgentLoopHooks(
                onPreToolUse = { tc -> hookManager.firePreToolUse(tc) },
                onPostToolUse = { tc, result -> hookManager.firePostToolUse(tc, result) }
            ),
            toolRegistry = toolRegistry
        )
        return Subagent(loop, description, prompt)
    }
}

关键设计取舍:

  1. 不注入 PermissionPipeline——子智能体后台运行无交互界面,ReadLinePrompter 会死锁;主智能体在 TaskTool 调用阶段已对"派发子任务"这一动作审批,子智能体内部工具调用不再二次确认
  2. 不注册 task/query_task——天然禁止递归派发孙智能体,避免资源失控
  3. 只注册 LoggingHook——便于调试,不注册 TodoDisplayHook(避免 println 干扰主 REPL 输出)

这是有意为之的"降权模型"——子智能体能力是主智能体的子集,但没有"再派发"能力。

为什么不提取 AgentLoopCore 抽象?

考虑过提取一个 AgentLoopCore 只包含核心循环逻辑,让 AgentLoop 和 Subagent 分别继承。但评估后不提取:

  • AgentLoop 已支持构造注入全部依赖(llmProvider/systemPrompt/config/hooks/toolRegistry)
  • 子智能体与主智能体的区别仅在"参数不同"
  • 直接 new AgentLoop(...) 即可,符合"严格迭代,不做预留"原则
  • 提取会增加无用抽象层,违反 YAGNI

第四层:SubagentStore

KOTLIN
class SubagentStore {
    private val records = ConcurrentHashMap<String, SubagentRecord>()
    private val runningCount = AtomicInteger(0)

    fun createRecord(description: String, prompt: String): String { /* 生成 task_id + PENDING */ }
    fun markRunning(id: String) { /* PENDING→RUNNING,count++ */ }
    fun markCompleted(id: String, finalText: String) { /* RUNNING→COMPLETED,count-- */ }
    fun markFailed(id: String, error: String) { /* RUNNING→FAILED,count-- */ }
    fun get(id: String): SubagentRecord? = records[id]
    fun runningCount(): Int = runningCount.get()
    fun clear() { records.clear(); runningCount.set(0) }
}

线程安全设计

TaskTool 在主 AgentLoop 协程中调用 createRecord/markRunning,子智能体在后台 launch 的协程中调用 markCompleted/markFailed——两者并发访问,故所有写操作都基于 ConcurrentHashMap.computeIfPresent 做状态机校验:

KOTLIN
fun markRunning(id: String) {
    records.computeIfPresent(id) { _, record ->
        if (record.status == SubagentStatus.PENDING) {
            runningCount.incrementAndGet()
            record.copy(status = SubagentStatus.RUNNING)
        } else {
            record // 状态不符,保持原样
        }
    }
}

非法转换(如 COMPLETED → RUNNING、PENDING → COMPLETED)通过 computeIfPresent 内状态检查自然忽略——只允许从预期前置状态转换。

runningCount 语义

只统计 RUNNING 状态的记录数,用于 TaskTool 的并发上限检查。markRunning 时 +1,markCompleted/markFailed 时 -1。AtomicInteger 保证并发场景下的精确计数。


第五层:Task 与 QueryTask 工具

TaskTool——异步派发

KOTLIN
class TaskTool(
    private val factory: SubagentFactory,
    private val store: SubagentStore,
    private val scope: CoroutineScope,
    private val maxConcurrent: Int
) : Tool {
    override val name = "task"
    override val isReadOnly = false  // 副作用:创建协程;串行执行避免并发竞态

    override suspend fun execute(input: JsonObject): ToolResult {
        val description = input["description"]?.jsonPrimitive?.content
            ?: return ToolResult("", "Error: description is required", isError = true)
        val prompt = input["prompt"]?.jsonPrimitive?.content
            ?: return ToolResult("", "Error: prompt is required", isError = true)

        if (store.runningCount() >= maxConcurrent) {
            return ToolResult("", "Error: max concurrent subagents ($maxConcurrent) reached", isError = true)
        }

        val taskId = store.createRecord(description, prompt)
        store.markRunning(taskId)

        scope.launch {
            try {
                val subagent = factory.create(description, prompt)
                val finalText = subagent.run()
                store.markCompleted(taskId, finalText)
            } catch (e: CancellationException) {
                throw e  // 重新抛出,遵守结构化并发
            } catch (e: Exception) {
                store.markFailed(taskId, e.message ?: "Unknown error")
            }
        }

        return ToolResult("", "Task dispatched: $taskId\nUse query_task to check status.")
    }
}

为什么 isReadOnly = false? 派发会创建协程、占用资源。AgentLoop 的并发模型是"读工具并行,写工具串行"——TaskTool 标为写工具,确保主智能体一次只派发一个任务,避免 runningCount >= maxConcurrent 检查出现 TOCTOU 竞态。

QueryTaskTool——同步查询

KOTLIN
class QueryTaskTool(private val store: SubagentStore) : Tool {
    override val name = "query_task"
    override val isReadOnly = true  // 读操作,可并发查询

    override suspend fun execute(input: JsonObject): ToolResult {
        val taskId = input["task_id"]?.jsonPrimitive?.content
            ?: return ToolResult("", "Error: task_id is required", isError = true)

        val record = store.get(taskId)
            ?: return ToolResult("", "Error: task not found: $taskId", isError = true)

        val content = when (record.status) {
            SubagentStatus.PENDING -> "Task $taskId is PENDING (not yet started)."
            SubagentStatus.RUNNING -> "Task $taskId is RUNNING. Try again later."
            SubagentStatus.COMPLETED -> "Task $taskId COMPLETED.\n\nResult:\n${record.finalText ?: ""}"
            SubagentStatus.FAILED -> "Task $taskId FAILED.\n\nError:\n${record.error ?: "Unknown error"}"
        }
        return ToolResult("", content, isError = record.status == SubagentStatus.FAILED)
    }
}

FAILED 状态返回 isError = true——让 LLM 在 tool_result 中明确感知失败,而不是把错误信息当作"正常结果"误用。

包依赖方向:为什么 TaskTool/QueryTaskTool 在 subagent/ 包?

按 s02 的工具约定,所有 Tool 实现都放在 tool/tools/ 包。但 TaskTool/QueryTaskTool 依赖 SubagentFactory/SubagentStore,如果把它们放在 tool/tools/,会引入反向依赖:

TEXT
tool.tools → subagent → agent, tool

这破坏了 tool → (独立) 的单向依赖。放在 subagent/ 包内则保持合法:

TEXT
subagent → agent, llm, tool      ✓ 单向

第六层:ReplLoop 集成

结构化并发

KOTLIN
class ReplLoop(...) {
    private lateinit var subagentScope: CoroutineScope
    private lateinit var subagentStore: SubagentStore
    // ...

    fun start() {
        subagentScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

        // ...组装工具、权限、hooks、AgentLoop...

        printWelcome()
        try {
            while (true) {
                // REPL 主循环
            }
        } finally {
            subagentScope.cancel()  // 结构化并发:退出时取消所有子智能体
        }
        printGoodbye()
    }
}

为什么用 SupervisorJob? 普通 Job 的子协程失败会传播给父协程和其他兄弟——一个子智能体抛异常会让整个 subagentScope 取消。SupervisorJob 隔离了失败——单个子任务 FAILED 不影响其他正在运行的子任务。

为什么用 Dispatchers.Default? 子智能体跑 LLM 调用和工具执行,是 CPU + IO 混合型工作。Default 调度器在共享线程池上运行,不阻塞主线程。主 REPL 的 runBlocking { agentLoop.run(input) } 在调用方线程上跑,与后台子任务互不阻塞。

/clear 命令扩展

KOTLIN
CMD_CLEAR, CMD_C -> {
    agentLoop.messagesHistory.clear()
    approvalStore.clear()
    todoStore.clear()
    subagentStore.clear()  // 新增:清空对话也清空任务列表
    true
}

清空对话时也清空 subagentStore——避免悬挂任务。正在运行的后台协程不会被中断(它们持有对 AgentLoop 的引用),但它们的 markCompleted/markFailed 会被 computeIfPresent 忽略(记录已被 clear 删除)。


关键设计取舍汇总

决策点选择理由
AgentLoopCore 抽象不提取AgentLoop 已支持构造注入;提取增加无用抽象层
子智能体 PermissionPipeline不注入后台运行无交互界面,ReadLinePrompter 死锁;主智能体 Task 调用阶段已审批
子智能体 HookManager是,仅注册 LoggingHook便于调试,不注册 TodoDisplayHook 避免干扰主 REPL
子智能体工具集4 内置 + TodoWriteTool,不含 Task/QueryTask天然禁止递归派发孙智能体
Task 工具 isReadOnlyfalse副作用:创建协程;串行执行避免并发竞态
QueryTask 工具 isReadOnlytrue读操作,可并发查询
SubagentStore 类型class(ReplLoop 持有)与 ToolRegistry/TodoStore 一致;测试隔离靠局部实例
Task/QueryTask 包路径subagent/ 包避免 tool.tools → subagent 反向依赖
协程 scopeSupervisorJob + Dispatchers.Default单任务失败不传播;不阻塞主线程
异步结果获取Task + QueryTask 两工具主智能体派发后继续推理,后续轮询查询
异常处理CancellationException 重新抛出,其他异常转 FAILED遵守结构化并发;失败信息通过 QueryTask 返回 LLM

文件清单

TEXT
src/main/kotlin/com/sepcai/code/
├── subagent/                          # [新建] 子智能体领域包
│   ├── SubagentStatus.kt              # 状态枚举:PENDING/RUNNING/COMPLETED/FAILED
│   ├── SubagentRecord.kt              # 不可变任务记录 data class
│   ├── SubagentPrompts.kt             # 子智能体默认 systemPrompt
│   ├── Subagent.kt                    # 包装独立 AgentLoop
│   ├── SubagentFactory.kt             # 工厂:组装隔离依赖图
│   ├── SubagentStore.kt               # ConcurrentHashMap + AtomicInteger 状态存储
│   ├── TaskTool.kt                    # 派发工具(isReadOnly=false)
│   └── QueryTaskTool.kt               # 查询工具(isReadOnly=true)
├── agent/
│   ├── AgentConfig.kt                 # [修改] +maxConcurrentSubagents 字段
│   └── AgentLoop.kt                   # [修改] +toolRegistry 构造参数 + 3 处引用点
├── tool/
│   ├── ToolRegistry.kt                # [修改] object → class
│   └── tools/
│       └── TodoWriteTool.kt           # [修改] +store 构造参数 + 消掉日志耦合
├── todo/
│   └── TodoStore.kt                   # [修改] object → class
├── permission/rules/
│   └── ToolCategoryRule.kt            # [修改] +registry 构造参数
└── repl/
    └── ReplLoop.kt                    # [修改] +CoroutineScope + Task/QueryTask 注册 + try/finally

src/test/kotlin/com/sepcai/code/
├── subagent/                          # [新建]
│   ├── SubagentFactoryTest.kt         # 5 个用例(隔离性、工具集、run 返回值)
│   ├── SubagentStoreTest.kt           # 9 个用例(状态机、并发安全)
│   ├── TaskToolTest.kt                # 5 个用例(派发、并发上限、参数校验)
│   └── QueryTaskToolTest.kt           # 6 个用例(四种状态、未知名、缺参数)
├── tool/
│   ├── ToolRegistryTest.kt            # [修改] 删 beforeTest,每用例局部实例
│   └── TodoWriteToolTest.kt           # [修改] 删 before/afterTest,每用例注入 store
├── todo/
│   └── TodoStoreTest.kt               # [修改] 删 before/afterTest,每用例局部实例
├── permission/rules/
│   └── ToolCategoryRuleTest.kt        # [修改] 删 clear,每用例注入 registry
└── agent/
    └── AgentLoopTest.kt               # [修改] 删 afterTest,每用例注入 registry + 2 个集成测试

8 个新源文件 + 7 个修改源文件 · 4 个新测试文件 + 5 个测试修改 · 全部通过。


下一站

s06 完成了阶段二的第二个能力——异步并行。智能体现在能:

  • 派发独立子任务到后台执行
  • 主智能体继续推理,不被阻塞
  • 后续轮询查询子任务状态和结果

但子智能体的结果是内存存储的——/clear 或 /exit 后所有 task 记录消失。当你想让智能体"昨天的重构任务进度继续"时,它仍然无能为力。s12 会引入持久化(design.md 决策 4),但在此之前,s07-s11 会继续完善其他能力。

Table of Contents

Current section:目标

  • 1. 目标
  • 2. 架构:异步后台模型
  • 3. 第一层:重构两个单例
  • 4. 为什么必须先重构?
  • 5. ToolRegistry:object → class
  • 6. AgentLoop 构造函数注入
  • 7. 隐藏耦合点:ToolCategoryRule
  • 8. TodoStore:同样的演进
  • 9. 测试改造模式
  • 10. 第二层:子智能体数据模型
  • 11. SubagentStatus——状态机
  • 12. SubagentRecord——不可变快照
  • 13. 第三层:Subagent 与 SubagentFactory
  • 14. Subagent——持有独立 AgentLoop
  • 15. SubagentFactory——组装隔离的依赖图
  • 16. 为什么不提取 AgentLoopCore 抽象?
  • 17. 第四层:SubagentStore
  • 18. 线程安全设计
  • 19. runningCount 语义
  • 20. 第五层:Task 与 QueryTask 工具
  • 21. TaskTool——异步派发
  • 22. QueryTaskTool——同步查询
  • 23. 包依赖方向:为什么 TaskTool/QueryTaskTool 在 subagent/ 包?
  • 24. 第六层:ReplLoop 集成
  • 25. 结构化并发
  • 26. /clear 命令扩展
  • 27. 关键设计取舍汇总
  • 28. 文件清单
  • 29. 下一站
Back to top

Related Posts

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

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

August 21st, 2026

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

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

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

August 13th, 2026

作为收官之作,把 s01-s19 的二十个核心机制整合为一个全面智能体:统一的状态查询与运行周期、自治认领与工作区隔离的贯通,以及 /status 命令的全局可视化。全机制协同运转,标志着 Cat-Code 从零完整复刻 Claude Code 核心能力的收官。

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

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

August 13th, 2026

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