


























上个月在《AI Agent 开发杂谈》里聊了 NekoCode 的架构思路,但那篇偏概述。这篇文章换个角度,从工程实现层面拆开几个核心子系统:它们解决什么问题,代码里怎么落地,又如何互相配合。
在聊子系统之前,先说说 NekoCode 长什么样。毕竟一个终端工具,交互体验决定了你用不用得下去。
GUI 界面包含 session 切换、明暗两种色调、上下文状态,以及 Skill、MCP、Plugin 管理页面。
TUI 界面的整体风格我称之为「深夜书房」——暗色背景上,teal 色(#4ec9b0)偶尔闪现,像暗处猫眼的反光。色彩体系刻意克制,只用了六种颜色:
#a0a0a0:柔和灰,长时间看不累#4ec9b0:用于 Assistant 标签、spinner、处理卡片边框#c9a96e:用户消息的左侧竖条,暖而不刺眼#7a8ba0:推理过程的分隔线#e06c75:错误和危险操作#98c379:代码增行弱化文字用 #666666 和 #808080,边框线用 #333333 隐入背景。整套配色没有高饱和色,在终端里看久了也不会视觉疲劳。
下面是整个 UI 最花心思的部分。Agent 执行任务时,界面会实时展示当前状态:
整个处理卡片用 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 设计上主要遵循几个原则:
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 全文搜索,加速代码理解 |
下面逐一展开。
Agent 循环是整个系统的骨架,入口是 Agent.Run()。核心是一个 for 循环,每轮调用 runTurn():
Prepare → Reason → [有工具调用?] → Execute → Feedback → PostTurn
↓ 无
PostTurn → 结束
每轮开始前做三件事:触发自动压缩(AutoCompactIfNeeded,按需执行 MicroCompact 或 Compact)、计算本轮工具配额(budget.ComputeQuota,基于剩余 token 动态分配)、执行 PreTurn Hook 注入 hints。
调用 LLM,传入当前上下文和工具定义。返回结果分三种情况:文本回复(ActionChat)→ 进入 PostTurn;工具调用列表(ActionExecuteTool)→ 进入 Execute;工具调用解析失败(GarbledToolCall)→ 标记后进入 PostTurn,由 Hook 处理。
executeAndFeedback 是执行的核心。先过 filterToolCalls——检查配额是否够用、执行 PreToolUse Hook(可能 block 某些调用)、注入 pre-tool hints。然后 Executor.ExecuteBatch 并行执行允许的工具(worker pool 上限 10),收集结果后执行 PostToolUse Hook 和 PostTool Hook。最后工具结果写回上下文,下一轮 Reason 会看到这些结果。
LLM 给出文本回复后,执行 PostTurn Hook。这里会检查回答质量、验证声明是否属实、是否有未完成任务等。如果 Hook 注入了 hint 或 block 了 final answer,循环继续;否则结束。
maxAgentSteps),防止死循环用户可以在 Agent 执行中按 Enter 插入新消息。实现上通过 steering channel(buffer 4)接收中断,replaceCtx() 取消当前 LLM 调用的 context。被中断的调用返回 context.Canceled,Reason 阶段检测到后标记 Interrupted=true,runTurn 把上下文截断到调用前状态(TruncateTo),清空 steering channel 中的消息注入上下文,重新开始 Reason。
LLM 层负责屏蔽不同供应商的协议差异。核心接口是 types.LLM,两个实现:
bot/llm/anthropic/):原生 Messages API,支持 thinking/reasoning 内容分离bot/llm/openai/):Chat Completions API,DeepSeek、MiniMax 等国内模型通过此协议接入tools.CallLLM 封装了统一的流式调用逻辑。支持三个回调:TextCallback(文本增量)、ReasoningCallback(推理过程增量)、CheckDone(检查是否应提前终止)。流式解析时自动分离 reasoning content 和普通 content。
withRetry 实现指数退避重试,处理网络抖动和临时性 API 错误。重试次数和间隔可配置。
子 Agent 的 thinking 模式强制关闭(节省 token),主 Agent 根据模型配置决定。跨协议统一控制——Anthropic 通过 thinking.budget_tokens,OpenAI 兼容协议通过 reasoning_effort 或 extra_body 参数。
上下文管理是 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 膨胀。
项目记忆当前是全局文件 ~/.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 命令依赖此机制。
工具通过 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 工具则来自外部服务器。
不是简单的「允许/禁止」二分,而是按关键词智能分级:
git log、git diff、git status、go version、go vet、ls、cat、ps、du、file 等纯输出命令curl、wget、rm、chmod、kill、reboot、git push、git reset --hardsudo、eval、ssh、curl | bash、dd、mkfs使用 oldString/newString 模式而非行号替换。hasSufficientEditAnchor 检查锚定充分性:oldString ≥ 200 字符,或包含 ≥ 5 个非空行。锚定充分时,即使文件未被 read 过也能放行(ReadBeforeWriteHook 的例外)。编辑后自动跑 gofmt 检查 Go 文件语法。
写入上下文前会经过 BudgetResult。当前实现只对 grep 结果做首尾保留:保留前 50 行和后 50 行,中间用截断标记替代,避免大范围搜索结果撑爆上下文窗口;其他工具结果暂不在这里做通用 50KB 截断。
Hook 是 NekoCode 最核心的治理机制。设计思想:不修改 Agent 循环代码,通过事件注入约束模型行为。
UserSubmit → PreTurn → PreModelRequest → [Reason] → PreToolUse → [Execute]
↓
PostToolUse → PostTool
↓
[Reason 返回文本] → PostTurn → Stop
| 动作 | 效果 |
|---|---|
Hint |
注入提示到下一轮上下文,引导模型行为 |
Stop |
立即终止 Agent 循环 |
BlockTool |
阻止某个工具调用执行 |
RequireTool |
要求模型必须调用某类工具(否则 block final answer) |
BlockFinal |
阻止模型输出最终答案(要求修正后重试) |
| 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 条规则) |
BlockFinal 和 RequireTool 不是无限重试。responseGate 限制最多 2 次重试,超过后放行答案,防止「被 block → 重试 → 再被 block」的死循环。
Hook 之间通过 store(map[string]int64)和 strVals(map[string]string)共享状态。Policy Manager 每轮开始时同步 Ledger 数据到 store,Hook 可通过 StatePatch 修改状态。
Policy Manager 是 Hook 系统和 Agent 循环之间的桥梁,持有三个核心组件:
bot/policy/ledger/):操作账本,记录文件读取、修改、验证、工具错误、被 block 的工具调用bot/policy/budget/):探索效率追踪器——评估探索类工具调用的收益递减ResetTurnBetween 在每轮开始时将 Ledger 状态同步到 Hook store:
StoreLedgerModified:已修改文件数StoreLedgerVerified:是否有通过验证StoreLedgerErrors:工具错误数StoreLedgerNonDocModified:是否有非文档类修改StoreLedgerProgress:本轮是否有新进展(新读取/修改/验证)Hook 基于这些状态做出决策。例如 ProgressStallHook 检查 StoreLedgerProgress,连续 8 轮为 0 则触发。
ExplorationTracker 评估探索类工具调用的效率。每次探索类调用后更新评分,评分归零且调用超过 10 次时,ExplorationExhaustedHook 触发。
System Prompt 采用分层组装策略,由 bot/prompt/ 负责构建:
动态注入的内容放在 System Prompt 末尾,保证前缀缓存最大化——基础层几乎不变,动态层变化频繁但放在最后。
子 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 |
subSlotManager 控制并发——最多 8 个 slot,每个有独立颜色索引(0-7,TUI 中区分)。Acquire 在无可用 slot 时阻塞等待(sync.Cond),Release 后唤醒等待者。
子 Agent 使用 NewSub 创建轻量级 ContextManager——独立 SystemPrompt 和 Messages,不继承主 Agent 对话历史。这种「纯净委派」避免上下文污染,也更省 token(前缀缓存更友好)。
子 Agent 的工具调用通过 TaskCallbackFn 回调到主 Agent 前端——sub_tool_start、sub_execute_tool、sub_agent_start、sub_agent_end。回调携带 subID:colorIdx 标签,前端据此渲染不同颜色的子 Agent 卡片。
遵循「Self-Serve First」——主 Agent 优先自己完成任务,只有满足三个条件才委派:跨 5 个以上文件、需要独立上下文、单回合确实太复杂。
三大扩展机制统一接入:
bot/extension/plugin/):兼容 Claude Code 插件格式,支持 GitHub 和本地路径安装。当前主要接入插件声明的 Hooks、MCP 配置、commands 与 agents 等资源bot/extension/mcp/):JSON-RPC 2.0 协议,接入外部工具服务器。当前实现以 stdio 进程客户端为主,并通过动态工具适配器暴露 MCP toolsbot/extension/skill/):YAML 定义技能包,/<skill> 一键加载。Skill 可注入 System Prompt 片段,并通过动态 skill 工具按需加载基于 Tree-sitter 的多语言代码解析 + SQLite FTS5 全文搜索。
symbol: 查询deps: 查询search: 查询file: 查询通过 project_info 工具暴露给 Agent,减少不必要的 read/grep 调用,加速代码理解。
NekoCode 的 10 个子系统围绕几个核心原则设计:分层解耦,比如上下文分层、Hook 独立于循环;纵深防御,通过多层 Hook 交叉验证;预算意识,把前缀缓存、配额管理和探索收益递减检测放进主循环;失败可恢复,依靠快照/恢复、兜底合成和 response gate 限流。
目前项目还在实验阶段,Hook 规则和压缩策略还有很多调优空间。如果你对某个子系统的实现细节感兴趣,docs/ARCHITECTURE.md 有更完整的代码级文档。欢迎到
GitHub 提 issue 或 PR。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。