前言 @

上个月在《AI Agent 开发杂谈》里聊了 NekoCode 的架构思路,但那篇偏概述。这篇文章换个角度,从工程实现层面拆开几个核心子系统:它们解决什么问题,代码里怎么落地,又如何互相配合。

UI 设计 @

在聊子系统之前,先说说 NekoCode 长什么样。毕竟一个终端工具,交互体验决定了你用不用得下去。

GUI 界面包含 session 切换、明暗两种色调、上下文状态,以及 Skill、MCP、Plugin 管理页面。

TUI 界面的整体风格我称之为「深夜书房」——暗色背景上,teal 色(#4ec9b0)偶尔闪现,像暗处猫眼的反光。色彩体系刻意克制,只用了六种颜色:

  • 主文字 #a0a0a0:柔和灰,长时间看不累
  • Teal 主色 #4ec9b0:用于 Assistant 标签、spinner、处理卡片边框
  • User 暗金 #c9a96e:用户消息的左侧竖条,暖而不刺眼
  • #7a8ba0:推理过程的分隔线
  • #e06c75:错误和危险操作
  • Diff 绿 #98c379:代码增行

弱化文字用 #666666#808080,边框线用 #333333 隐入背景。整套配色没有高饱和色,在终端里看久了也不会视觉疲劳。

tui

下面是整个 UI 最花心思的部分。Agent 执行任务时,界面会实时展示当前状态:

  • Summary 行:猫颜文字 + 工具调用计数(“3 tools · 1 change”)+ 活跃的子 Agent 彩色圆点
  • Activity 区:最近 5 条非持久工具调用,每条显示工具名和参数摘要
  • Changes 区:持久工具块的结果展示,当前包括 edit、bash、write,其中 edit 会内联展示 diff
  • Output 区:模型流式输出的文本,固定 6 行高度,内容自动滚动
  • Thinking 区:模型的推理过程(CoT),固定 6 行高度
  • Header 行:spinner + 阶段名(“Thinking”)+ 当前 skill + token 计数(↑↓)+ 微压缩计数(🧹)

整个处理卡片用 teal 色 NormalBorder 包裹,和已完成的消息在视觉上明确区分。

这里简单解释下:猫爪图标代表主 Agent 的工具调用,不同颜色的子 Agent 图标代表不同子 Agent 的工具调用。Header 里出现几个子 Agent 图标,就表示当前同时启动了几个子 Agent。

当 Agent 要执行 modify/danger 级别的操作时,底部弹出确认栏。确认栏用 box-drawing 字符画边框,显示具体命令和路径,而不是只显示工具名。等级标签 [modify] / [danger] 使用黄色,[blocked] 使用红色;[safe] 命令自动放行,不弹确认框。yes/no 按钮带背景色:绿色底 [enter] yes,红色底 [esc] no

输入交互 @

输入框支持多行(最多 8 行),Alt+Enter 换行,Enter 发送。输入 / 时底部弹出命令补全面板,Tab/Shift+Tab 选择,Enter 填入。↑/↓ 翻阅历史消息。Ctrl+E 切换工具块的折叠/展开。处理中按 Enter 触发 BTW 中断——注入新消息打断当前 LLM 调用。

设计思路 @

UI 设计上主要遵循几个原则:

  1. 信息密度适中:终端空间有限,不能什么都展示。非持久工具收进 Activity,edit/bash/write 这类持久结果放到 Changes,Activity 限制 5 条,让用户聚焦在模型的文本输出上,细节按需展开。
  2. 状态可见:Agent 在干什么、调了什么工具、token 花了多少、有没有触发压缩——这些信息实时可见,不会让人焦虑「它是不是卡住了」。
  3. 操作低摩擦:safe 命令自动放行、斜杠命令有补全、BTW 随时打断——减少不必要的确认和等待。
  4. 视觉克制:不用高饱和色、不用闪烁动画(除了猫眼)、不用 emoji 堆砌。终端本身就适合安静、专注的氛围。

子系统全景 @

NekoCode 目前由 10 个子系统 组成,按职责分为三层:

核心层——Agent 运行的基础设施:

# 子系统 代码位置 一句话职责
1 Agent 循环 bot/agent/runtime/ ReAct 循环引擎,驱动 Reason → Execute → Feedback 全流程
2 LLM 抽象层 bot/llm/ 多协议适配(Anthropic / OpenAI),流式调用,指数退避重试
3 上下文管理 bot/contextmgr/ 分层上下文构建 + 五级自动压缩 + 全局 Memory + 会话快照与恢复
4 工具系统 bot/tools/ 13 个固定注册工具 + 条件/动态工具,Bash 四级安全分级,oldString/newString 内容锚定编辑

治理层——约束模型行为、保障安全:

# 子系统 代码位置 一句话职责
5 Hook 系统 bot/hooks/ 事件驱动治理,8 个注入点,12 个内置 Hook,5 种动作类型
6 Policy 系统 bot/policy/ Ledger 操作账本 + 探索效率追踪,为 Hook 提供决策数据
7 Prompt 构建 bot/prompt/ System Prompt 分层组装,动态注入项目规范、Skills、Hints

扩展层——能力外延与工程支撑:

# 子系统 代码位置 一句话职责
8 子 Agent bot/agent/subagent/ 独立上下文的任务委派,3 种类型,8 槽位并发管理
9 扩展生态 bot/extension/ Plugin / MCP / Skill 三大扩展机制的统一接入
10 代码索引 bot/index/ Tree-sitter 多语言解析 + FTS5 全文搜索,加速代码理解

下面逐一展开。


1. Agent 循环 @

Agent 循环是整个系统的骨架,入口是 Agent.Run()。核心是一个 for 循环,每轮调用 runTurn()

Prepare → Reason → [有工具调用?] → Execute → Feedback → PostTurn
                        ↓ 无
                     PostTurn → 结束

Prepare 阶段 @

每轮开始前做三件事:触发自动压缩(AutoCompactIfNeeded,按需执行 MicroCompact 或 Compact)、计算本轮工具配额(budget.ComputeQuota,基于剩余 token 动态分配)、执行 PreTurn Hook 注入 hints。

Reason 阶段 @

调用 LLM,传入当前上下文和工具定义。返回结果分三种情况:文本回复(ActionChat)→ 进入 PostTurn;工具调用列表(ActionExecuteTool)→ 进入 Execute;工具调用解析失败(GarbledToolCall)→ 标记后进入 PostTurn,由 Hook 处理。

Execute 阶段 @

executeAndFeedback 是执行的核心。先过 filterToolCalls——检查配额是否够用、执行 PreToolUse Hook(可能 block 某些调用)、注入 pre-tool hints。然后 Executor.ExecuteBatch 并行执行允许的工具(worker pool 上限 10),收集结果后执行 PostToolUse Hook 和 PostTool Hook。最后工具结果写回上下文,下一轮 Reason 会看到这些结果。

PostTurn 阶段 @

LLM 给出文本回复后,执行 PostTurn Hook。这里会检查回答质量、验证声明是否属实、是否有未完成任务等。如果 Hook 注入了 hint 或 block 了 final answer,循环继续;否则结束。

保护机制 @

  • 步数上限:硬上限 150 步(maxAgentSteps),防止死循环
  • 连续 hint 检测:连续 3 次 PostTurn hint 注入(模型一直说话但不调工具)→ 强制停止
  • 连续失败检测:连续 5 次 LLM 调用失败 → 强制停止
  • 兜底合成:循环结束但没有文本输出时,额外发一次 LLM 请求要求总结;主路径失败还有 emergency fallback(30s 超时 + 自动压缩 + 不重试)

BTW 中断 @

用户可以在 Agent 执行中按 Enter 插入新消息。实现上通过 steering channel(buffer 4)接收中断,replaceCtx() 取消当前 LLM 调用的 context。被中断的调用返回 context.Canceled,Reason 阶段检测到后标记 Interrupted=truerunTurn 把上下文截断到调用前状态(TruncateTo),清空 steering channel 中的消息注入上下文,重新开始 Reason。


2. LLM 抽象层 @

LLM 层负责屏蔽不同供应商的协议差异。核心接口是 types.LLM,两个实现:

  • Anthropic 协议bot/llm/anthropic/):原生 Messages API,支持 thinking/reasoning 内容分离
  • OpenAI 兼容协议bot/llm/openai/):Chat Completions API,DeepSeek、MiniMax 等国内模型通过此协议接入

流式处理 @

tools.CallLLM 封装了统一的流式调用逻辑。支持三个回调:TextCallback(文本增量)、ReasoningCallback(推理过程增量)、CheckDone(检查是否应提前终止)。流式解析时自动分离 reasoning content 和普通 content。

重试机制 @

withRetry 实现指数退避重试,处理网络抖动和临时性 API 错误。重试次数和间隔可配置。

Thinking 控制 @

子 Agent 的 thinking 模式强制关闭(节省 token),主 Agent 根据模型配置决定。跨协议统一控制——Anthropic 通过 thinking.budget_tokens,OpenAI 兼容协议通过 reasoning_effort 或 extra_body 参数。


3. 上下文管理 @

上下文管理是 Agent 系统最复杂的子系统之一。核心挑战:窗口有限、前缀缓存宝贵、压缩不能丢关键信息。

分层构建 @

Build() 按以下顺序组装上下文:

Layer 0:  System Prompt(最稳定,最大化前缀缓存命中)
Layer 0M: 项目记忆(~/.nekocode/memory.md)
Layer 0.5: Archive(压缩摘要)+ Skills + Hints
Layer 1:  对话消息(经过 filterValidMessages 过滤)
Layer 2:  Todo 列表

filterValidMessages 做两件事:移除没有对应 tool result 的 assistant tool_call 消息(孤儿调用);移除没有对应 tool_call 的 tool 消息(孤儿结果);空 content 的非 system 消息补 .(某些 API 不允许空 content)。

五级压缩 @

compact.Compactor 驱动,基于上下文窗口剩余量自动触发,默认阈值如下:

级别 剩余 token 动作 是否调 LLM
Normal > 44,800 无操作 -
Warning ≤ 44,800 仅告警 -
MicroCompact ≤ 35,200 清除旧的只读工具结果,保留最近 5 个
Compact ≤ 25,600 LLM 生成结构化摘要,压缩最旧消息
Blocking ≤ 6,400 拒绝新输入,强制压缩

MicroCompact 是轻量级操作——只清除标记为 compactable 的工具结果(read/grep/glob 等只读工具的旧输出),不触发 LLM 调用,不影响前缀缓存。Compact 调用独立 LLM 客户端生成摘要。

摘要合并 @

多次 Compact 后会产生多个 Archive。Summarize() 调用 MergeClient(独立 LLM 客户端)将新旧 Archive 合并为一个,避免 Archive 膨胀。

Project Memory @

项目记忆当前是全局文件 ~/.nekocode/memory.md,启动时加载并注入 Layer 0。文件按 5 个固定段落组织:Tech Stack、Active Goals、Completed Tasks、Key Architecture Map、User Preferences。代码里已经有 MergeFromCompaction,用于把压缩摘要里的关键事实合并回 memory,但它不是按 session 单独写入 ~/.nekocode/sessions/<id>/memory.md

快照与恢复 @

ManagerSnapshot 捕获完整状态——SystemPrompt、Skills、Archive、Memory、Hints、Messages、CompactBoundary、Budget、Tracker。支持会话存档和恢复,/sessions 命令依赖此机制。


4. 工具系统 @

注册机制 @

工具通过 Registry 统一管理,基于泛型 common.Registry[Tool]。每个工具实现 Tool 接口(Name()Description()Parameters()),注册后自动生成 LLM function calling 的 tool definitions。支持条件注册和动态注册(Skill 加载时动态添加工具)。

固定工具与条件工具 @

工具 功能 安全等级 执行模式
bash Shell 命令执行 Safe~Blocked(智能分级) Sequential
read 文件读取 + 二进制检测 Safe Parallel
write 文件创建/覆盖 Modify Sequential
edit oldString/newString 内容锚定替换 Modify Sequential
list 目录列表 Safe Parallel
tree 目录树可视化 Safe Parallel
glob 文件模式匹配 Safe Parallel
grep ripgrep 内容搜索 Safe Parallel
web_search Web 搜索 Safe Parallel
web_fetch 网页抓取 Safe Parallel
question 向用户提问 Safe Sequential
todo_write 任务列表更新 Safe Sequential
task 子 Agent 委派 Safe Parallel
image_gen AI 文生图,配置了图片模型后注册 Safe Sequential
project_info 代码索引查询,索引管理器可用时注册 Safe Parallel
skill Skill 加载工具,扩展层动态注册 Safe Sequential

固定注册在 bot/tools/catalog.RegisterAll 里的工具是 bash、read、write、list、tree、glob、edit、grep、web_search、web_fetch、question、todo_write、task。image_gen 只有在配置了图片生成模型时注册,project_info 在代码索引管理器可用时注册,skill 由扩展层动态注册;MCP 工具则来自外部服务器。

Bash 四级智能分级 @

不是简单的「允许/禁止」二分,而是按关键词智能分级:

  • Safe(自动放行):git loggit diffgit statusgo versiongo vetlscatpsdufile 等纯输出命令
  • Modify(弹框确认):默认级别
  • Danger(警告确认):curlwgetrmchmodkillrebootgit pushgit reset --hard
  • Blocked(直接拒绝):sudoevalsshcurl | bashddmkfs

Edit 内容锚定 @

使用 oldString/newString 模式而非行号替换。hasSufficientEditAnchor 检查锚定充分性:oldString ≥ 200 字符,或包含 ≥ 5 个非空行。锚定充分时,即使文件未被 read 过也能放行(ReadBeforeWriteHook 的例外)。编辑后自动跑 gofmt 检查 Go 文件语法。

工具结果预算 @

写入上下文前会经过 BudgetResult。当前实现只对 grep 结果做首尾保留:保留前 50 行和后 50 行,中间用截断标记替代,避免大范围搜索结果撑爆上下文窗口;其他工具结果暂不在这里做通用 50KB 截断。


5. Hook 系统 @

Hook 是 NekoCode 最核心的治理机制。设计思想:不修改 Agent 循环代码,通过事件注入约束模型行为

8 个注入点 @

UserSubmit → PreTurn → PreModelRequest → [Reason] → PreToolUse → [Execute]
                                                         ↓
                                                    PostToolUse → PostTool
                                                         ↓
                              [Reason 返回文本] → PostTurn → Stop

5 种动作类型 @

动作 效果
Hint 注入提示到下一轮上下文,引导模型行为
Stop 立即终止 Agent 循环
BlockTool 阻止某个工具调用执行
RequireTool 要求模型必须调用某类工具(否则 block final answer)
BlockFinal 阻止模型输出最终答案(要求修正后重试)

12 个内置 Hook @

Hook 注入点 职责
QuotaHook PreTurn 工具调用配额管理
ToolResultGuardrailHook PreModelRequest 工具结果过多时提醒检查未完成任务
ReadBeforeWriteHook PreToolUse 未读文件不准改
ReadOnlySpiralHook PostTool 连续只读不写 → 要求总结
VerificationHook PostTurn 有未完成任务但没调工具 → block
ExplorationExhaustedHook PreTurn 探索配额耗尽 → 要求行动
ExplorationGuardHook PreToolUse 探索耗尽后 block 探索类工具
ExploreCascadeHook PostTool 过多 researcher 子 Agent → 提醒收敛
ProgressStallHook PostTool 连续 8 轮无进展 → 强制要求 edit/write/bash
CompletionQualityHook PostTurn 回答质量检查
GarbledCircuitBreaker PostTurn 累计 5 次乱码 → Stop
FinalCheckHook PostTurn 最终答案诚实性校验(3 条规则)

FinalCheckHook 的三条规则 @

  1. missing_verification:修改了非文档文件但没有通过验证,且回答未声明「未验证」→ block
  2. unsupported_test_claim:回答声称测试通过,但 ledger 无验证记录 → block
  3. unreported_tool_error:存在工具错误,但回答声称完成且未提及失败 → block

Response Gate @

BlockFinalRequireTool 不是无限重试。responseGate 限制最多 2 次重试,超过后放行答案,防止「被 block → 重试 → 再被 block」的死循环。

状态共享 @

Hook 之间通过 storemap[string]int64)和 strValsmap[string]string)共享状态。Policy Manager 每轮开始时同步 Ledger 数据到 store,Hook 可通过 StatePatch 修改状态。


6. Policy 系统 @

Policy Manager 是 Hook 系统和 Agent 循环之间的桥梁,持有三个核心组件:

  • HookReg:Hook 注册表引用
  • Ledgerbot/policy/ledger/):操作账本,记录文件读取、修改、验证、工具错误、被 block 的工具调用
  • Explorationbot/policy/budget/):探索效率追踪器——评估探索类工具调用的收益递减

每轮同步 @

ResetTurnBetween 在每轮开始时将 Ledger 状态同步到 Hook store:

  • StoreLedgerModified:已修改文件数
  • StoreLedgerVerified:是否有通过验证
  • StoreLedgerErrors:工具错误数
  • StoreLedgerNonDocModified:是否有非文档类修改
  • StoreLedgerProgress:本轮是否有新进展(新读取/修改/验证)

Hook 基于这些状态做出决策。例如 ProgressStallHook 检查 StoreLedgerProgress,连续 8 轮为 0 则触发。

探索追踪 @

ExplorationTracker 评估探索类工具调用的效率。每次探索类调用后更新评分,评分归零且调用超过 10 次时,ExplorationExhaustedHook 触发。


7. Prompt 构建 @

System Prompt 采用分层组装策略,由 bot/prompt/ 负责构建:

  • 基础层:角色设定(黑猫形象、软萌风格)、核心准则(先理清思路、按需探索、信任权威数据等)
  • 工程规范层:不添加超出需求的功能、不设计假设性需求、修改成本大时直接重写
  • 输出规范层:工具调用时禁止解释性文字、ASCII 树必须套代码块、inline code 仅用于短路径
  • 工具准则层:优先专用工具、并行只读、串行修改、edit/write 前必须 Read
  • 动态注入层:Skills 列表、Hints(Hook 注入)、项目规范(NEKOCODE.md)、环境信息(OS/Arch/日期/工作目录)

动态注入的内容放在 System Prompt 末尾,保证前缀缓存最大化——基础层几乎不变,动态层变化频繁但放在最后。


8. 子 Agent @

子 Agent 通过 task 工具触发,三种类型:

类型 用途 可用工具
executor 执行编码任务 read/write/edit/bash/grep/glob/list
researcher 代码探索/调研 read/grep/glob/list/web_search/web_fetch
verify 验证修改 read/grep/glob/list/bash

Slot 管理 @

subSlotManager 控制并发——最多 8 个 slot,每个有独立颜色索引(0-7,TUI 中区分)。Acquire 在无可用 slot 时阻塞等待(sync.Cond),Release 后唤醒等待者。

上下文隔离 @

子 Agent 使用 NewSub 创建轻量级 ContextManager——独立 SystemPrompt 和 Messages,不继承主 Agent 对话历史。这种「纯净委派」避免上下文污染,也更省 token(前缀缓存更友好)。

回调桥接 @

子 Agent 的工具调用通过 TaskCallbackFn 回调到主 Agent 前端——sub_tool_startsub_execute_toolsub_agent_startsub_agent_end。回调携带 subID:colorIdx 标签,前端据此渲染不同颜色的子 Agent 卡片。

设计原则 @

遵循「Self-Serve First」——主 Agent 优先自己完成任务,只有满足三个条件才委派:跨 5 个以上文件、需要独立上下文、单回合确实太复杂。


9. 扩展生态 @

三大扩展机制统一接入:

  • Pluginbot/extension/plugin/):兼容 Claude Code 插件格式,支持 GitHub 和本地路径安装。当前主要接入插件声明的 Hooks、MCP 配置、commands 与 agents 等资源
  • MCPbot/extension/mcp/):JSON-RPC 2.0 协议,接入外部工具服务器。当前实现以 stdio 进程客户端为主,并通过动态工具适配器暴露 MCP tools
  • Skillbot/extension/skill/):YAML 定义技能包,/<skill> 一键加载。Skill 可注入 System Prompt 片段,并通过动态 skill 工具按需加载

10. 代码索引 @

基于 Tree-sitter 的多语言代码解析 + SQLite FTS5 全文搜索。

  • 符号索引:解析函数、类型、变量定义,支持 symbol: 查询
  • 依赖分析:解析 import/include,支持 deps: 查询
  • 全文搜索:FTS5 索引所有源码,支持 search: 查询
  • 文件定位:文件名模糊匹配,支持 file: 查询

通过 project_info 工具暴露给 Agent,减少不必要的 read/grep 调用,加速代码理解。


结尾 @

NekoCode 的 10 个子系统围绕几个核心原则设计:分层解耦,比如上下文分层、Hook 独立于循环;纵深防御,通过多层 Hook 交叉验证;预算意识,把前缀缓存、配额管理和探索收益递减检测放进主循环;失败可恢复,依靠快照/恢复、兜底合成和 response gate 限流。

目前项目还在实验阶段,Hook 规则和压缩策略还有很多调优空间。如果你对某个子系统的实现细节感兴趣,docs/ARCHITECTURE.md 有更完整的代码级文档。欢迎到 GitHub 提 issue 或 PR。