起点:开发者为什么做这个东西
痛点与洞察:成本焦虑撞上现成的省钱机制
1.1 先回答「为什么」
读 Reasonix 的源码之前,先读它的 README。中文 README 开篇没有讲“我们的 Agent 多聪明”,而是先把产品位钉在成本上(README.zh-CN.md:42-43):
面向终端的 DeepSeek 原生 AI coding agent。
由配置与插件驱动的极薄 harness——单一静态 Go 二进制,围绕 DeepSeek 的前缀缓存调优,长会话也能把 token 成本压低。
这不是营销话术,而是整套架构的出发点:
- 痛点:长会话里 token 成本随轮次线性增长;DeepSeek 的定价让“一直聊下去”变贵。
- 现成机制:DeepSeek 自动做前缀缓存——只要请求前缀字节稳定,命中的部分就能打折。
- 洞察:省钱的钥匙不是“少调用模型”,而是“让前缀一直命中”。于是 cache 从「优化项」升格为「架构约束」。
flowchart LR
P["痛点:长会话 token 成本高"] --> I["洞察:DeepSeek 自动前缀缓存<br/>前缀稳定 = 命中 = 省钱"]
I --> C["约束:系统前缀必须字节稳定"]
C --> D["推论:易变内容外移 turn tail<br/>工具 schema 稳定 · 压缩明码标价 · miss 可诊断"]
1.2 三个可验证的工程主张
| 主张 | 在代码里长什么样 |
|---|---|
| DeepSeek 原生 | Provider 层保留 reasoning_content 往返;缺推理内容时静默重试(agent.go:1388-1394 的 observeMissingToolCallReasoning) |
| Cache-first | 系统前缀(base + tools + memory)跨 turn 字节稳定;易变内容走 control.Compose 的 turn tail(REASONIX.md:14-16,control/input.go:181-183) |
| 薄 harness + 单二进制 | docs/SPEC.md 契约:核心只认接口;CGO_ENABLED=0;能力靠 config / plugin / init() 自注册 |
1.3 这条约束被写成了「宪法」,而不是注释
仓库自己的 standing instructions(REASONIX.md:14-16):
- Cache-first: the system-prompt prefix (base prompt + tools + memory) must stay
byte-stable across turns so DeepSeek's automatic prefix cache stays warm. Never
mutate it mid-session — ride the turn tail instead (see `control.Compose`).
甚至 PR 模板强制要求 Cache-impact / Cache-guard 元数据(REASONIX.md:64-71)。
本系列里只有 Hermes 的「前缀缓存神圣」能对上劲;Reasonix 的差别是把它焊进了 Go 运行时诊断 + CI 脚本,让“不许打破缓存”成为可执行的协作纪律。
产品位与动机证据:README / REASONIX / SPEC 里的原话
2.1 动机证据表
| 证据 | 出处 | 它证明了什么 |
|---|---|---|
| 「围绕 DeepSeek 的前缀缓存调优,长会话也能把 token 成本压低」 | README.zh-CN.md:42-43 |
成本是产品出发点 |
| 「must stay byte-stable… Never mutate it mid-session」 | REASONIX.md:14-16 |
约束用宪法口吻写 |
| 「switching models inside one shared conversation would break the prefix and tank cache hits, so we don't」 | docs/SPEC.md:238-240 |
连双模型都被约束反推成独立 session |
| Compaction 是「cache-reset point」,cache hit rate 是「key observability signal」 | docs/SPEC.md:328-329 |
压缩是明码标价的缓存重置 |
PR 强制 Cache-impact / Cache-guard |
REASONIX.md:64-71 |
约束焊进协作流程 |
| 「Memory added mid-session rides the turn (never the cached system prefix)」 | internal/control/input.go:181-183 |
每个实现点都在解释为什么 |
| Economy 档面向「成本敏感任务」 | docs/COLLABORATION_MODES.zh-CN.md:86,136 |
产品档位按成本切 |
2.2 产品经理视角:它在卖什么体验
| 卖点 | 工程落点 |
|---|---|
| 长会话更省 token | 稳定前缀 → DeepSeek 自动 prefix cache;PrefixShape / CompareShape 解释 cache miss(agent/cache_shape.go) |
| 装完就能用 | 单静态二进制;npm 包 reasonix;reasonix.example.toml |
| 多前端同一套行为 | control.Controller 背后挂 TUI / HTTP-SSE / Wails desktop / ACP(REASONIX.md:11-13) |
| 可验证交付 | Delivery Profile:complete_step 证据签收 + readiness 门禁(docs/GOAL_ENFORCEMENT.zh-CN.md) |
| 协作模式可切换 | Economy / Balanced / Delivery;可选 planner_model 双模型(docs/COLLABORATION_MODES.zh-CN.md) |
🧠 一句话:别家在优化「工具调用多聪明」;Reasonix 在优化「同一次会话里,前缀到底能不能一直命中缓存」——并把这件事当成不能违约的产品契约。
仓库地图与技术栈:数字形状与依赖方向
3.1 数字化的项目形状(基线 627b051)
| 指标 | 量级 |
|---|---|
| Go 源文件 | 1686 个 .go |
internal/ 非测试 |
216,079 行 |
| 全仓含测试 | 553,005 行 |
| 巨文件心脏 | control/controller.go 6,560 · cli/chat_tui.go 5,154 · agent/agent.go 4,362 |
| 内置工具契约 | docs/TOOL_CONTRACT.md(compile-time builtins + 全量 boot 表面,由测试守护) |
docs/SPEC.md 给出的布局契约:
cmd/reasonix/main.go # 入口;blank-import providers + builtin tools
internal/
cli/ # 子命令、装配、退出码
control/ # 传输无关 Controller(所有前端的背后)
agent/ # Session + harness 循环 + compact + cache shape
provider/ # Provider 接口 + openai/anthropic/responses
tool/ + tool/builtin/ # Tool 接口 + init() 自注册
permission/ # allow / ask / deny + bash 分解
sandbox/ # OS 沙箱封装
memory/ · instruction/ # 背景事实 vs standing instructions
plugin/ · mcp* / capability # MCP / 能力代理
checkpoint/ · evidence/ # 变更回滚与交付证据
acp/ · bot/ · desktop* # 编辑器协议、机器人、桌面
依赖方向(docs/SPEC.md:54-56):cli → {agent, plugin, config} → {tool, provider};父包不 import 自注册子包。
3.2 技术栈
| 层 | 技术 | 位置 |
|---|---|---|
| 语言 / 分发 | Go,CGO_ENABLED=0 单静态二进制 |
Makefile、cmd/reasonix |
| 配置 | TOML(BurntSushi/toml) |
internal/config、reasonix.example.toml |
| TUI | Bubble Tea 系全屏聊天 | internal/cli/chat_tui.go |
| 桌面 | Wails(Go 后端 + 前端) | desktop/ |
| 协议 | ACP、HTTP/SSE serve、MCP stdio JSON-RPC |
internal/acp、internal/control、internal/plugin |
| Provider | OpenAI 兼容 / Anthropic / Responses | internal/provider/* blank-import |
入口极薄(cmd/reasonix/main.go):
// Blank imports wire compile-time built-ins into their registries.
_ "reasonix/internal/provider/anthropic"
_ "reasonix/internal/provider/openai"
_ "reasonix/internal/provider/responses"
_ "reasonix/internal/tool/builtin"
3.3 与 Claude Code / Hermes / Codex 的关系
| 维度 | Claude Code | Hermes Agent | OpenAI Codex | Reasonix |
|---|---|---|---|---|
| 语言形态 | TypeScript(闭源) | Python 大脑 + TS 脸 | Rust workspace | Go 单二进制 |
| 模型策略 | 绑 Anthropic | 模型无关 + 学习闭环 | 绑 OpenAI 栈 | DeepSeek 优先,OpenAI 兼容可插 |
| 上下文哲学 | 强提示词工程 | 前缀缓存神圣 + 记忆写盘 | 两层 turn + 压缩 | Cache-first 写进宪法 + 运行时 shape 诊断 |
| 前端 | 终端为主 | CLI/TUI/桌面/20+ IM | CLI + app-server | Controller 统一:TUI / serve / Wails / ACP |
| 权限 | 弹窗 + 规则 | 环境隔离多后端 | AskForApproval × OS 沙箱 | Policy 规则 + bash 分解 + sandbox + workspace confine |
| 完成判定 | 自然结束 + 工具 | 技能/记忆闭环 | 三方停机协议 | 自然结束 + maxSteps/todo stall;Delivery 证据签收 |
若只记一句:Reasonix = Hermes 的 cache 哲学 × Codex 的工程硬度 × Claude Code 的终端 coding 品类,焊在 DeepSeek 的自动前缀缓存上。
思维导图:一条约束如何推出十项设计
开发者思维导图:从「前缀必须稳定」到十项设计
这一章是整篇的“地图”:先看开发者脑子里那根主轴,再进代码。下面十项都能从「前缀必须字节稳定」推出;每一项也都有明确的代价。
| # | 推论 | 选择 | 如果不这么做会怎样 |
|---|---|---|---|
| 1 | 系统前缀必须稳定 | 易变上下文进 turn tail,不进 system(control.Compose) |
每轮插话/记忆更新都会打冷整段前缀 |
| 2 | 工具 schema 也是前缀 | schema 排序后再哈希(normalizeToolSchemas) |
工具注册顺序抖动造成“假 miss” |
| 3 | 插话不可避免 | Steer 明确付一次 cache 税(run_loop.go:276-283) |
假装零成本插话,缓存统计失真 |
| 4 | 上下文终会增长 | 压缩分 soft/snip/summary,压缩 = cache-reset point | 无节制 summary 频繁重置缓存 |
| 5 | MCP 库存会变 | use_capability 固定代理隔离动态 schema |
装一个 MCP 工具就冷一次缓存 |
| 6 | bash 语义必须精确 | 运行时只读降级 BashCommandIsReadOnly |
schema 稳定但语义模糊,误伤并行/证据 |
| 7 | DeepSeek 协议有怪癖 | Missing reasoning 静默重试一次 | thinking 模式下工具轮直接失败 |
| 8 | 跑满预算不该算崩溃 | maxSteps → Pause 而非 Fatal | 长任务在预算边界丢会话资产 |
| 9 | 多前端要一致 | 行为进 control.Controller 不进脸 |
每个前端各修各的,行为漂移 |
| 10 | 约束要可执行 | SPEC / TOOL_CONTRACT / 测试同源 + PR 元数据 | 宪法只是 wiki 摆设 |
Cache-first 宪法:稳定前缀 vs turn tail
5.1 两条轨道
| 轨道 | 内容 | 规则 |
|---|---|---|
| Cache-stable prefix | base system + tool schemas + standing memory/instructions | 会话内禁止字节级漂移 |
| Turn tail(Compose) | 用户正文、goal 块、plan marker、语言偏好、<memory-update>、background-jobs、hook context、retrieval recall |
每轮可变;绝不能回写进 prefix |
Controller.Compose(control/input.go:137-212)把这件事写进注释(input.go:181-183):
Memory added mid-session rides the turn (never the cached system prefix),
so it takes effect now without invalidating the prompt cache. It folds into
the system prefix on the next session, where it costs nothing per turn.
装配顺序(概念上):
flowchart TB
IN["用户 text"] --> G{"有 running goal?"}
G -->|是| GB["activeGoalBlock + autoResearch"]
G -->|否| P
GB --> P{"planMode?"}
P -->|是| PM["PlanModeMarker"]
P -->|否| L
PM --> L["response/reasoning language 包装"]
L --> M["drainPending → memory-update"]
M --> J["background-jobs note"]
J --> H["hook context"]
H --> R["memory.recall Block(真实 user turn)"]
R --> OUT["交给 Agent.Run"]
这与 Open Design「按缓存频率分带装配 system prompt」、Hermes「记忆写盘立即生效、下个会话才进提示词」是同一哲学族。Reasonix 的独特处是:把 miss 诊断成结构化事件,而不是只靠文档告诫。
5.2 PrefixShape:为什么 miss?
// PrefixShape hashes the portions of the request prefix that influence
// provider-side prompt-cache reuse. Comparing snapshots across turns
// lets us explain *why* a cache miss happened.
type PrefixShape struct {
SystemHash string
ToolsHash string
PrefixHash string
LogRewriteVersion int
ToolSchemaTokens int
}
CaptureShape 会对 tool schema 排序后再哈希(normalizeToolSchemas),避免“顺序抖动”假 miss。CompareShape 给出 system / tools / log_rewrite 原因列表,并带上 usage 里的 cache hit/miss tokens。
发往 provider 前还会剥掉会打冷缓存的 UI 元数据:LocalOnly 显示记录不进 ModelMessages(provider.go 的 LocalOnly 标记),墙钟与取消半截流都不会污染前缀。
5.3 与 MCP 动态工具的张力
动态 mcp__* 工具一旦进主 Registry,就会改 tools 哈希 → 冷缓存。Reasonix 的解法(docs/TOOL_CONTRACT.md:45-96):
- Delivery / Planner / 多数 sub-agent:暴露固定名代理工具
use_capability(list / inspect / call / decline); - 按需连 MCP 不改变该代理的 provider-visible schema;
- Balanced 的 Executor 刻意保留直接
mcp__*(接受可能的前缀变化)——这是有意识的产品取舍,不是疏漏。
🧠 承重墙:Cache-first 不是「尽量少改 prompt」,而是「哪些变化允许付 cache miss 的税」被显式分类:steer 付税、memory-update 不碰 prefix、MCP 库存变化用代理隔离。
双模型为何必须分 session
6.1 动机
想要“先规划后执行”的成本/质量平衡,但模型切换会破坏前缀。
6.2 约束与选择
docs/SPEC.md:238-240 把话挑明:
switching models inside one shared conversation would break the prefix and tank cache hits, so we don't.
配置了 planner_model 时,Planner 与 Executor 各有独立 session(agent/coordinator.go):两套前缀各自 cache-stable,会话永不混写。这与 UI 上的 Plan Mode(turn-tail marker)不是同一层。
6.3 代价
- 双份前缀、双份工具面,首轮成本更高;
- 规划期发现的能力必须在 handoff 后仍可直接 call(
use_capability的固定代理正好承担这件事); - 路由更复杂:
executor_only|plan_and_execute|plan_for_approval|plan_only(planner_route.go)。
实现:从约束到代码
主循环:Agent.Run → runToolLoop
7.1 生命周期入口
Agent.Run 自己说得很清楚(注释始于 agent.go:1310,函数体 1319-1378):
- 解析本轮
maxSteps(可被 context 覆盖); - 开 workspace lease / steer 队列 / background evidence 提交 defer;
interceptAgentStart(extension 可 abort);beginRunTurn初始化状态;return a.runToolLoop(ctx, state)。
没有神秘的「框架基类」——就是显式状态机,策略拆在 beginRunTurn / handleFinalResponse / handleToolRound。
7.2 runToolLoop:一轮里发生什么
核心循环在 run_loop.go:274-355:
sequenceDiagram
participant U as User / Controller
participant A as Agent.runToolLoop
participant P as Provider.Stream
participant T as executeOne(s)
U->>A: Run(input)
loop step = 0..maxSteps
A->>A: consumeSteer?(写入 session,可接受一次 cache miss)
A->>A: CaptureShape(system+tools)
A->>P: streamWithMissingReasoningRecovery
P-->>A: text + reasoning + tool_calls + usage
A->>A: CompareShape → cache diagnostics
alt 无 tool_calls
A->>A: handleFinalResponse(含 maybeCompact)
else 有 tool_calls
A->>T: handleToolRound → 并行只读 / 串行写入
T-->>A: tool results 写入 session
end
end
A-->>U: maxStepsPause 或 error 或 nil
几个味道很重的细节:
| 细节 | 含义 | 位置 |
|---|---|---|
| Steer | 中途插话进队列;消费时写入 user 消息并带引导前缀;注释承认「一次 cache miss 不可避免」 | run_loop.go:276-283 |
| Missing reasoning recovery | DeepSeek thinking 模式下 tool call 缺 reasoning_content → 同请求静默重放至多一次 |
run_loop.go:357-364,agent.go:1388-1394 |
| Stream recovery | 中断流最多 maxStreamRecoveries=3,且 不消耗 maxSteps(step--) |
run_loop.go:300-309 |
| Cache diagnostics | 每轮 CompareShape 对比 system/tools/log_rewrite |
run_loop.go:285-294 |
| 暂停而非崩溃 | maxStepsPause / todoStallPause:工作已在 session,用户再发一条即可续 |
agent.go:1469-1494 |
| Delivery readiness 失败 | 终答前宿主校验 todo/criteria/verify/signoff → FinalReadinessError,开下一轮 |
handleFinalResponse |
| 空终答 / thinking-only | 最多 maxEmptyFinalBlocks 次回催 |
run_loop.go,agent.go:42 |
| Recovery grace | recovery episode 耗尽后 summarize-only 一圈,再 RecoveryPauseError |
recovery 路径 |
7.3 装配入口:boot.Build → 同一 Runner
| 模式 | 触发 | Turn API |
|---|---|---|
| TUI | 裸 reasonix / chat |
异步 Controller.SendWithRaw |
| Headless | reasonix run / -p |
同步跑一轮;Ask 对 writer fail closed,需 --auto/-y |
| ACP | reasonix acp |
阻塞 Controller.RunTurn(session/prompt) |
| serve / desktop / bot | 各自入口 | SSE / Wails / IM → 同一 Controller |
boot.Build(internal/boot)一次性冻结 system prefix(output style、环境探针快照、memory、skill 索引),再把可选 planner_model 包成 Coordinator。
7.4 单工具执行管线
executeOne(execute_one.go:65-101)是固定五段:
parseToolCall— Resolve、歧义 MCP 名、重复成功/失败 loop guard、stale-anchor 编辑拦截;bash 可按参数降级为只读(permission.BashCommandIsReadOnly);interceptToolBefore— extension 可改写调用;resolveToolPolicy— 权限门禁;prepareToolExecution— 预览、mutation 记账、parent write 锁;finishToolExecution— 真正执行 + 回执。
只读工具可并行,写入串行——与 Claude Code / OpenAI4S 的「只读波次」同族,但落地在 Go 的 mutation observer / evidence ledger 上。
Compose:把易变内容赶进 turn tail
8.1 动机
系统前缀要字节稳定,但 goal、plan、语言偏好、记忆更新、检索结果每轮都可能变。
8.2 约束
凡是不稳定、不必要时不出现的内容,都不能进 cache-stable prefix。
8.3 被否方案
- “把所有动态内容都拼进 system prompt”:省事但每轮冷缓存,直接违背宪法;
- “动态内容完全不进上下文”:goal/plan/记忆更新无法即时生效。
8.4 选择
control.Compose(control/input.go:137-212)在真实 user turn 前按固定顺序装配:activeGoalBlock → PlanModeMarker → 语言包装 → <memory-update> → background-jobs → hook context → memory.recall Block。
8.5 代价
- 每轮多一次装配与剥离(
StripComposePrefixes); - 若前端/扩展绕过
Compose直接发消息,缓存宪法会被破坏——所以所有前端都走同一 Controller。
压缩三阶梯:soft → snip → summary(明码标价的缓存重置)
compact.go:20-37 把策略钉死:
| 阶段 | 默认阈值(占 context window) | 做什么 |
|---|---|---|
| Soft | 0.5 | 报告上下文在涨,仍保 cache-stable prefix |
| Tool-result snip | 0.6 | 廉价改写陈旧 tool result |
| Summary compact | 0.8(force 0.9) | 摘要折叠;保留固定 token 的 recent tail(默认 16384) |
设计要点:
- 触发用比例,保留用绝对 token 预算——大窗口不会过度压缩,小窗口也不会卡在阈值附近反复 compact;
- Summary 用结构化标题(Standing facts / Goal / Decisions / Files / Commands / Errors / Pending),包在
<compaction-summary>里; - Ablation 开关可关掉 cache 友好策略做对照实验(
internal/ablation); - Compact 会 bump
LogRewriteVersion→CompareShape会标log_rewrite——诚实承认这是 cache 重置点。
与 Raven「无损归档」、Claude Code「摘要丢弃」相比:Reasonix 明确站在 「先廉价 snip,再付一次 summary 的 cache 税」 这一边。
工具 ABI:builtin + MCP + use_capability 固定代理
10.1 Compile-time builtins(契约表)
docs/TOOL_CONTRACT.md 由测试守护(TestBuiltinToolContractDocumentation),与运行时 registry 同源。核心面:
| 类别 | 工具 |
|---|---|
| 读 | read_file grep glob ls code_index web_fetch |
| 写 | write_file edit_file multi_edit move_file delete_range delete_symbol notebook_edit |
| Shell | bash + bash_output / wait / kill_shell |
| 任务/交付 | todo_write complete_step update_goal |
实现侧全部 init() { tool.RegisterBuiltin(...) }(如 builtin/readfile.go:27)。
10.2 全量 boot 表面 vs Economy
默认 full-token boot 还挂 session / memory / skill / subagent / LSP / install / slash 等:ask、task/fleet/parallel_tasks、memory/remember/forget、run_skill、LSP 四件套、explore/research/review/security_review 等(docs/TOOL_CONTRACT.md)。
Delivery 另加:
use_capability:稳定 MCP 代理;review_report:中高风险变更的结构化 review;- Host 侧 evidence:verification / diff / files / manual,无证据的
complete_step直接拒绝。
Economy 刻意只暴露瘦面(约 ask/bash/read_file/write_file/edit_file/后台三件套 + connect_tool_source),其余按需挂载——用「一次看不全工具」换 token 与前缀稳定。
10.3 Tool 接口味道
工具带 ReadOnly、预览(Previewer)、图像、PlanMode 分类等能力位——权限与并行调度读的是这些位,而不是靠模型「自觉」。Checkpoint 只跟踪带 Previewer 的编辑工具(见第 12 章)。
权限、沙箱、工作区围栏:应用层规则 × OS 围栏
11.1 Permission Policy + Ask / Auto / Yolo
permission.Policy(permission.go):
- 规则形态对齐 Claude Code:
Tool/Tool(glob)/ legacyTool=literal(ParseRule); Decide(toolName, readOnly, args);bash 走分段分解(DecomposeBashCommand);BashCommandIsReadOnly:把「schema 上可写的 bash」在具体 argv 上降成只读(影响并行、mutation、evidence);BashSubjectRequiresExplicitApproval:间接执行、危险 git 等强制显式批准。
产品审批模式(docs/TOOL_APPROVAL_MODES.md)与 collaboration mode(normal/plan/goal)正交:
| 模式 | 行为要点 |
|---|---|
| Ask | 写入默认询问;headless reasonix run 对 writer fail closed |
| Auto | 普通 writer 自动放行;deny/ask 规则、Plan 确认、嵌套/间接 Bash、MCP destructive、敏感 remember/forget 仍要问 |
| Yolo | 唯一可绕过嵌套 Bash 人工门槛;仍不绕过 deny 与 sandbox |
另有 internal/guardian:独立安全评审子会话(带 denial circuit breaker)——不能代替记忆写入的用户确认。Hooks(internal/hook)提供 PreToolUse/PostToolUse/PostToolUseFailure,并做 Claude 工具名兼容映射。
11.2 Sandbox + confine
builtin/bash.go 把命令包进 sandbox.Command:
| 平台 | Bash OS sandbox |
|---|---|
| macOS | sandbox-exec(Seatbelt) |
| Linux | bwrap |
| Windows | 产品固定 off,命令 unconfined;文件类 builtins 仍有 workspace confine |
enforce 但无后端时 fail closed(不裸跑)。escape 走 sandbox.EscapeApprover。MCP stdio 默认不继承 Bash sandbox(internal/plugin/plugin.go 的产品默认是 host 进程,注释写明原因)。
SECURITY.md 把边界写清楚:workspace 围栏、权限、沙箱、密钥、HTTP serve 的 localhost/CORS、桌面/bot 隔离、updater 校验——以及什么不算漏洞。
11.3 Checkpoint / Rewind
对齐 Claude Code Esc-Esc / /rewind 叙事(docs/CHECKPOINTS.md):
- 机制:文件快照(非 git);sidecar
<session-id>.ckpt/; - 跟踪范围:带
Previewer的编辑工具(write_file/edit_file/multi_edit); - 不跟踪:
bash副作用;move_file尚未实现 Previewer; - API:
Controller.Checkpoints/Rewind/PrepareRewind+CommitRewind(含 coverage confirmation); - 明确不做:git-backed rollback(文档标 out of scope)。
Checkpoint / Delivery / Goal:交付证据与完成协议
12.1 正交三轴
docs/GOAL_ENFORCEMENT.zh-CN.md 开篇:
Goal 是唯一的跨 turn 调度器,Delivery 是纯质量门禁,工具权限与沙箱不受 Goal 开关影响。
| 轴 | 职责 |
|---|---|
Goal(/goal) |
跨 turn 推进、预算、pause/resume |
| Delivery | readiness:todo / criteria / verification / review / signoff / capability… |
| Permission / Sandbox | 始终独立 |
模型通过 update_goal(continue|complete|blocked) 申报意图;宿主用结构化 ReadinessResult(agent.go:1497+)决定是否真完成。宣称 complete 但缺证据 → 开启下一轮,而不是信模型嘴硬。
12.2 complete_step
内置工具描述写得很凶(TOOL_CONTRACT.md):completion 必须带 evidence;host 代为推进 todo。这与 grok-build Goal Mode、MiMo「不信任自我报告」同谱——Reasonix 用 host-observed receipts 落地。
12.3 协作 Profile
(docs/COLLABORATION_MODES.zh-CN.md)
| Profile | 要点 |
|---|---|
| Economy | 单模型,较瘦工具面,面向成本敏感任务 |
| Balanced | 完整工具面;可选独立 planner_model |
| Delivery | Balanced + use_capability + 验收合约 + review 门禁 |
control.Controller:一张脸接所有前端
13.1 设计铁律
REASONIX.md:11-13:
One transport-agnostic
control.Controllersits behind every frontend (chat TUI, HTTP/SSE serve, Wails desktop). Add behavior to the controller, not a frontend, so all three inherit it.
实际还有 ACP / bot 等入口。统一装配:boot.Build → Controller → agent.Runner(或 Coordinator)。
13.2 一次用户消息的路径
flowchart LR
TUI["chat_tui"] --> SUB["Submit / Send"]
HTTP["serve HTTP/SSE"] --> SUB
DESK["Wails desktop"] --> SUB
ACP["ACP"] --> SUB
SUB --> ADM["admitGuardedTurn<br/>防重入 / park"]
ADM --> COMP["Compose(turn tail)"]
COMP --> RUN["Coordinator? → Agent.Run"]
RUN --> EVT["event.Sink<br/>reasoning/text/tool/notice"]
EVT --> TUI
EVT --> HTTP
EVT --> DESK
关键 API 族(controller.go):Send / Submit / SubmitHTTP / SubmitDisplay / RunTurn / Goal 循环变体;runGuarded 做 turn admission,避免双开跑飞。
13.3 为什么 Controller 会变成 6k 行巨文件?
因为它吞下了:会话恢复、checkpoint、memory slash、MCP 生命周期、capability、steer fallback、yolo/plan、attachments、goal usage……所有前端共享的产品行为。代价是文件巨大;收益是「修一处,TUI/桌面/HTTP 一起对」。本系列里 OpenAI4S 的 gateway.py、Codex 的 turn.rs 同属「巨心脏」形态。
记忆、指令、扩展、桌面/ACP
14.1 Context Engine v2:指令 ≠ 记忆
docs/SESSION_MEMORY_RETRIEVAL.md 的中心法则:
- Standing instructions(
REASONIX.md/AGENTS.md/CLAUDE.md+.local+ 祖先目录 + 全局):必须出现在相关 turn;进 cache-stable prefix; - Background memory:可过时的事实;BM25 auto-recall(默认
defaultAutoRecallLimit=4、defaultAutoRecallChars=2400)进 turn tail;索引在下个会话才并入 prefix; #note//remember写指令文件;remember工具写背景事实——不要混用;- 安全写入:部分 scope create-only;其余需确认——Auto/Yolo/Guardian 不能绕过;
- 指令解析:更深目录覆盖更广;同目录
.local胜出;@path导入限 5 层且禁止逃逸。
14.2 三层扩展:MCP · Plugin Package · Extension Protocol v1
| 层 | 是什么 | 信任模型 |
|---|---|---|
MCP(internal/plugin) |
JSON-RPC;stdio / Streamable HTTP;工具名 mcp__<server>__<tool>;兼容项目 .mcp.json |
精确规则 + mcp_connect__*;OAuth 等仍在 deferred |
Plugin packages(internal/pluginpkg) |
reasonix-plugin.json;兼容 .claude-plugin / .codex-plugin;贡献 skills/hooks/mcp/commands/agents/themes |
安装启用后按贡献面生效 |
Extension Protocol v1(reasonix.extension.v1) |
NDJSON JSON-RPC sidecar;17 个冻结 hook point(tool.before、permission.decision、system_prompt.build、compaction.*…);schema 生成 + CI drift-check |
Full trust:安装即最高权限,可覆写 host deny;UI 标 FULL TRUST |
MCP 与 Extension 是不同层:前者接工具/资源,后者接宿主生命周期拦截。Full-trust Extension 是独立攻击面,也是「Open Design 式公开合约」在 Reasonix 里的最硬落地(docs/EXTENSION_PROTOCOL.md)。
14.3 Subagent profiles
Skill frontmatter runAs: subagent(docs/SUBAGENT_PROFILES.md):内置 explore / research / review / security_review;CLI reasonix subagent …;调用走 /<profile> 或 task/fleet + write_paths 并行写隔离。默认并发 max_subagent_concurrency=6、max_parallel_writers=3。Claude 插件 agents/*.md 可映射为 /<plugin>:agent:<name>。
14.4 桌面与 ACP
- Desktop(
desktop/):嵌套 Wails 模块(保持父模块CGO_ENABLED=0);React+TS webview ↔ 同一Controller(无 HTTP hop); - ACP(
internal/acp,docs/ACP.md):reasonix acp [--profile …];NDJSON JSON-RPC;loadSession+embeddedContext;能力广告 无 image/audio;MCPhttp: true、sse: false;vendor 扩展含_reasonix.io/session/steer; - VS Code 扩展
SivanLiu.reasonix-agent拉起本机reasonix acp; - Remote-SSH / workbench:基线 PR #7368 reconnect hardening——产品已从本地 TUI 长到远程工作台。
品味与边界
十项决策五段式复盘
| # | 选择 | 动机 | 约束 | 被否方案 | 代价 |
|---|---|---|---|---|---|
| 1 | 易变上下文进 turn tail | 前缀稳定 = 省钱 | DeepSeek 自动前缀缓存按字节命中 | 全部塞 system | 每轮装配/剥离成本;绕过 Compose 会破坏宪法 |
| 2 | Tool schema 排序后哈希 | 避免假 miss | 工具注册顺序不可控 | 按注册序哈希 | 排序逻辑本身要测试守护 |
| 3 | Steer 付一次 cache 税 | 插话必须立即可见 | 模型必须看到新指令 | 不插话 / 假装零成本 | 每插一次话就 miss 一轮 |
| 4 | 压缩分三阶梯 | 延迟 summary 重置 | 上下文终会增长 | 一次到位 summary | 需要额外 snip 逻辑与阈值调参 |
| 5 | use_capability 固定代理 |
动态 MCP 不能动 schema | MCP 库存随时变 | 直接把 mcp__* 注册进 Registry | 多一层解析与权限名(mcp_connect__*) |
| 6 | bash 运行时只读降级 | schema 稳定、语义精确 | bash 是否只读取决于 argv | 只按 schema 判 | BashCommandIsReadOnly 要维护危险命令清单 |
| 7 | Missing reasoning 静默重试 | DeepSeek thinking 偶发缺 reasoning | provider 要求回放 thinking | 直接失败 | 增加一次同请求重放;需防抖 cooldown |
| 8 | maxSteps → Pause | 会话资产优先 | 预算有限但工作已落盘 | 报错重来 | 暂停状态机与恢复路径变多 |
| 9 | 行为进 Controller | 多前端一致 | 前端数量增长 | 各前端自实现 | Controller 变成 6k 行巨文件 |
| 10 | 契约可执行(SPEC/TOOL_CONTRACT/PR) | 宪法不能只靠自觉 | 仓库会一直长大 | wiki 文档 | CI/测试成本;PR 元数据要求 |
横向对比(编程 Agent 桌)
| 维度 | Claude Code | Open Design | OpenAI4S | Hermes | Codex | Reasonix |
|---|---|---|---|---|---|---|
| 品类 | 终端编程 | 设计宿主 | 科研双平面 | 个人学习 agent | 官方 Rust CLI | DeepSeek 终端编程 |
| 主循环 | 自有 | 无(接别人) | Engine+Cell | Python loop | run_turn |
runToolLoop |
| 上下文 | 强提示工程 | 20 层分带缓存 | 科学态在内核 | 前缀神圣 | 两层 turn | Cache-first + shape |
| 完成信号 | 自然结束 | 产物文件 | finalize / submit_output | 技能沉淀 | 三方停机 | 自然结束 + Delivery readiness |
| 扩展 | MCP/Skills | CLI 适配器 | Skills 食谱 | 74 工具插件 | MCP+crate | MCP + Plugin包 + Full-trust Extension v1 |
| 分发 | 闭源产品 | daemon+web | Python daemon | uv/Python | Rust 二进制 | Go 静态二进制 |
| 权限产品面 | 弹窗+规则 | 宿主委托 | Notebook 审批 | 环境多后端 | Ask×沙箱 | Ask/Auto/Yolo × sandbox(正交) |
诚实边界
- 巨文件成本:
controller.go/chat_tui.go/agent.go均数千行,新人 onboarding 陡; - DeepSeek 优先税:reasoning 恢复、cache 假设对其他网关不一定成立(有兼容路径,但产品心智仍偏 DeepSeek);
- Executor 直接 MCP:Balanced Executor 仍可能因 MCP 库存变化冷缓存——文档已承认;
- Windows Bash OS sandbox:产品固定 unconfined;文件工具仍有 confinement;
- Checkpoint 覆盖面:不跟踪 bash 副作用;无 git-backed;
move_filepreview 未完成; - Extension full trust:安装即最高权限——能力强,威胁模型需用户理解;
- MCP long tail:OAuth、
list_changed、部分 scopes 等仍 deferred(docs/SPEC.md:877); - ACP 能力广告:无 image/audio;agent 侧 MCP SSE 收窄;
- 旧分支:
v1仅关键修复;分析/二次开发应对齐main-v2。
源码导览索引与本地复现
| 顺序 | 路径 | 看什么 |
|---|---|---|
| 1 | README.zh-CN.md + docs/SPEC.md |
产品位与契约 |
| 2 | REASONIX.md |
团队自己的 cache 宪法 |
| 3 | cmd/reasonix/main.go + internal/boot |
入口与 boot.Build |
| 4 | internal/control/input.go → Compose |
turn tail |
| 5 | internal/agent/run_loop.go |
主循环 |
| 6 | internal/agent/cache_shape.go |
miss 诊断 |
| 7 | internal/agent/compact.go + prune.go |
压缩三阶梯 |
| 8 | internal/agent/execute_one.go |
单工具管线 |
| 9 | internal/agent/coordinator.go |
双模型双 session |
| 10 | docs/TOOL_CONTRACT.md + internal/tool/builtin/ |
工具面 |
| 11 | docs/TOOL_APPROVAL_MODES.md + permission/ + sandbox/ |
Ask/Auto/Yolo × 围栏 |
| 12 | docs/CHECKPOINTS.md |
rewind 快照 |
| 13 | docs/GOAL_ENFORCEMENT.zh-CN.md |
完成协议 |
| 14 | docs/SESSION_MEMORY_RETRIEVAL.md |
指令 vs 记忆 |
| 15 | docs/EXTENSION_PROTOCOL.md + PLUGIN_PACKAGES.md |
扩展三层 |
| 16 | docs/ACP.md / desktop/README.md |
嵌入与桌面 |
| 17 | internal/control/controller.go(选读) |
多前端编排 |
本地复现:
cd "参考项目/DeepSeek-Reasonix"
git fetch origin main-v2
git checkout main-v2
git reset --hard 627b051
# 可选:跑契约测试(需本机 Go)
# go test ./internal/tool -run TestBuiltinToolContractDocumentation
附录 A · 与本系列「Agent 工程模式目录」的对照
| 模式目录章节 | Reasonix 落点 |
|---|---|
| 一 Prompt Cache 工程 | 教科书级:稳定前置 / 易变外移 / 工具稳定 / 压缩慎重 / 遥测可见(五板斧齐) |
| 二 停止判定 | 自然停止 + maxSteps/todo stall Pause + Delivery readiness |
| 三 权限闸门 | Ask/Auto/Yolo + 规则前置 + bash 分解 + OS sandbox(Win Bash 缺口) |
| 四 上下文压缩 | snip 优先,summary 付税;soft 明确保 prefix |
| 五 记忆体系 | 指令文件(prefix)≠ 背景事实(BM25/tail);确认门不可被 Yolo 绕过 |
| 六 工具系统 | 固定代理隔离动态 MCP;Economy connect_tool_source |
| 七 沙箱 | Seatbelt/bwrap;应用围栏 → 权限 → OS sandbox |
| 八 子 Agent | Skill profile + task/fleet;write_paths 并行写隔离 |
| 十二 持久化 | 文件 checkpoint rewind(非 git);不覆盖 bash 副作用 |
| 十三 扩展生态 | MCP + Plugin 包 + Full-trust Extension Protocol v1 |
| 十五 模型策略 | DeepSeek 优先;planner_model 双 session 保各自 cache |
附录 B · 一句话收束
Reasonix 不是「又一个 Go 写的 Claude Code 仿制品」。
它把 DeepSeek 的自动前缀缓存,从费用边角料提升为架构中轴:Compose 分流、Shape 诊断、Capability 代理、PR 元数据、Delivery 证据——全部绕着「别把缓存打冷」转。若你在本系列里已经理解 Hermes 的 cache 神圣与 Open Design 的分带装配,读 Reasonix 就是看同一种哲学在 Go 单二进制 coding agent 里被执行到什么硬度。
附录 C · 开发者动机还原:从「成本焦虑」到「架构宪法」
第 1、2、4 章已经把动机链展开;这里保留一张浓缩对照表,方便回看。
| 动机 | 证据 | 长成的架构 |
|---|---|---|
| 长会话 token 成本高 | README.zh-CN.md:42-43 |
产品定位「围绕前缀缓存调优」 |
| 前缀必须字节稳定 | REASONIX.md:14-16 |
宪法 + PR 元数据 + CI |
| 易变内容必须外移 | control/input.go:181-183 |
Compose turn tail |
| 双模型不能共用会话 | docs/SPEC.md:238-240 |
Planner / Executor 独立 session |
| 压缩必须诚实标价 | docs/SPEC.md:328-329 |
soft → snip → summary + LogRewriteVersion |
| miss 必须可解释 | cache_shape.go |
PrefixShape / CompareShape 诊断 |
| 成本是产品轴 | COLLABORATION_MODES.zh-CN.md:86,136 |
Economy / Balanced / Delivery 三档 |
分析基线:参考项目/DeepSeek-Reasonix @ 627b051(main-v2)。
系列位置:编程 Agent 样本;与 Hermes(cache)、Codex(沙箱正交)、Claude Code(品类)最近邻。
附录 A · 与本系列「Agent 工程模式目录」的对照
| 模式目录章节 | Reasonix 落点 |
|---|---|
| 一 Prompt Cache 工程 | 教科书级:稳定前置 / 易变外移 / 工具稳定 / 压缩慎重 / 遥测可见(五板斧齐) |
| 二 停止判定 | 自然停止 + maxSteps/todo stall Pause + Delivery readiness |
| 三 权限闸门 | Ask/Auto/Yolo + 规则前置 + bash 分解 + OS sandbox(Win Bash 缺口) |
| 四 上下文压缩 | snip 优先,summary 付税;soft 明确保 prefix |
| 五 记忆体系 | 指令文件(prefix)≠ 背景事实(BM25/tail);确认门不可被 Yolo 绕过 |
| 六 工具系统 | 固定代理隔离动态 MCP;Economy connect_tool_source |
| 七 沙箱 | Seatbelt/bwrap;应用围栏 → 权限 → OS sandbox |
| 八 子 Agent | Skill profile + task/fleet;write_paths 并行写隔离 |
| 十二 持久化 | 文件 checkpoint rewind(非 git);不覆盖 bash 副作用 |
| 十三 扩展生态 | MCP + Plugin 包 + Full-trust Extension Protocol v1 |
| 十五 模型策略 | DeepSeek 优先;planner_model 双 session 保各自 cache |
附录 B · 一句话收束
Reasonix 不是「又一个 Go 写的 Claude Code 仿制品」。
它把 DeepSeek 的自动前缀缓存,从费用边角料提升为架构中轴:Compose 分流、Shape 诊断、Capability 代理、PR 元数据、Delivery 证据——全部绕着「别把缓存打冷」转。若你在本系列里已经理解 Hermes 的 cache 神圣与 Open Design 的分带装配,读 Reasonix 就是看同一种哲学在 Go 单二进制 coding agent 里被执行到什么硬度。
附录 C · 开发者动机还原:从「成本焦虑」到「架构宪法」
第 1、2、4 章已经把动机链展开;这里保留一张浓缩对照表,方便回看。
| 动机 | 证据 | 长成的架构 |
|---|---|---|
| 长会话 token 成本高 | README.zh-CN.md:42-43 |
产品定位「围绕前缀缓存调优」 |
| 前缀必须字节稳定 | REASONIX.md:14-16 |
宪法 + PR 元数据 + CI |
| 易变内容必须外移 | control/input.go:181-183 |
Compose turn tail |
| 双模型不能共用会话 | docs/SPEC.md:238-240 |
Planner / Executor 独立 session |
| 压缩必须诚实标价 | docs/SPEC.md:328-329 |
soft → snip → summary + LogRewriteVersion |
| miss 必须可解释 | cache_shape.go |
PrefixShape / CompareShape 诊断 |
| 成本是产品轴 | COLLABORATION_MODES.zh-CN.md:86,136 |
Economy / Balanced / Delivery 三档 |
分析基线:参考项目/DeepSeek-Reasonix @ 627b051(main-v2)。
系列位置:编程 Agent 样本;与 Hermes(cache)、Codex(沙箱正交)、Claude Code(品类)最近邻。