CODING AGENT HARNESS · SOURCE AUDITREPORT 15 / 18
15

DeepSeek-Reasonix

DeepSeek 原生、缓存优先、Go 单二进制与多前端控制面;把低 token 成本、可恢复长会话和插件/子 Agent 治理做成同一套运行时。

Go · DeepSeek-native Cache-first HarnessMITmain-v2
SOURCE
VERIFIED
Repository
esengine/DeepSeek-Reasonix
Commit
22e18cafc0dfe27f192e8e303ffae0a0bc548eb0
Commit date
2026-08-03T06:00:46+08:00
Findings
31
Citations
87
Tracked files
2,291
EXECUTIVE READING

先给结论,再进入源码

核心机制

Agent Run + typed event stream;Delivery readiness、Auto Guard 与 repeat guards

上下文

prefix cache stable;0.5/0.6/0.8/0.9 分层清理;固定 16K tail + summary/archive

安全边界

workspace roots + OS sandbox/escape approver;Plan 不是权限边界;运行可按配置放宽

适用建设

DeepSeek/OpenAI-compatible endpoint、终端/桌面/ACP、多前端企业本地部署

值得借鉴

  • DeepSeek thinking/tool-call 回放和 prefix cache 工程化
  • 安全审批、Auto Guard、证据账本串成工具流水线
  • Go 单二进制 + TUI/HTTP/Desktop/ACP 共享控制面

需要警惕

  • 核心、桌面与服务端体量大,二次开发成本高
  • 沙箱是否强制取决于配置与宿主,不能把 Plan 当隔离
  • 外部 MCP/插件/自定义 provider 扩展供应链和网络信任面

直接带走

  • cache-stable system prefix + local-only transcript
  • tool pipeline + evidence receipts
  • planner/executor separation + read-only parallel tasks
00 · METHOD

研究口径:先锁提交,再沿运行链读代码

README POLICY

README 只用于确认项目入口与命名;实现结论来自 Go 的 boot、control、agent、provider、tool、sandbox、permission、plugin、skill、memory、session、evidence、eventwire,以及对应单元/E2E/基准代码。

FACT POLICY

优先引用运行实现和契约;对 DeepSeek thinking、缓存、MCP、Delivery、Plan、沙箱和子 Agent 分别区分模型协议事实、主机强制边界、配置默认值和可选能力。

INFERENCE POLICY

不推断 DeepSeek 服务端内部,也不把 README 或 prompt 中的承诺当成已启用能力;源码没有证明默认强制的地方明确标为限制或风险。

L1运行实现
L2接口契约
L3测试证明
L4文档佐证
L5明确推断

本页引用 29 个不同源码/测试文件;证据角色分布:契约 27 · 实现 51 · Prompt 2 · 配置 2 · 测试 5。代码块是固定提交中的原文截取,长区间仅在中部折叠,首尾行号保持真实。

01 · TECHNICAL MAPS

架构总图与单轮执行链路

两张图均由本页证据账本生成,并通过 Archify showcase 9 项校验(0 error / 0 warning)。图可单独打开、搜索、缩放和追踪关系。

FIGURE 01DeepSeek-Reasonix Harness 架构图全屏打开 ↗
FIGURE 02用户输入到工具回写的技术链路全屏打开 ↗
02 · COVERAGE MAP

审计维度与证据等级

架构与 Agent Loop verified L1 / L2 / L3

boot/Controller/Agent 的装配、异步 turn admission、主循环上限与 Delivery readiness guard。

Provider、流式与重试 verified L1 / L2 / L3

OpenAI-compatible/DeepSeek 与 Anthropic SSE、thinking replay、断流重连和工具调用配对修复。

上下文、压缩与恢复 verified L1 / L2 / L3

软阈值、工具结果瘦身、摘要、归档、机械折叠、固定尾部与 stuck 熔断。

上下文、压缩与记忆 verified L1 / L2 / L3

层级指令、自动记忆、cache-stable system prefix 与 session-only turn injection。

工具分发与结果治理 verified L1 / L2 / L3

parse→proxy/policy→delivery/recovery/permission→lease/hooks/preview→execute/evidence。

执行环境与沙箱 verified L1 / L2 / L3

macOS Seatbelt、Linux bubblewrap、Windows 差异、workspace roots、network 和 escape approver。

权限与安全 verified L1 / L2 / L3

纯权限策略、Bash 间接执行分类、Auto Guard、MCP 授权与凭据环境过滤。

MCP 与连接器 verified L1 / L2 / L3

stdio/HTTP/SSE JSON-RPC、惰性 schema cache、启动并发、身份摘要与 live safety recheck。

指令、Skills 与插件 verified L1 / L2 / L3

内建 Skills、metadata-first 索引、只读子 Agent 入口、项目/全局 memory 和 Hooks。

子 Agent 与协作 verified L1 / L2 / L3

Planner/Executor 两模型、parallel_tasks 64 上限、深度/并发/读写调度和持久 transcript。

持久化与观测 verified L1 / L2 / L3

JSONL event log、revision/CAS、recovery branch、typed event wire、usage/cache/evidence receipts。

测试、基准与成熟度 verified L1 / L3

真实 Agent loop、断流恢复、配对修复、compaction stuck guard、DeepSeek cache benchmark 与 MIT 许可证。

01
DIMENSION · ARCHITECTURE-LOOP

架构与 Agent Loop

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

01
L1事实reasonix-arch-001

Boot 是唯一装配根,所有前端共享同一套 Harness

源码事实

boot 包明确负责加载配置、解析模型、构造内建工具与插件、接入权限 gate、构造 executor,并可包装双模型 Coordinator;TUI、HTTP/SSE、桌面 webview 都通过同一个 Controller 驱动。

白话解释

终端、桌面和服务端不是各写一套 Agent,而是都插到同一个“总电闸”上,所以权限、工具和生命周期不会因为换界面而变一套。

对自研 Harness 的含义

自研时应把 frontend 变成事件消费者,避免在 UI 层重复实现 turn、审批和恢复。

关键源码 · 契约
internal/boot/boot.go · L1–L8
    1  // Package boot assembles a ready-to-drive control.Controller from configuration:
    2  // it loads config, resolves the model(s), builds the tool registry (built-ins +
    3  // plugins), wires the permission gate, and constructs the executor — optionally
    4  // wrapping it in a two-model Coordinator. It is the one place that turns "what the
    5  // user configured" into "a Controller a frontend can drive", so every frontend —
    6  // the terminal TUI, the HTTP/SSE server, the desktop webview — shares the exact
    7  // same assembly instead of each re-deriving it. Frontends pass only a sink and a
    8  // couple of run knobs; everything else comes from config.
查看全部 2 处证据
  • 契约 internal/boot/boot.go:1–8 boot 作为唯一装配点以及四类前端共享。
  • 契约 internal/control/controller.go:1–10 transport-agnostic Controller 与 typed event sink。
02
L1事实reasonix-arch-002

主循环以模型自然结束为主,额外叠加多种止损护栏

源码事实

单个工具结果先被截到 32 KiB;空最终回复、流恢复、handoff 和 final readiness 各有上限。Agent 在 MaxSteps<=0 时不按轮数截断,直到模型给出无工具的 final、取消或 provider 错误;Delivery 模式还要求 todo、验证和 complete_step 证据。

白话解释

它允许长任务一直做,但每个工具输出和“最后确认”都有保险丝;只要交付模式缺验收或验证,主机就不会让它假装完成。

对自研 Harness 的含义

“无限循环”与“无限输出”被拆开治理;建设时要同时设计自然终止、重复调用 guard、输出预算和交付完成判定。

关键源码 · 实现
internal/agent/agent.go · L33–L62
   33  // maxToolOutputBytes caps a single tool result before it goes into the model's
   34  // context. ~32KB is roughly 8K tokens — enough for a full file read or a busy
   35  // grep, while preventing one accidental "read this 5 MB log" from blowing the
   36  // window before the next compaction runs.
   37  const maxToolOutputBytes = 32 * 1024
   38  
   39  const maxFinalReadinessBlocks = 3
   40  
   41  // maxFinalReadinessBlocksWithProgress is the hard cap on readiness retries when
   42  // the model keeps producing new host-observable receipts between blocks. A
   43  // converging turn (edit → verify → review still catching up to the latest
   44  // mutation) deserves more nudges than a stuck one; a turn that stalls with no
   45  // new receipts still fails at maxFinalReadinessBlocks.
   46  const maxFinalReadinessBlocksWithProgress = 6
   47  const maxEmptyFinalBlocks = 3
   48  const maxStreamRecoveries = 3
   49  const maxExecutorHandoffNudges = 1
   50  
   51  // DeliveryRuntimeMarker is the delivery-mode contract block appended to user
   52  // turns (withTurnPreferences). Exported as the single source of truth for the
   53  // byte-exact suffix strip in preview derivation and for cross-package tests;
   54  // its text is cache-frozen — changing it breaks steer replay matching and the
   55  // prefix stability of every live delivery session.
   56  const DeliveryRuntimeMarker = `<delivery-runtime>
   57  This session is in delivery-first mode. Before any state-changing tool call,
   58  establish concrete, verifiable acceptance criteria with todo_write. After the
   59  change, inspect the result, run relevant verification, and sign off each step
   60  with complete_step citing the successful verification command. The host enforces
   61  these gates and will reject mutation or finalization when evidence is missing.
   62  </delivery-runtime>`
查看全部 3 处证据
  • 实现 internal/agent/agent.go:33–62 工具输出、final readiness、流恢复与 Delivery marker 上限。
  • 契约 internal/agent/agent.go:1060–1064 MaxSteps<=0 的自然终止契约。
  • 实现 internal/agent/agent.go:1203–1213 Run 的生命周期与工具循环入口。
03
L1事实reasonix-arch-003

Controller 对并发 turn、旋转、收尾和自动保存有明确状态机

源码事实

Controller 拒绝同一会话并发前台 turn;运行中可选择 park FIFO,finishing 窗口仍保持 admission 关闭,TurnDone 派发完成后才启动排队 turn;rotating 与 running 互斥,避免会话切换与执行产生 TOCTOU。

白话解释

用户连按几次发送不会把同一个会话撕成两半:新消息要么排队,要么明确被拒;切换会话时也不会恰好换掉正在用的那份上下文。

对自研 Harness 的含义

桌面/HTTP 多入口必须把“正在运行、等待审批、后台任务、收尾”分开建模,不能只暴露一个 running 布尔值。

关键源码 · 契约
internal/control/controller.go · L60–L76
   60  // ErrTurnRunning reports that a caller tried to start a second foreground turn
   61  // while one is already active in the same Controller.
   62  var ErrTurnRunning = errors.New("turn already running")
   63  
   64  // errTurnRunningRotation and errRotationInProgress are returned by the
   65  // session-rotation gate (beginRotation) when a rotation cannot proceed: a turn
   66  // is in flight, or another rotation already holds the gate.
   67  var (
   68  	errTurnRunningRotation = errors.New("cannot start a new session while a turn is running")
   69  	errRotationInProgress  = errors.New("cannot start a new session while another session change is in progress")
   70  )
   71  
   72  // errNoSessionPath is returned by snapshot when a session has content to persist
   73  // but no resolved session path — a misconfiguration (e.g. an unresolvable data
   74  // dir in a bot deployment) that previously dropped conversations silently
   75  // (#4414). Callers log it and continue; it must never be swallowed quietly.
   76  var errNoSessionPath = errors.New("session has content but no session path; conversation cannot be persisted")
查看全部 3 处证据
  • 契约 internal/control/controller.go:60–76 重复 turn、rotation 与持久化错误。
  • 契约 internal/control/controller.go:298–308 RuntimeStatus 的 foreground/prompt/jobs 细分。
  • 实现 internal/control/controller.go:698–820 admission、park、finishing 与 FIFO drain。
02
DIMENSION · PROVIDER-STREAMING

Provider、流式与重试

本章共 4 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

04
L1事实reasonix-provider-001

Provider 消息对象把模型内容与本地显示元数据分开

源码事实

provider.Message 同时承载正文、Raw/ProviderContent、图片、reasoning_content、Anthropic 签名、tool calls 和 LocalOnly;ModelMessages 会在发请求前移除 LocalOnly、RawContent 和 provider-only metadata,并在健康历史上原样返回底层切片以保留缓存路径。

白话解释

用户看到的半截流式输出可以保存下来,但它不会偷偷再喂给模型;真正发到 API 的内容和本地 UI 账本是两条线。

对自研 Harness 的含义

本地可观测性、恢复和 prompt cache 可以同时成立;模型可见消息类型必须有单独的 wire sanitizer。

关键源码 · 契约
internal/provider/provider.go · L40–L75
   40  // Message is a single conversation message.
   41  type Message struct {
   42  	Role Role `json:"role"`
   43  	// Content is the provider-visible conversation content. Keeping this legacy
   44  	// field provider-visible preserves replay for older CLI/Desktop releases.
   45  	Content string `json:"content,omitempty"`
   46  	// RawContent is the user-authored form of a user turn, when it differs from
   47  	// Content because the host added transient context. Older releases ignore
   48  	// this field and still replay the provider-visible Content safely.
   49  	RawContent string `json:"raw_content,omitempty"`
   50  	// ProviderContent is a transitional field written by early Context Engine v2
   51  	// builds. Loaders migrate it into Content/RawContent before normal use.
   52  	ProviderContent  string   `json:"provider_content,omitempty"`
   53  	Images           []string `json:"images,omitempty"`            // data URLs (data:<mime>;base64,…) on user (attachments) and tool (MCP image results) messages; embedded only for vision-capable models
   54  	ReasoningContent string   `json:"reasoning_content,omitempty"` // assistant: thinking-mode chain-of-thought, round-tripped on multi-turn
   55  	// ReasoningSignature is an opaque, provider-issued proof that ReasoningContent
   56  	// is genuine model output. Anthropic requires the signed thinking block be
   57  	// replayed on the next turn when a tool call followed thinking; providers
   58  	// without signed reasoning (e.g. the openai-compatible ones) leave it empty.
   59  	// Round-tripped alongside ReasoningContent.
   60  	ReasoningSignature string           `json:"reasoning_signature,omitempty"`
   61  	ToolCalls          []ToolCall       `json:"tool_calls,omitempty"`      // set by assistant
   62  	ToolCallID         string           `json:"tool_call_id,omitempty"`    // links a tool result to its call
   63  	Name               string           `json:"name,omitempty"`            // tool message: tool name
      … 2 lines omitted; exact range 40–75 …
   66  	CreatedAt          int64            `json:"createdAt,omitempty"`       // local UI metadata; unix milliseconds; stripped before provider requests
   67  	Edited             bool             `json:"edited,omitempty"`          // local UI metadata; provider requests ignore it
   68  	Original           string           `json:"original,omitempty"`        // user prompt before inline edit
   69  	// LocalOnly marks durable transcript content that must never be sent to a
   70  	// model provider. Interrupted streaming output uses it so every frontend can
   71  	// replay what the user saw without feeding partial reasoning or tool-call
   72  	// arguments back into the next request.
   73  	LocalOnly       bool                     `json:"local_only,omitempty"`
   74  	InterruptedTurn *InterruptedTurnRecovery `json:"interrupted_turn,omitempty"`
   75  }
查看全部 2 处证据
  • 契约 internal/provider/provider.go:40–75 Message 字段、reasoning 签名与 LocalOnly 语义。
  • 实现 internal/provider/provider.go:194–221 ModelMessages 清除本地/原始字段。
05
L1事实reasonix-provider-002

发请求前会修复 tool-call 配对和被截断的 JSON

源码事实

NormalizeMessages 在 wire 层补齐未回答的 assistant tool call、丢弃孤儿 tool result、恢复缺失工具名,并对半流式截断参数闭合 JSON;健康历史走 zero-allocation fast path,存盘历史使用保守的 NormalizeSessionMessages。

白话解释

模型上次说“我要调用工具”但进程崩了,下一次请求前会补一张“没有结果”的占位回执,避免 API 因为票据对不上直接 400。

对自研 Harness 的含义

恢复协议应在 provider 边界做防御性修复,而不是污染用户保存的原始 session。

关键源码 · 契约
internal/provider/provider.go · L179–L192
  179  // interruptedToolResult stands in for a tool result that never landed — an
  180  // assistant tool_calls turn whose execution was cut short (interrupt, crash) and
  181  // later resumed. Sending such a turn unanswered trips the OpenAI/DeepSeek 400
  182  // "An assistant message with 'tool_calls' must be followed by tool messages
  183  // responding to each 'tool_call_id'".
  184  const interruptedToolResult = "[no result: the previous turn was interrupted before this tool call completed]"
  185  
  186  // SanitizeToolPairing is the provider-side alias for NormalizeMessages. It repairs
  187  // a history so it satisfies the tool-call contract the OpenAI-compatible and
  188  // Anthropic APIs enforce (every assistant tool_calls answered, no orphan tool
  189  // messages, truncated args closed) right before sending it to the wire — without
  190  // touching the stored session. Kept as a distinct name so call sites read as
  191  // "defensive wire prep" rather than "session mutation".
  192  func SanitizeToolPairing(msgs []Message) []Message { return NormalizeMessages(msgs) }
查看全部 3 处证据
  • 契约 internal/provider/provider.go:179–192 SanitizeToolPairing 的 wire-safe 定位。
  • 实现 internal/provider/provider.go:223–251 配对、孤儿、工具名与 truncated args 修复规则。
  • 实现 internal/provider/provider.go:364–443 copy-on-write 的 JSON 闭合逻辑。
06
L1事实reasonix-provider-003

DeepSeek thinking 与工具调用 reasoning replay 是显式协议分支

源码事实

OpenAI-compatible client 对 DeepSeek 标记 RequiresToolCallReasoning,并按模型/配置决定是否警告缺失 reasoning;构造请求时对包含 tool calls 的 assistant 消息发送 reasoning_content(包括空字符串),同时支持 DeepSeek Beta prefix continuation 和最多三次尚未输出 token 的断流重连。

白话解释

Reasonix 没把 DeepSeek 当普通 OpenAI 接口:它记得“思考字段”要跟着工具调用回放,并把 Beta 截断续写和断网重试接在同一个流上。

对自研 Harness 的含义

如果自研只保存 text/tool_calls 而丢 reasoning,DeepSeek thinking 模型会在下一轮请求失败或失去缓存命中。

关键源码 · 实现
internal/provider/openai/openai.go · L279–L316
  279  func (c *client) Name() string { return c.name }
  280  
  281  func (c *client) RequiresToolCallReasoning() bool {
  282  	return c != nil && c.deepseek && c.thinkingType != "disabled"
  283  }
  284  
  285  func (c *client) RequiresReasoningRoundTrip() bool {
  286  	return c != nil && c.kimiK3
  287  }
  288  
  289  func (c *client) WarnOnMissingToolCallReasoning() bool {
  290  	return c.RequiresToolCallReasoning() && expectsDeepSeekToolCallReasoning(c.model, c.thinkingType)
  291  }
  292  
  293  func expectsDeepSeekToolCallReasoning(model, thinkingType string) bool {
  294  	if strings.EqualFold(strings.TrimSpace(thinkingType), "enabled") {
  295  		return true
  296  	}
  297  	model = strings.ToLower(strings.TrimSpace(model))
  298  	return strings.Contains(model, "deepseek-v4-flash") ||
  299  		strings.Contains(model, "deepseek-v4-pro") ||
  300  		strings.Contains(model, "deepseek-v3.2") ||
  301  		strings.Contains(model, "deepseek-reasoner") ||
  302  		strings.Contains(model, "deepseek-r1")
      … 4 lines omitted; exact range 279–316 …
  307  		return ""
  308  	}
  309  	protocol := "openai"
  310  	if c.deepseek {
  311  		protocol = "deepseek"
  312  	}
  313  	return strings.Join([]string{
  314  		"openai", strings.TrimSpace(c.name), strings.TrimSpace(c.baseURL),
  315  		strings.TrimSpace(c.model), protocol, strings.TrimSpace(c.thinkingType), strings.TrimSpace(c.effort),
  316  	}, "\x00")
查看全部 4 处证据
  • 实现 internal/provider/openai/openai.go:279–316 DeepSeek tool-call reasoning requirement 与配置指纹。
  • 实现 internal/provider/openai/openai.go:426–471 SSE open、认证重试与 stream reconnect。
  • 实现 internal/provider/openai/openai.go:473–611 prefix continuation、usage 合并与断流重连上限。
  • 实现 internal/provider/openai/openai.go:627–679 DeepSeek reasoning_content wire 字段的回放规则。
07
L1事实reasonix-provider-004

Anthropic 与 DeepSeek Anthropic 端点共用抽象但保留签名差异

源码事实

Anthropic provider 使用手写 net/http SSE;原生 Anthropic 的 thinking block 需要签名回放,而 DeepSeek Anthropic 端点使用 unsigned thinking、thinking.type 与 output_config.effort,client 通过 deepseek 标志分别处理。

白话解释

同一套 Message 能接两种协议,但不会把 Anthropic 的签名规则硬套到 DeepSeek 兼容端点上。

对自研 Harness 的含义

Provider adapter 应该把“协议共性”和“供应商必须回放的证明字段”拆开,不要用一套简单 JSON 模板覆盖所有模型。

关键源码 · 契约
internal/provider/anthropic/anthropic.go · L1–L20
    1  // Package anthropic implements the Anthropic Messages API provider (POST
    2  // /v1/messages, SSE streaming) with a hand-written net/http client — no SDK. It
    3  // self-registers under the "anthropic" kind, so any Claude model is a config
    4  // instance rather than code.
    5  //
    6  // Two notes, both rooted in the transport-agnostic provider.Message abstraction:
    7  //
    8  //   - Extended thinking is opt-in (provider config thinking="adaptive"). Anthropic
    9  //     requires the *signed* thinking block be replayed on the next turn when a tool
   10  //     call followed thinking, so Message carries ReasoningSignature alongside
   11  //     ReasoningContent and this provider replays the signed block on the next
   12  //     request. DeepSeek's Anthropic endpoint instead uses unsigned thinking blocks,
   13  //     thinking.type enabled|disabled, and output_config.effort; requests carrying
   14  //     tools must replay all provider reasoning. Some other compatible gateways such
   15  //     as LongCat use the binary toggle without output_config. (redacted_thinking
   16  //     blocks are not yet captured/replayed.)
   17  //   - Native Anthropic requests omit temperature/top_p. Current Claude models
   18  //     (Opus 4.8/4.7) reject sampling parameters with a 400; Anthropic steers
   19  //     behavior via prompting instead. DeepSeek's compatible endpoint accepts the
   20  //     caller's temperature, so that field is preserved only for DeepSeek.
查看全部 2 处证据
  • 契约 internal/provider/anthropic/anthropic.go:1–20 手写 SSE、signed thinking 与 DeepSeek unsigned thinking 差异。
  • 实现 internal/provider/anthropic/anthropic.go:129–185 deepseek、thinking、effort 与 RequiresToolCallReasoning。
03
DIMENSION · CONTEXT-COMPACTION

上下文、压缩与恢复

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

08
L1事实reasonix-context-001

上下文维护是 0.5/0.6/0.8/0.9 多级管道

源码事实

默认 softCompactRatio=0.5、toolResultSnipRatio=0.6、compactRatio=0.8、forceRatio=0.9,tail 固定 16384 tokens;先提示并保持 cache prefix,再瘦身旧工具结果,必要时 prune 后摘要,连续两次仍压不下去则设置 compactStuck 暂停自动压缩。

白话解释

它不会一到 50% 就重写整段历史:先提醒、再剪掉过期工具输出,真的快满才摘要;窗口太小导致反复压缩时会熔断而不是死循环。

对自研 Harness 的含义

Context pipeline 应把成本、缓存稳定性、信息损失和熔断作为不同阈值测试,而不是单一 max-token 截断。

关键源码 · 契约
internal/agent/compact.go · L19–L36
   19  // Compaction is a low-frequency cache-reset point: the prompt grows append-only
   20  // (high cache hits) until a turn nears compactRatio of the window, then it is
   21  // compacted down to a tail budget. The budget is a fixed token count, not a
   22  // fraction of the window, so a huge window still compacts rarely while a small
   23  // one still lands below the trigger (which is what stops the re-compaction loop).
   24  const (
   25  	defaultSoftCompactRatio    = 0.5   // report growing context here, but keep the cache-stable prefix intact
   26  	defaultToolResultSnipRatio = 0.6   // rewrite stale tool results cheaply before summary compaction
   27  	defaultCompactRatio        = 0.8   // trigger: prompt at this fraction of the window compacts
   28  	defaultCompactForceRatio   = 0.9   // force compaction at this high-water mark even for low-value folds
   29  	defaultCompactTarget       = 0.5   // safety cap: the kept tail never exceeds this fraction of the window
   30  	defaultTailTokens          = 16384 // verbatim recent-tail budget, in tokens
   31  	minRecentKeep              = 2     // never keep fewer recent messages than this
   32  	minCompactMessages         = 2     // skip compaction below this many compactable messages
   33  	fallbackTokPerChar         = 0.25  // ~4 chars/token, used before any usage is available to calibrate
   34  	maxPinnedFirstUserTokens   = 1500  // ceiling on pinning the first user turn verbatim; larger first turns (pasted content) stay foldable
   35  	pinnedFirstUserWindowFrac  = 0.15  // and never pin a first turn worth more than this fraction of the window
   36  )
查看全部 2 处证据
  • 契约 internal/agent/compact.go:19–36 压缩比例、尾部预算与估算参数。
  • 实现 internal/agent/compact.go:82–147 soft notice、snip、prune、compact 与 stuck 熔断。
09
L1事实reasonix-context-002

摘要保留用户事实、最近尾部并归档完整旧历史

源码事实

summarySystemPrompt 要求保留 standing facts、goal、decisions、files/code、commands/outcomes、errors/fixes、pending/next step;compact 会把可折叠区域先 archive,再摘要,摘要失败时使用机械 fold marker,保留小 user turns 和完整 active turn。

白话解释

压缩不是把聊天变成一句“继续工作”:它把用户硬约束、做过的命令、错误和下一步分栏记录,原始旧消息还留在 archive 里。

对自研 Harness 的含义

摘要 schema 本身是 Harness 的恢复协议,应该有 golden tests,不能完全交给模型自由发挥。

关键源码 · Prompt
internal/agent/compact.go · L49–L80
   49  // summarySystemPrompt steers the executor to distill older history into a
   50  // structured briefing it can keep relying on after the originals are dropped.
   51  // The section layout mirrors what a coding agent actually needs to resume work
   52  // mid-task: the goal verbatim, the concrete state of the code, and an explicit
   53  // next step — so the post-compaction turn doesn't lose the thread or re-derive
   54  // decisions already made.
   55  const summarySystemPrompt = `You are compacting the earlier part of a coding agent's conversation to save context.
   56  The agent keeps your summary alongside the user's own turns (kept verbatim) and the recent tail; your job is to fold the assistant/tool work into a briefing it can resume from.
   57  Write under these exact headings, omitting a heading only if it has no content:
   58  
   59  ## Standing facts & constraints
   60  Everything the user stated that still governs the work — names, paths, IDs, versions, tokens, preferences, and hard "never do X" rules — in their own words. Be exhaustive; this is the durable contract, so prefer over- to under-including.
   61  
   62  ## Goal
   63  The user's request and intent.
   64  
   65  ## Decisions & rationale
   66  Key choices made so far and why — so they are not re-litigated or reversed.
   67  
   68  ## Files & code
   69  Files read or modified, with the specific facts that matter: signatures, line locations, data shapes, and exact edits applied. Be concrete; this is what lets the agent act without re-reading everything.
   70  
   71  ## Commands & outcomes
   72  Commands run (builds, tests, git) and their relevant results — what passed, what failed, and the error text that matters.
   73  
   74  ## Errors & fixes
   75  Problems hit and how they were resolved (or not), so the same dead ends are not repeated.
   76  
   77  ## Pending & next step
   78  What is still in progress or unstarted, and the single most concrete next action to take.
   79  
   80  Rules: be terse — bullet points and fragments, not prose. Preserve identifiers, paths, and numbers exactly. Do NOT invent anything not present in the messages; if something is unknown, leave it out rather than guessing.`
查看全部 3 处证据
  • Prompt internal/agent/compact.go:49–80 结构化 summary headings 与不可编造规则。
  • 契约 internal/agent/compact.go:194–203 archive、Started/Done 事件与 active tail。
  • 实现 internal/agent/compact.go:216–297 保留 active turn、archive、summary retry 与机械折叠。
04
DIMENSION · CONTEXT-COMPACTION-MEMORY

上下文、压缩与记忆

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

10
L1事实reasonix-context-003

项目指令与记忆在启动时组成稳定 system prefix,编辑延迟到下一 session

源码事实

memory.Load 一次发现层级文档和自动记忆索引,Compose 把 base prompt 放在最前并追加 memory;WriteDoc 只允许写已识别的记忆文件,并明确不会改变当前 cache-stable system prefix,而是由 controller 通过 turn-tail note 临时生效。

白话解释

REASONIX/AGENTS/全局记忆像开机时装进机器的说明书;本轮改说明书不会偷偷改掉正在复用的缓存前缀,下一次 session 才完全换新。

对自研 Harness 的含义

指令层级和 cache key 需要一起设计;动态信息应走短暂 turn injection,不要频繁重写系统前缀。

关键源码 · 实现
internal/memory/memory.go · L12–L53
   12  // Set is everything memory loaded for one session: the hierarchical docs and a
   13  // handle to the auto-memory store (whose index is captured at load time). It is
   14  // assembled once at boot and folded into the system prompt by Compose. CWD and
   15  // UserDir are retained so the controller can resolve quick-add targets without
   16  // re-deriving discovery context.
   17  type Set struct {
   18  	Docs                   []Source // REASONIX.md / AGENTS.md, ascending precedence
   19  	GlobalGuidance         []Memory // stable snapshot of global user/feedback bodies
   20  	Store                  Store    // auto-memory store (may be a zero/disabled Store)
   21  	Index                  string   // MEMORY.md contents at load time
   22  	CWD                    string   // project working dir used for discovery
   23  	UserDir                string   // user config root (may be "")
   24  	InstructionDiagnostics []instruction.Diagnostic
   25  }
   26  
   27  // Options configures discovery. CWD defaults to "." and UserDir is the user
   28  // config root (config.MemoryUserDir()); a "" UserDir disables user-global docs
   29  // and the auto-memory store.
   30  type Options struct {
   31  	CWD     string
   32  	UserDir string
   33  }
   34  
   35  // Load discovers all memory for a session: the hierarchical docs and the
      … 8 lines omitted; exact range 12–53 …
   44  	resolved := instruction.Resolve(instruction.ResolveOptions{TargetDir: cwd, UserDir: opts.UserDir})
   45  	return &Set{
   46  		Docs:                   resolved.Documents,
   47  		GlobalGuidance:         store.globalGuidanceForProject(),
   48  		Store:                  store,
   49  		Index:                  store.Index(),
   50  		CWD:                    cwd,
   51  		UserDir:                opts.UserDir,
   52  		InstructionDiagnostics: resolved.Diagnostics,
   53  	}
查看全部 3 处证据
  • 实现 internal/memory/memory.go:12–53 Set、Load 与 instruction resolver。
  • 实现 internal/memory/memory.go:94–128 recognized memory path 写边界。
  • 契约 internal/memory/memory.go:175–188 base-first cache-stable Compose。
05
DIMENSION · TOOL-DISPATCH

工具分发与结果治理

本章共 4 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

11
L1事实reasonix-tools-001

每个工具调用固定经过 parse→policy→prepare→finish 四阶段

源码事实

executeOne 为一个调用创建 toolCallPlan,先解析 canonical tool 并应用重复成功/失败和 stale-anchor guard,再做 Plan/proxy/delivery/recovery/permission,随后获取 lease/写 claim、运行 hooks 和 preview,最后 Execute、记录 receipt、运行 post hooks 并截断结果。

白话解释

模型只递交一张工单;真正执行前会先确认工具身份、是否允许、是否会改文件、是否拿到写锁和快照,执行后还要把回执写回账本。

对自研 Harness 的含义

工具扩展点要落在统一 pipeline 中,不能让某个 MCP 或别名工具绕过审批、锁、证据和输出预算。

关键源码 · 契约
internal/agent/execute_one.go · L20–L80
   20  // toolCallPlan holds the resolved, policy-checked state for one tool call.
   21  // Package-private; not shared across goroutines beyond the single executeOne
   22  // invocation that owns it.
   23  type toolCallPlan struct {
   24  	call          provider.ToolCall
   25  	tool          tool.Tool
   26  	canonicalName string
   27  
   28  	permName     string
   29  	permArgs     json.RawMessage
   30  	execTool     tool.Tool
   31  	execArgs     json.RawMessage
   32  	evidenceName string
   33  	evidenceArgs json.RawMessage
   34  	readOnly     bool
   35  
   36  	resolved     tool.ResolvedCall
   37  	resolvedMeta *tool.ResolvedCall
   38  
   39  	mutates                   bool
   40  	verification              bool
   41  	planTransition            bool
   42  	planBefore                string
   43  	planAfter                 string
      … 27 lines omitted; exact range 20–80 …
   71  	if blocked, early := a.parseToolCall(plan); early {
   72  		return blocked
   73  	}
   74  	if blocked, early := a.resolveToolPolicy(ctx, plan); early {
   75  		return blocked
   76  	}
   77  	if blocked, early := a.prepareToolExecution(ctx, plan); early {
   78  		return blocked
   79  	}
   80  	return a.finishToolExecution(ctx, plan)
查看全部 2 处证据
  • 契约 internal/agent/execute_one.go:20–80 toolCallPlan 字段与四阶段执行。
  • 实现 internal/agent/execute_one.go:83–150 canonical resolve、重复/锚点 guard 与 policy 路由。
12
L1事实reasonix-tools-002

use_capability 代理会先解析真实 MCP 目标,再重新做 Plan 与安全判断

源码事实

proxy resolver 在权限、hooks 和 evidence 之前把 provider-visible call 映射到真实 target/name/args;解析后的目标会再次检查 Plan safety,Plan 中只有已授权、非 destructive 的 MCP 可走 planner trusted path,普通 writer 或未授权 MCP 被阻断。

白话解释

模型看到的是一个稳定的“能力入口”,但系统不会因为套了一层代理就放过真实目标;拆包后还要重新验一次。

对自研 Harness 的含义

统一 capability proxy 能保持 prompt schema 稳定,但必须做 resolution 后的第二次安全判定和审计双回执。

关键源码 · 实现
internal/agent/execute_one.go · L153–L269
  153  // applyPlanModeAndProxy handles initial Plan mode, proxy resolution / skip path,
  154  // resolved-target Plan re-check, and MCP Plan availability.
  155  func (a *Agent) applyPlanModeAndProxy(ctx context.Context, plan *toolCallPlan) (toolOutcome, bool) {
  156  	t := plan.tool
  157  	call := plan.call
  158  	if a.planMode.Load() {
  159  		// Translate the tool's optional plan-mode self-report into the policy's
  160  		// tri-state. Mirrors the t.(tool.Previewer) assertion precedent below.
  161  		safety := planmode.PlanSafetyUnknown
  162  		if c, ok := t.(tool.PlanModeClassifier); ok {
  163  			if c.PlanModeSafe() {
  164  				safety = planmode.PlanSafetySafe
  165  			} else {
  166  				safety = planmode.PlanSafetyUnsafe
  167  			}
  168  		}
  169  		if decision := a.planModeDecision(plan.canonicalName, t.ReadOnly(), safety, json.RawMessage(call.Arguments)); decision.Blocked {
  170  			return toolOutcome{
  171  				output:  decision.Message,
  172  				blocked: true,
  173  				errMsg:  "blocked: tool is unavailable during planning",
  174  			}, true
  175  		}
  176  	}
      … 83 lines omitted; exact range 153–269 …
  260  		if plan.readOnly && !mcpServerAuthorized(plan.execTool) {
  261  			reason = "reader from an unauthorized server"
  262  		}
  263  		return toolOutcome{
  264  			output:  fmt.Sprintf("blocked: MCP %s %q is unavailable during Plan mode; finish or exit Plan mode before requesting this call", reason, plan.permName),
  265  			blocked: true,
  266  			errMsg:  "blocked: MCP target is unavailable during planning",
  267  		}, true
  268  	}
  269  	return toolOutcome{}, false
查看全部 2 处证据
  • 实现 internal/agent/execute_one.go:153–269 proxy resolve、provider transcript 保持原名与 resolved target Plan re-check。
  • 实现 internal/agent/execute_one.go:396–431 授权 MCP 与 explicit deny 的最终判定。
13
L1事实reasonix-tools-003

Delivery 模式把验收标准变成 host-enforced tool policy

源码事实

Delivery gates 拒绝会遮蔽 verifier exit code、混合 mutation+verification、opaque inline interpreter 的 Bash;state-changing call 之前必须完成 todo_write 并有 active canonical todo。permission 和 Auto Guard 通过后才获取 workspace write lease,hooks/preview 仍在 Execute 前发生。

白话解释

交付模式不接受“我顺便跑了个检查并 echo $?”这种无法审计的成功;先列验收清单,改完后单独验证,再签收。

对自研 Harness 的含义

这是一种把“完成定义”放入执行器的强约束,比只在 system prompt 里提醒可靠。

关键源码 · 实现
internal/agent/execute_one.go · L272–L312
  272  // applyDeliveryPolicyGates enforces delivery-profile bash and criteria rules and
  273  // classifies whether the call mutates workspace state.
  274  func (a *Agent) applyDeliveryPolicyGates(plan *toolCallPlan) (toolOutcome, bool) {
  275  	if a.deliveryProfile && plan.evidenceName == "bash" && evidence.BashToolCallMasksVerificationExit(plan.evidenceArgs) {
  276  		return toolOutcome{
  277  			output:  "blocked: the trailing echo/printf of $? masks the verifier's exit status, so this command would look successful even when the check failed. Run the verifier or read-only extraction pipeline by itself and let its exit status be the tool result; for example: tail ... | head ... | node --check -",
  278  			blocked: true,
  279  			errMsg:  "blocked: verification exit status masked",
  280  		}, true
  281  	}
  282  	if a.deliveryProfile && plan.evidenceName == "bash" && evidence.BashToolCallMixesMutationAndVerification(plan.evidenceArgs) {
  283  		return toolOutcome{
  284  			output:  "blocked: this command mixes a verification check with a segment that may write state. Run the state-changing preparation separately while a todo is in_progress, then run a read-only verification command. For generated input, prefer a host-recognized read-only pipeline into the verifier (for example: tail ... | head ... | node --check -) instead of writing a temporary file.",
  285  			blocked: true,
  286  			errMsg:  "blocked: mixed mutation and verification command",
  287  		}, true
  288  	}
  289  	if a.deliveryProfile && plan.evidenceName == "bash" && evidence.BashToolCallUsesOpaqueInlineInterpreter(plan.evidenceArgs) {
  290  		return toolOutcome{
  291  			output:  "blocked: delivery mode cannot audit inline interpreter source such as node -e or python -c, so executing it would become an opaque mutation and invalidate prior verification. For inspection, use read_file/grep or another host-proven read-only command. For validation, use a conventional verifier such as node --check, a project test/check/lint command, or a read-only extraction pipeline into the verifier. For an intentional state change, use a file tool or a script file under the current in_progress todo. " + evidence.VerificationCommandSummary(),
  292  			blocked: true,
  293  			errMsg:  "blocked: opaque inline interpreter command",
  294  		}, true
  295  	}
      … 7 lines omitted; exact range 272–312 …
  303  		}, true
  304  	}
  305  	if a.deliveryProfile && plan.mutates && !a.hasActiveCanonicalTodo() {
  306  		return toolOutcome{
  307  			output:  "blocked: delivery-first mode requires every state change to belong to the current in_progress todo. Preserve the completed todo prefix, append a concrete new item if more work was discovered, mark that item in_progress with todo_write, then retry this mutation.",
  308  			blocked: true,
  309  			errMsg:  "blocked: active delivery todo required",
  310  		}, true
  311  	}
  312  	return toolOutcome{}, false
查看全部 3 处证据
  • 实现 internal/agent/execute_one.go:272–312 Delivery 的验证、混合命令、inline interpreter 与 todo gates。
  • 实现 internal/agent/execute_one.go:315–433 Auto Guard、permission、MCP authorization 与 deny。
  • 实现 internal/agent/execute_one.go:435–550 write lease、parent claim、PreToolUse、preview 与 call context。
14
L1事实reasonix-tools-004

执行结果会同时写证据账本、hooks 和恢复观测

源码事实

finishToolExecution 在真实 target 执行后写入 model-visible receipt 和 proxy target receipt,记录 output bytes/todo 状态,触发 PostToolUse 或 failure hook、Auto Guard observation,再统一截断回模型的输出。

白话解释

工具成功不等于只把一段字符串塞回聊天:系统还保存“谁调用了谁、是否真的产出、哪个 todo 前进了、恢复守卫看到什么”。

对自研 Harness 的含义

审计与恢复需要结构化 receipt,而不是从自然语言最终答案里猜发生过什么。

关键源码 · 实现
internal/agent/execute_one.go · L552–L654
  552  // finishToolExecution performs the concrete Execute, records evidence, runs
  553  // post hooks and recovery observation, and truncates the model-facing result.
  554  func (a *Agent) finishToolExecution(ctx context.Context, plan *toolCallPlan) toolOutcome {
  555  	cctx := plan.cctx
  556  	runTool := plan.runTool
  557  	runArgs := plan.runArgs
  558  	call := plan.call
  559  	t := plan.tool
  560  	readOnly := plan.readOnly
  561  	permName := plan.permName
  562  	permArgs := plan.permArgs
  563  	evidenceName := plan.evidenceName
  564  	evidenceArgs := plan.evidenceArgs
  565  	mutates := plan.mutates
  566  	recoveryGen := plan.recoveryGen
  567  
  568  	var result string
  569  	var images []string
  570  	var err error
  571  	// A call that was authorized under reader classification carries that
  572  	// basis into dispatch: the MCP execution layer re-verifies it linearizably
  573  	// against server authorization and live safety metadata, and refuses to
  574  	// promote it into a writer lane if reclassification landed after the gate.
  575  	if readOnly && isInstalledMCPTool(runTool) && mcpServerAuthorized(runTool) && !mcpDestructiveHint(runTool) {
      … 69 lines omitted; exact range 552–654 …
  645  	a.recordRepeatSuccess(call, t)
  646  	// A foreground `task` sub-agent just finished — its result is the final answer.
  647  	// (A backgrounded one returns a "Started…" string and stops later in a job, so
  648  	// it doesn't fire here.) SubagentStop lets a hook react to delegated work.
  649  	if a.hooks != nil && call.Name == "task" && !isBackgroundTaskCall(call.Arguments) {
  650  		a.hooks.SubagentStop(ctx, result)
  651  	}
  652  	body, truncMsg := truncateToolOutput(result)
  653  	return toolOutcome{output: body, images: images, truncated: truncMsg != "", truncMsg: truncMsg, recoveryGeneration: recoveryGen}
  654  }
查看全部 2 处证据
  • 实现 internal/agent/execute_one.go:552–654 Execute、reader intent、evidence receipt、hooks、recovery observation 与截断。
  • 契约 internal/evidence/evidence.go:363–420 DeliveryCheckpoint 与 Ledger 的 receipt/lease 生命周期。
06
DIMENSION · EXECUTION-SANDBOX

执行环境与沙箱

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

15
L1事实reasonix-sandbox-001

Bash 沙箱是独立于 permission 的 OS enforcement 层

源码事实

sandbox.Spec 把 Mode、WriteRoots、ForbidReadRoots、Network、MinimalWrites 和 Shell 分开;macOS 使用 Seatbelt,Linux 使用 bubblewrap,Windows 当前没有 OS-level Bash sandbox。要求 enforce 但 backend 不可用时拒绝运行未包装命令,而不是静默降级。

白话解释

审批是“允许不允许做”,沙箱是“允许做也只能在哪些目录/网络里做”;没有真正的后端时,默认宁可不跑。

对自研 Harness 的含义

不要把 Plan mode 或 permission ask 当沙箱;OS backend availability 应有显式 fail-closed 结果。

关键源码 · 契约
internal/sandbox/sandbox.go · L1–L14
    1  // Package sandbox wraps a shell command in an OS-level jail so the model's
    2  // `bash` calls are confined: it may read almost freely but write only inside
    3  // the writable roots (workspace, configured extras, plus temp and toolchain
    4  // caches), with optional forbid-read roots, and reach the network only when
    5  // allowed. This is the *enforcement* layer beneath the permission rules
    6  // (*policy*): a permitted command still cannot escape the box.
    7  //
    8  // macOS uses Seatbelt via sandbox-exec and Linux uses bubblewrap when available.
    9  // Windows does not currently provide an OS-level bash sandbox and resolves the
   10  // product setting to off. When enforce is requested but no OS sandbox backend
   11  // is available, the bash tool fails closed instead of running the command
   12  // unwrapped.
   13  // Confining the in-process file-writer built-ins is handled separately, in
   14  // package tool/builtin.
查看全部 2 处证据
  • 契约 internal/sandbox/sandbox.go:1–14 policy 与 enforcement 分层及平台后端。
  • 实现 internal/sandbox/sandbox.go:21–83 Spec roots/network、enforce 与 unavailable fail-closed 文案。
16
L1事实reasonix-sandbox-002

沙箱逃逸是单次、显式、可审计的二次授权

源码事实

EscapeApprover 只描述在 OS sandbox 启动失败后重跑一次 unconfined command,nil approver 代表 fail closed;boot 根据 workspace root、extra dirs、forbid read roots、network override 和 WorkspaceOnly 组装 bashSpec,并在 enforce 后端不可用时发出告警。

白话解释

沙箱坏了不自动打开裸 shell;只有带 UI 的宿主明确同意某一条命令,才允许这一次越界。

对自研 Harness 的含义

需要把“沙箱失败恢复”和“普通命令审批”拆成两个不同 approval kind,便于后台运行时安全默认拒绝。

关键源码 · 契约
internal/sandbox/escape.go · L8–L46
    8  // EscapeRequest describes a one-shot request to rerun a shell command without
    9  // the OS sandbox after the platform sandbox failed to start.
   10  type EscapeRequest struct {
   11  	Command string
   12  	Args    json.RawMessage
   13  	Reason  string
   14  }
   15  
   16  // EscapeApprover asks the user whether one command may run unconfined after the
   17  // OS sandbox failed. Nil means fail closed.
   18  type EscapeApprover interface {
   19  	ApproveSandboxEscape(ctx context.Context, req EscapeRequest) (allow bool, reason string, err error)
   20  }
   21  
   22  // EscapeSessionChecker reports whether a sandbox escape has already been
   23  // approved for the current session without prompting the user again.
   24  type EscapeSessionChecker interface {
   25  	SandboxEscapeSessionAllowed(ctx context.Context, req EscapeRequest) bool
   26  }
   27  
   28  type escapeApproverContextKey struct{}
   29  
   30  // WithEscapeApprover stamps an interactive sandbox-escape approver onto a tool
   31  // execution context.
      … 5 lines omitted; exact range 8–46 …
   37  }
   38  
   39  // EscapeApproverFrom returns the sandbox-escape approver carried by ctx.
   40  func EscapeApproverFrom(ctx context.Context) (EscapeApprover, bool) {
   41  	if ctx == nil {
   42  		return nil, false
   43  	}
   44  	approver, ok := ctx.Value(escapeApproverContextKey{}).(EscapeApprover)
   45  	return approver, ok && approver != nil
   46  }
查看全部 2 处证据
  • 契约 internal/sandbox/escape.go:8–46 EscapeRequest、Approver 与 nil fail-closed。
  • 实现 internal/boot/boot.go:442–474 workspace roots、network、WorkspaceOnly、forbid roots 与 sandbox availability。
07
DIMENSION · PERMISSIONS-SECURITY

权限与安全

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

17
L1事实reasonix-security-001

权限 Policy 是纯函数,deny→ask→allow→fallback 且按每个路径判定

源码事实

permission.Policy 不做 I/O;read-only 默认 Allow,writer 使用 Mode fallback;Decision 优先级为 deny、session allow、ask、allow、fallback。多路径工具必须所有 subject 都安全,Bash 还会把 compound command 拆段,识别 eval/source/xargs/解释器 -c 等间接执行形状。

白话解释

权限规则可以单测;一个 move 同时碰两个路径时,只要一个路径被拒就拒绝整单;`bash -c`、`eval` 这类能藏第二条命令的写法不会被普通前缀规则轻易放过。

对自研 Harness 的含义

把 shell parser 和 policy decision 分离是比字符串黑名单更稳的权限基础,但仍需要持续覆盖新 shell 语法。

关键源码 · 契约
internal/permission/permission.go · L1–L5
    1  // Package permission decides, per tool call, whether to allow it, deny it, or
    2  // ask the user first. The core is a pure Policy (rule evaluation, no I/O); a
    3  // Gate wraps a Policy with an optional interactive Approver and is what the
    4  // agent consults at execute time. Keeping rule evaluation pure makes it
    5  // trivially testable and keeps the agent independent of how "ask" is resolved.
查看全部 3 处证据
  • 契约 internal/permission/permission.go:1–5 纯 Policy 与 Gate 分层。
  • 实现 internal/permission/permission.go:142–191 Decision、Policy 与 precedence。
  • 实现 internal/permission/bash_approval.go:17–67 Bash nested/indirect/expansion 分类。
18
L1事实reasonix-security-002

凭据防护同时覆盖子进程环境、敏感文件和诊断文本

源码事实

secrets 由 boot 从 user-global 配置一次性设置;ProcessEnv 永远移除 credential-store 注入的 key,用户可选择再过滤继承环境中的 api_key/token/password 等变量;Redact/RedactCredentials 覆盖 bearer、OpenAI/GitHub/Slack/AWS/JWT、Cookie 和 URL userinfo。

白话解释

项目目录里的配置不能偷偷改全局 secret 开关;工具子进程拿到的是清理后的环境,日志/错误边界还会把常见 token 形状打码。

对自研 Harness 的含义

凭据过滤必须在 composition root 和 subprocess boundary 做,而不是只在最终 UI 文本上补救。

关键源码 · 实现
internal/boot/boot.go · L200–L211
  200  	cfg, err := config.LoadForRoot(root)
  201  	if err != nil {
  202  		return nil, err
  203  	}
  204  	applyRuntimeAutoPricingCurrency(cfg, opts.AutoPricingCurrency)
  205  	// Arm the credential-protection layers from the user-global [secrets]
  206  	// section before any tool, hook, or plugin subprocess can spawn. Package
  207  	// globals are correct here because [secrets] is user-global (project
  208  	// reasonix.toml cannot override it), so concurrent workspaces agree.
  209  	secrets.SetFilterSubprocessEnv(cfg.Secrets.FilterSubprocessEnv)
  210  	secrets.SetProtectSensitiveFiles(cfg.Secrets.ProtectSensitiveFiles)
  211  	secrets.RegisterCredentialEnvKeys(cfg.CredentialEnvNames())
查看全部 3 处证据
  • 实现 internal/boot/boot.go:200–211 启动工具/插件前设置 secret filters 与 credential env keys。
  • 契约 internal/secrets/redact.go:48–84 user-global toggles 与 credential registration。
  • 实现 internal/secrets/redact.go:142–180 ProcessEnv 与多种 credential redaction。
08
DIMENSION · MCP-CONNECTORS

MCP 与连接器

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

19
L1事实reasonix-mcp-001

MCP 以统一 JSON-RPC 适配 stdio、Streamable HTTP 和 legacy SSE

源码事实

plugin 包把握手、tools/list、tools/call 统一在 transport interface 下;stdio 用受 ctx 绑定的子进程与专用 reader demux,HTTP transport 用 session id、单 origin redirect 规则、16 MiB body cap 和串行 request/response mutex。

白话解释

外部工具可以是本地进程,也可以是远程 HTTP/SSE,但 Agent 看到的都是同一个 Tool;连接、超时、取消和响应大小在连接层处理。

对自研 Harness 的含义

连接器治理应是独立 runtime 层,不能让每个工具自己实现 JSON-RPC、超时和取消。

关键源码 · 契约
internal/plugin/plugin.go · L1–L7
    1  // Package plugin is Reasonix's MCP client. It connects to external MCP servers and
    2  // adapts their tools to the tool.Tool interface, so the agent treats plugin
    3  // tools and built-ins uniformly. The wire protocol is JSON-RPC 2.0 in every
    4  // case; only the transport differs (stdio subprocess, Streamable HTTP, or the
    5  // legacy HTTP+SSE). A transport interface hides that difference so the MCP-level
    6  // logic — handshake, tools/list, tools/call — is written once.
    7  package plugin
查看全部 4 处证据
  • 契约 internal/plugin/plugin.go:1–7 MCP JSON-RPC 与三种 transport。
  • 实现 internal/plugin/transport_stdio.go:29–56 stdio reader、取消和 call mutex。
  • 实现 internal/plugin/transport_http.go:18–31 HTTP body cap、session header 与串行同 server calls。
  • 实现 internal/plugin/transport_http.go:45–68 same-origin redirect credential protection。
20
L1事实reasonix-mcp-002

MCP 项目服务器先做无 secret 的身份授权,再做 live safety 对账

源码事实

project launch identity 对 stdio 固定 executable path 与 SHA-256,对 HTTP 规范化 URL 但只保留 header 名并替换 userinfo/query credentials;cached tool 的 readOnly/destructive 事实与 live server 不一致时,当前调用在执行前阻断。MCP 默认 host mode 不进入 Bash sandbox,confined 只在显式内部模式使用。

白话解释

换了 MCP 二进制、目标地址或工具安全标记,不会因为旧 schema cache 还在就直接执行;同时默认插件是受信任 host 进程,不能误以为它自动继承 Agent shell 沙箱。

对自研 Harness 的含义

MCP 需要 provenance、exact launch grant、schema cache key 和 live reclassification 四件套;Host/Confined 必须在产品上清楚区分。

关键源码 · 实现
internal/plugin/security.go · L17–L66
   17  // projectLaunchIdentityDigest resolves a secret-free identity before a project
   18  // server is authorized. For stdio this pins the real executable path and file
   19  // content; for HTTP it normalizes the endpoint while retaining only header key
   20  // names. Installed and host-session servers never call this path.
   21  func projectLaunchIdentityDigest(ctx context.Context, s Spec) (string, error) {
   22  	identity, err := buildProjectLaunchIdentity(ctx, s)
   23  	if err != nil {
   24  		return "", err
   25  	}
   26  	return mcplaunch.ProjectLaunchIdentityDigest(identity)
   27  }
   28  
   29  func buildProjectLaunchIdentity(ctx context.Context, s Spec) (mcplaunch.ProjectLaunchIdentity, error) {
   30  	transport := strings.ToLower(strings.TrimSpace(s.Type))
   31  	if transport == "" {
   32  		transport = "stdio"
   33  	}
   34  	launchArgs := effectiveLaunchArgs(s)
   35  	if s.LauncherIdentityArgs != nil {
   36  		launchArgs = s.LauncherIdentityArgs
   37  	}
   38  	identity := mcplaunch.ProjectLaunchIdentity{
   39  		Server: s.Name, Transport: transport,
   40  		Dir: s.Dir, Args: append([]string(nil), launchArgs...),
      … 16 lines omitted; exact range 17–66 …
   57  		if err != nil {
   58  			return mcplaunch.ProjectLaunchIdentity{}, fmt.Errorf("hash MCP executable %q: %w", exe, err)
   59  		}
   60  	case "http", "streamable-http", "streamable_http":
   61  		identity.Transport = "http"
   62  		identity.URL = normalizeIdentityURL(s.URL)
   63  	default:
   64  		identity.URL = normalizeIdentityURL(s.URL)
   65  	}
   66  	return identity, nil
查看全部 4 处证据
  • 实现 internal/plugin/security.go:17–66 project launch identity、executable hash 与 normalized endpoint。
  • 实现 internal/plugin/security.go:127–175 credential-aware URL identity normalization。
  • 实现 internal/plugin/security.go:247–261 cached/live tool safety reconcile。
  • 契约 internal/plugin/plugin.go:37–50 host 与 confined process mode 的安全语义。
21
L1事实reasonix-mcp-003

MCP 启动是 catalog-first、cache-aware、可惰性连接的

源码事实

boot 对有缓存 schema 的服务器只注册 placeholder,不启动进程;cache miss 才做一次后台 catalog discovery,真正首次调用走 EnsureConnected。插件启动有默认并发上限 8、超时和 StartAvailable 的“单个失败不拖垮整场”路径;Economy profile 还把可选 MCP 放到 connect_tool_source。

白话解释

开一个项目不会立刻 fork 二十个 npm MCP 进程;先用缓存把工具菜单画出来,真正用到时再连,坏一个插件也不必让整场会话起不来。

对自研 Harness 的含义

扩展生态的启动成本应被当成调度问题,采用 schema cache、lazy process 和 bounded startup,而不是启动时全量阻塞。

关键源码 · 契约
internal/plugin/plugin.go · L243–L279
  243  // StartPolicy tunes batch plugin startup. The zero value disables every safeguard,
  244  // so most call sites should use the StartAll / StartAvailable wrappers, which
  245  // fill in production defaults.
  246  type StartPolicy struct {
  247  	// PerPluginTimeout caps how long a single plugin's handshake (start +
  248  	// initialize + listTools + listPrompts/Resources) may take. Zero disables.
  249  	// Exceeded plugins are recorded as failures and, when AbortOnError is set,
  250  	// tear down the whole batch with the timeout as the cause.
  251  	PerPluginTimeout time.Duration
  252  
  253  	// Concurrency caps how many handshakes run at once. Zero or negative means
  254  	// no cap (every plugin gets a goroutine immediately). A small cap prevents
  255  	// process storms / FD exhaustion when many MCP servers are configured.
  256  	Concurrency int
  257  
  258  	// AbortOnError makes any single failure tear down the partial batch and
  259  	// return an error (StartAll semantics). When false, failures are recorded
  260  	// on the host and other plugins keep going (StartAvailable semantics).
  261  	AbortOnError bool
  262  
  263  	// SkipPersistence disables RecordStartup / SaveCachedSchema side effects.
  264  	// Use for read-only live probes (capability diagnostics) that must not
  265  	// write MCP stats or schema cache files under Reasonix home.
  266  	SkipPersistence bool
      … 3 lines omitted; exact range 243–279 …
  270  // Eight is the standard "process storm" guardrail (Bazel's --jobs=auto, most LSP
  271  // managers) — large enough to mask single-plugin latency, small enough to spare
  272  // a workstation with 20+ configured MCP servers from fork-bombing itself.
  273  const defaultStartConcurrency = 8
  274  
  275  // defaultStartTimeout is the per-plugin budget used by StartAvailable. Five
  276  // seconds covers a healthy stdio MCP spawning under a slow npm/node loader; past
  277  // that, an interactive user is better served by recording the failure and moving
  278  // on than by stalling the whole session.
  279  const defaultStartTimeout = 5 * time.Second
查看全部 3 处证据
  • 契约 internal/plugin/plugin.go:243–279 StartPolicy 并发、超时、abort 与 persistence。
  • 实现 internal/boot/boot.go:500–563 MCP cache placeholder、Economy on-demand 与 startup specs。
  • 实现 internal/boot/boot.go:642–681 cache-hit 不启动、cache-miss discovery 与 lazy toolset。
09
DIMENSION · INSTRUCTIONS-SKILLS-PLUGINS

指令、Skills 与插件

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

22
L1事实reasonix-instruction-001

Skills 采用 metadata-first 索引,正文按需加载

源码事实

skill.IndexMaxChars 将固定索引限制为 4000 字符;system prompt 只放名称/描述和 subagent tag,skill body 通过 run_skill 或 /name 按需加载。内建 explore/research/review/security_review 使用只读工具,写入型 init/test 以内联主循环执行。

白话解释

模型开机只拿一张“有哪些 playbook”的目录,不把所有长说明书塞进 prompt;真正需要时才读正文,重研究工作可以放到隔离子 Agent。

对自研 Harness 的含义

Skills 是扩展协议,不应把所有脚本正文常驻上下文;metadata、allowed tools、RunAs 和 ReadOnly 应成为 manifest 的核心字段。

关键源码 · 契约
internal/skill/index.go · L10–L28
   10  // IndexMaxChars caps the pinned skills-index block so it can't bloat the
   11  // cache-stable system-prompt prefix; bodies never enter the prefix.
   12  const IndexMaxChars = 4000
   13  
   14  const missingDescPlaceholder = `(no description — frontmatter is missing a "description:" line; tell the user to add one)`
   15  
   16  // indexHeader introduces the skills block in the system prompt: the invocation
   17  // policy (mandatory for inline, judgment-based for subagent) and how to call one.
   18  const indexHeader = "# Skills — playbooks you can invoke\n\n" +
   19  	"One-liner index. Before non-trivial work, scan it: if an untagged (inline) skill is even plausibly relevant to the task, invoke it before continuing instead of pre-judging — loading one imperfect inline skill is cheap. Skills tagged `[🧬 subagent]` are the heavy path; reach for them only when the task genuinely needs context-heavy work, not on weak relevance. Each entry is a built-in or a user-authored playbook. Call `run_skill({ name: \"<skill-name>\", arguments: \"<task>\" })` — `name` is JUST the identifier (e.g. `\"explore\"`), NOT the `[🧬 subagent]` tag that follows it. Prefer the dedicated top-level tool when one exists for a built-in subagent skill. Entries tagged `[🧬 subagent]` spawn an isolated subagent — its tool calls and reasoning never enter your context, only its final answer does; use them for context-heavy work (deep exploration, multi-step research) where you only need the conclusion. Untagged skills are inlined: the body becomes a tool result you read and act on directly. The user can also invoke a skill via `/<name>`."
   20  
   21  const readOnlyIndexHeader = "# Skills — read-only playbooks you can invoke\n\n" +
   22  	"One-liner index for the narrow read-only skill surface. Call `read_only_skill({ name: \"<skill-name>\", arguments: \"<task>\" })` — `name` is JUST the identifier, NOT the `[🧬 subagent]` tag. Inline skills are loaded into context. Skills tagged `[🧬 subagent]` run in an isolated ephemeral read-only subagent with only read-only research tools and safe foreground bash; no writes, installers, memory mutation, continuation/fork, background jobs, or writer-capable delegation are available. Read-only nested delegation may be available until max_subagent_depth is reached."
   23  
   24  // IndexBlock renders the system/tool-result skills listing without attaching it
   25  // to a base prompt. Only names + descriptions (+ a subagent tag) are listed;
   26  // bodies load on demand via run_skill.
   27  func IndexBlock(skills []Skill) string {
   28  	return indexBlockWithHeader(indexHeader, skills)
查看全部 3 处证据
  • 契约 internal/skill/index.go:10–28 索引上限与调用策略。
  • 实现 internal/skill/index.go:37–90 metadata-only index、manual exclusion 与 clipped description。
  • 配置 internal/skill/builtins.go:281–354 builtin skills 的 RunAs、AllowedTools、ReadOnly 和 triggers。
23
L1事实reasonix-instruction-002

Memory 写入有封闭路径集合,背景事实与高权指令分开

源码事实

memory.Set 发现 REASONIX.md/AGENTS.md 层级文档、全局 guidance 和 auto-memory index;BackgroundBlock 明确把事实当作可能过期的背景而不是 standing instruction,WriteDoc 只允许写 canonical 或已发现 memory 文件。

白话解释

记忆里的“上次事实”不会自动压过当前用户指令;Agent 也不能借 memory tool 任意写到磁盘上任何路径。

对自研 Harness 的含义

组织级 memory 需要 authority、freshness 和 write allowlist 三种元数据,不能把一个大 MEMORY.md 当万能系统 prompt。

关键源码 · 契约
internal/memory/memory.go · L12–L24
   12  // Set is everything memory loaded for one session: the hierarchical docs and a
   13  // handle to the auto-memory store (whose index is captured at load time). It is
   14  // assembled once at boot and folded into the system prompt by Compose. CWD and
   15  // UserDir are retained so the controller can resolve quick-add targets without
   16  // re-deriving discovery context.
   17  type Set struct {
   18  	Docs                   []Source // REASONIX.md / AGENTS.md, ascending precedence
   19  	GlobalGuidance         []Memory // stable snapshot of global user/feedback bodies
   20  	Store                  Store    // auto-memory store (may be a zero/disabled Store)
   21  	Index                  string   // MEMORY.md contents at load time
   22  	CWD                    string   // project working dir used for discovery
   23  	UserDir                string   // user config root (may be "")
   24  	InstructionDiagnostics []instruction.Diagnostic
查看全部 3 处证据
  • 契约 internal/memory/memory.go:12–24 Docs、GlobalGuidance、Store 与 Index 的分层数据结构。
  • 实现 internal/memory/memory.go:131–155 背景事实的低权语义与 stale warning。
  • 实现 internal/memory/memory.go:94–128 recognized memory file write allowlist。
10
DIMENSION · SUBAGENTS-COLLABORATION

子 Agent 与协作

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

24
L1事实reasonix-collab-001

Planner 与 Executor 是两份独立 session,显式审批路线 fail-closed

源码事实

Coordinator 用 planner provider/session 做只读研究和结构化 plan,再把 plan handoff 给完整工具 Executor;两个 session 的 prefix 不互相污染。普通 plan-and-execute 在 planner 失败时可降级 executor-only,但 plan-only 或 plan-for-approval 缺 plan 时直接报错,避免 planner outage 变成未经批准的副作用。

白话解释

规划模型负责读和写计划,执行模型负责改东西;规划挂掉时,普通请求可继续,但“必须先批准”的路线不会偷偷绕过去。

对自研 Harness 的含义

双模型不是简单串两个 API,而是要有独立上下文、路由决策和不同的失败语义。

关键源码 · Prompt
internal/agent/coordinator.go · L36–L81
   36  // DefaultPlannerPrompt steers the planner toward concise plans, not execution.
   37  const DefaultPlannerPrompt = `You are the planner in a two-model coding agent.
   38  Given a task, produce a concise, ordered plan for the executor model to carry out.
   39  Use the read-only tools available to you when the task needs context from the
   40  workspace, user rules, or docs; keep that research targeted and stop once you
   41  have enough evidence. Do not write full implementations or attempt side effects.
   42  Do not ask the user how to trigger the executor and do not say you are waiting
   43  for the executor. Output executor-ready instructions: what to do, which files or
   44  commands are relevant, expected blockers, and key decisions. Keep it short and
   45  actionable.
   46  
   47  A host-authored <planner-turn> block at the end of the user turn selects the
   48  planning depth. For depth=light, return a compact objective, 1-4 ordered steps,
   49  likely touchpoints, and the main verification; omit empty boilerplate sections.
   50  For depth=full, inspect enough evidence to distinguish verified touchpoints from
   51  candidate touchpoints, then include goal/non-goals when useful, ordered steps,
   52  risks or blockers, concrete acceptance criteria, command-level verification, and
   53  rollback only when the change is risky or difficult to reverse. Label assumptions
   54  instead of presenting inferred paths or commands as verified facts.
   55  
   56  If execution must stop for explicit user approval of the plan, end the plan with
   57  a final line containing exactly [planner_requires_approval]. If execution needs
   58  a user-owned decision or missing user-provided value before it can be safe, do
   59  not ask in prose; include one structured block:
      … 12 lines omitted; exact range 36–81 …
   72  When you need external real data and the capability route does not name a
   73  specific tool, call use_capability(action="list") first to see configured MCP
   74  servers, then inspect or call a non-destructive capability. If a capability is
   75  destructive, do not treat that as missing configuration or an unavailable MCP:
   76  write the operation into the plan for the executor instead.
   77  
   78  If your research shows the task needs no changes and no actions at all (already
   79  implemented, already resolved), explain that briefly and end your reply with a
   80  final line containing exactly [no_changes]. Never emit that marker when any
   81  work, verification, or follow-up remains.`
查看全部 3 处证据
  • Prompt internal/agent/coordinator.go:36–81 Planner 只读、结构化 approval/ask/no_changes 契约。
  • 实现 internal/agent/coordinator.go:117–190 独立 planner/executor sessions 与 cache stability。
  • 实现 internal/agent/coordinator.go:340–399 route、planner research budget 与 fail-open/fail-closed 分支。
25
L1事实reasonix-collab-002

parallel_tasks 只允许读操作,最多 64 个并发请求并受 scheduler/depth 限制

源码事实

parallel_tasks 工具被标记 ReadOnly/PlanModeSafe,拒绝空、单个或超过 64 个任务;每个 child 有独立 session、read-only registry、child depth、max steps、provider profile 和 nested event sink,并经 scheduler 获取读槽位,取消时 drain/wait 后聚合结果。

白话解释

它能同时派 64 个“查代码/查资料”的小工,但这些小工不能写文件,且仍要遵守并发和嵌套深度额度。

对自研 Harness 的含义

并发 Agent 必须把任务输入上限、工具 allowlist、读写属性、预算、取消和结果聚合一起作为资源合同。

关键源码 · 契约
internal/agent/parallel_tasks.go · L17–L65
   17  // ParallelTasksTool dispatches multiple read-only sub-agent tasks concurrently
   18  // and collects all results. Each sub-task runs as a foreground sub-agent in its
   19  // own goroutine, emitting nested events so the frontend renders independent
   20  // cards for each sub-task.
   21  type ParallelTasksTool struct {
   22  	taskTool *TaskTool
   23  	reg      *tool.Registry
   24  }
   25  
   26  // NewParallelTasksTool creates a parallel dispatch tool that reuses the given
   27  // TaskTool's sub-agent infrastructure.
   28  func NewParallelTasksTool(taskTool *TaskTool, reg *tool.Registry) *ParallelTasksTool {
   29  	return &ParallelTasksTool{taskTool: taskTool, reg: reg}
   30  }
   31  
   32  func (p *ParallelTasksTool) Name() string { return "parallel_tasks" }
   33  
   34  func (p *ParallelTasksTool) Description() string {
   35  	return "Dispatch multiple read-only sub-agent tasks concurrently and collect their results. Each task runs in its own read-only sub-agent in parallel. Blocks until all complete."
   36  }
   37  
   38  func (p *ParallelTasksTool) Schema() json.RawMessage {
   39  	return json.RawMessage(`{
   40  "type":"object",
      … 15 lines omitted; exact range 17–65 …
   56      }
   57    }
   58  },
   59  "required":["tasks"]
   60  }`)
   61  }
   62  
   63  func (p *ParallelTasksTool) ReadOnly() bool { return true }
   64  
   65  func (p *ParallelTasksTool) PlanModeSafe() bool { return true }
查看全部 4 处证据
  • 契约 internal/agent/parallel_tasks.go:17–65 parallel_tasks 的只读/Plan-safe 定义和 schema。
  • 实现 internal/agent/parallel_tasks.go:78–115 64 task cap、unknown-field reject 与输入校验。
  • 实现 internal/agent/parallel_tasks.go:163–239 scheduler slot、depth、read-only child、nested sink。
  • 实现 internal/agent/parallel_tasks.go:242–307 cancel drain、WaitGroup 与聚合输出。
26
L1事实reasonix-collab-003

子 Agent 复用父工具基座,但单独限制模型、深度、并发、沙箱和 transcript

源码事实

boot 在所有内建/MCP 工具装配后创建 TaskTool,子 Agent 继承 parent registry 但剔除 task,使用 headless gate、独立 provider resolver、transcripts、MaxSubagentDepth、scheduler、workspace lease 和 bash sandbox contract;另外显式注册 parallel_tasks、fleet 与 read_only_task。

白话解释

子 Agent 不是复制一份全局进程;它们共享已经治理过的工具目录,却有自己的会话、额度和后台审批边界。

对自研 Harness 的含义

多 Agent 设计要把“共享能力”与“共享状态”分开,避免 child 绕过 parent 的安全设置或无限递归。

关键源码 · 实现
internal/boot/boot.go · L756–L779
  756  	// The `task` tool spawns sub-agents that reuse the parent's provider and
  757  	// tool registry. Wired here after the built-ins / plugins are loaded so
  758  	// sub-agents inherit the full tool set (minus `task` itself, to keep
  759  	// nesting out of the picture). It registers into the same reg the
  760  	// executor uses, so the model surfaces it like any other tool.
  761  	resolveSubagentProvider := func(modelRef, effort string) (provider.Provider, *provider.Pricing, int, error) {
  762  		me := *entry
  763  		selectedRef := modelRefFromEntry(entry)
  764  		if strings.TrimSpace(modelRef) != "" {
  765  			if resolved, ok := cfg.ResolveModel(modelRef); ok {
  766  				me = *resolved
  767  				selectedRef = modelRefFromEntry(resolved)
  768  			} else if opts.ProviderResolver != nil {
  769  				me = *syntheticEntryFromResolver(opts.ProviderResolver, modelRef)
  770  				selectedRef = modelRef
  771  			} else {
  772  				return nil, nil, 0, fmt.Errorf("unknown model %q", modelRef)
  773  			}
  774  		}
  775  		var effortOverride *string
  776  		if strings.TrimSpace(effort) != "" {
  777  			normalized, err := config.NormalizeEffort(&me, effort)
  778  			if err != nil {
  779  				if opts.ProviderResolver == nil {
查看全部 3 处证据
  • 实现 internal/boot/boot.go:756–779 TaskTool 继承 registry、gate 与 provider resolver。
  • 实现 internal/boot/boot.go:799–879 subagent model/effort、depth、scheduler、transcripts、lease/sandbox。
  • 实现 internal/boot/boot.go:880–909 task、parallel_tasks、fleet、read_only_task 注册顺序。
11
DIMENSION · PERSISTENCE-OBSERVABILITY

持久化与观测

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

27
L1事实reasonix-persist-001

会话持久化是带 revision/CAS 的 append-only event log

源码事实

Session.Save/SaveSnapshot/SaveRewrite 区分普通追加、快照和显式历史重写;写入前获取进程内与跨进程锁,探测 native event log、修复 torn tail,按 revision/digest 检查 stale prefix 或 diverged transcript,必要时生成 recovery branch 而不是覆盖更新文件。

白话解释

两个进程同时写同一会话时,旧进程不能把新内容抹掉;坏掉的 JSONL 尾巴先修,冲突会留下可恢复分支。

对自研 Harness 的含义

长任务恢复需要 append-only log、版本号、锁和 conflict branch,单纯覆盖一个 chat.json 不够。

关键源码 · 契约
internal/agent/save.go · L26–L74
   26  const (
   27  	cleanupPendingExt             = ".cleanup-pending.json"
   28  	maxRecoveryParentStemBytes    = 80
   29  	sessionLockSidecarSuffix      = ".jsonl.lock"
   30  	sessionLeaseLockSidecarSuffix = ".jsonl.lease.lock"
   31  	sessionLeaseInfoSidecarSuffix = ".jsonl.lease.json"
   32  	guardianSidecarSuffix         = ".guardian.jsonl"
   33  	// nameMaxBytes is the single-component filename limit shared by the
   34  	// filesystems Reasonix targets (APFS, ext4, NTFS all cap at 255).
   35  	nameMaxBytes = 255
   36  	// maxSessionBasenameBytes bounds transcript basenames that reconciliation
   37  	// leaves in place. Sidecars append up to ~16 bytes to the transcript name
   38  	// or its stem (".lease.lock", ".cleanup-pending.json", ".guardian.jsonl"),
   39  	// so 224 keeps every sidecar comfortably under nameMaxBytes with headroom
   40  	// for future suffixes. Names past this bound come from the pre-bounded
   41  	// recovery cascade and get renamed by reconcileOverlongSessionFilenames.
   42  	maxSessionBasenameBytes = 224
   43  )
   44  
   45  var (
   46  	sessionSaveLocks sync.Map
   47  	// sessionFileLockWait bounds cross-process save-lock acquisition. Session
   48  	// leases normally prevent competing writers, but CLI/legacy writers and a
   49  	// stalled process can still hold the compatibility .lock file. Navigation
      … 15 lines omitted; exact range 26–74 …
   65  	// forking further multiplies session files without converging (#5993).
   66  	ErrSessionRecoveryDepthExceeded = errors.New("session recovery chain depth exceeded")
   67  	sessionWriterID                 = newSessionWriterID()
   68  )
   69  
   70  // SessionRecoveryMaxDepth bounds nested recovery forks: a normal session may
   71  // fork a recovery branch (depth 1), which may itself fork twice more under
   72  // genuine repeated incidents; past that the caller should stop forking and
   73  // write onto the branch it already owns.
   74  const SessionRecoveryMaxDepth = 3
查看全部 3 处证据
  • 契约 internal/agent/save.go:26–74 sidecar、锁、recovery depth cap 与冲突 sentinel。
  • 实现 internal/agent/save.go:183–240 Save modes、locks、snapshot capture 与 event log probe。
  • 实现 internal/agent/save.go:294–329 append/compact event log、revision/index 与 durable save。
28
L1事实reasonix-persist-002

前端收到的是稳定 typed event wire,不是拼接日志

源码事实

eventwire.ToWire 把 ToolDispatch/Result/Progress、Usage、Approval、Ask、Compaction、Guardian、TurnDone、Retrying 映射为稳定 JSON;Usage 包含 prompt/completion/cache hit/miss/reasoning/cost,Tool 包含 resolved name、capability id、readOnly、truncated、duration 和 diff。

白话解释

桌面、TUI、HTTP 都能用同一套事件渲染工具卡、审批卡、压缩卡和成本仪表,不必从人类日志里猜状态。

对自研 Harness 的含义

观测 schema 应和执行状态机一起设计,尤其要暴露 resolved target、cache diagnostics、approval kind 和 retry outcome。

关键源码 · 契约
internal/eventwire/wire.go · L9–L31
    9  // Event is the JSON-friendly form shared by event frontends.
   10  // externalizable:"true" marks large string payloads the Remote protocol may
   11  // offload via content refs without changing provider-visible semantics.
   12  type Event struct {
   13  	Kind            string           `json:"kind"`
   14  	Text            string           `json:"text,omitempty" externalizable:"true"`
   15  	Detail          string           `json:"detail,omitempty" externalizable:"true"`
   16  	Code            string           `json:"code,omitempty"`
   17  	Reasoning       string           `json:"reasoning,omitempty" externalizable:"true"`
   18  	MemoryCitations []MemoryCitation `json:"memoryCitations,omitempty"`
   19  	Level           string           `json:"level,omitempty"`
   20  	Tool            *Tool            `json:"tool,omitempty"`
   21  	Usage           *Usage           `json:"usage,omitempty"`
   22  	Approval        *Approval        `json:"approval,omitempty"`
   23  	Ask             *Ask             `json:"ask,omitempty"`
   24  	Compaction      *Compaction      `json:"compaction,omitempty"`
   25  	Guardian        *Guardian        `json:"guardian,omitempty"`
   26  	Err             string           `json:"err,omitempty" externalizable:"true"`
   27  	Outcome         string           `json:"outcome,omitempty"`
   28  	Readiness       *FinalReadiness  `json:"readiness,omitempty"`
   29  	RetryAttempt    int              `json:"retryAttempt,omitempty"`
   30  	RetryMax        int              `json:"retryMax,omitempty"`
   31  }
查看全部 3 处证据
  • 契约 internal/eventwire/wire.go:9–31 共享 JSON Event 结构。
  • 实现 internal/eventwire/wire.go:33–125 tool/usage/approval/compaction/guardian/turn 映射。
  • 契约 internal/eventwire/wire.go:197–249 Tool 和 Usage 观测字段。
29
L1事实reasonix-persist-003

Evidence Ledger 把交付验收从文本变成可检查事实

源码事实

evidence.Ledger 保存本轮 receipts 和 background leases;DeliveryCheckpoint 只保留 criteria/work/mutation/pendingMutation 等压缩安全字段,后台任务证据在 turn 成功通过 delivery gate 后才 commit,失败 run 会让证据重新可收集。

白话解释

后台任务说“我改好了”不会自动算完成;只有主任务成功收下回执,证据才从临时状态转为已提交。

对自研 Harness 的含义

构建可恢复 Agent 时,副作用证据应有 provisional/committed 两阶段,避免失败 turn 把半成品当成功历史。

关键源码 · 契约
internal/evidence/evidence.go · L348–L373
  348  	// OutputBytes is the host-observed length of the tool's (redacted, trimmed)
  349  	// output. Content-evidence checks require it to be non-zero so a command
  350  	// that printed nothing (head -n 0, >/dev/null) can never count as reading.
  351  	OutputBytes int `json:"output_bytes,omitempty"`
  352  }
  353  
  354  // BackgroundLease identifies a background job whose evidence was provisionally
  355  // merged into the current turn's ledger. The host commits these leases only
  356  // after the turn passes its delivery gates, so a failed turn leaves the job's
  357  // evidence collectable again.
  358  type BackgroundLease struct {
  359  	Session string
  360  	JobID   string
  361  }
  362  
  363  // DeliveryCheckpoint is the compact, persistence-safe state carried across
  364  // runs of one host-owned Goal. It intentionally stores no raw tool arguments or
  365  // output. PendingMutation means a previously observed change still needs fresh
  366  // verification, review, and sign-off before the Goal can finalize.
  367  type DeliveryCheckpoint struct {
  368  	ScopeID             string `json:"scopeID,omitempty"`
  369  	CriteriaEstablished bool   `json:"criteriaEstablished,omitempty"`
  370  	WorkObserved        bool   `json:"workObserved,omitempty"`
  371  	MutationObserved    bool   `json:"mutationObserved,omitempty"`
  372  	PendingMutation     bool   `json:"pendingMutation,omitempty"`
  373  }
查看全部 2 处证据
  • 契约 internal/evidence/evidence.go:348–373 BackgroundLease 与 DeliveryCheckpoint 字段。
  • 实现 internal/evidence/evidence.go:375–420 Ledger receipts/background lease reset 与去重。
12
DIMENSION · TESTS-BENCHMARKS-MATURITY

测试、基准与成熟度

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

30
L3事实reasonix-tests-001

测试覆盖了真实 loop 的配对、取消、断流恢复和 compaction 熔断

源码事实

loop_e2e_test 驱动实际 Agent.Run 验证空 ID 多工具结果不折叠、取消后 session 无 dangling tool、partial stream 不把 LocalOnly 文本泄漏给 provider,并验证断流重试次数;compact_loop_e2e_test 验证窗口过小时总 compaction 不超过 2 次且进入 paused notice。

白话解释

这些不是只测 helper 的单元测试,而是把模型流、工具、session 和恢复串起来,专门覆盖最容易把长任务搞坏的边界。

对自研 Harness 的含义

评测自研 Harness 时要把“崩溃/断流/重试后下一次请求是否可发送”列为一等场景,而不是只测最终字符串。

关键源码 · 测试
internal/agent/loop_e2e_test.go · L68–L102
   68  // TestRunMultiToolRoundEmptyIDsSurvivePairing drives the real loop through a turn
   69  // that fans out two tool calls carrying no id (a gateway that streams by index),
   70  // then asserts both results still pair back after SanitizeToolPairing — the repair
   71  // that runs on every send. Keying on tool_call_id alone collapsed them into one,
   72  // dropping a result from the model's context on the very next turn.
   73  func TestRunMultiToolRoundEmptyIDsSurvivePairing(t *testing.T) {
   74  	mp := testutil.NewMock("m",
   75  		testutil.Turn{ToolCalls: []provider.ToolCall{
   76  			{ID: "", Name: "echo", Arguments: `{"text":"alpha"}`},
   77  			{ID: "", Name: "echo", Arguments: `{"text":"beta"}`},
   78  		}},
   79  		testutil.Turn{Text: "done"},
   80  	)
   81  	a := New(mp, echoRegistry(), NewSession(""), Options{}, event.Discard)
   82  	if err := a.Run(context.Background(), "go"); err != nil {
   83  		t.Fatalf("Run: %v", err)
   84  	}
   85  
   86  	repaired := provider.SanitizeToolPairing(a.Session().Messages)
   87  	var results []string
   88  	for _, m := range repaired {
   89  		if m.Role == provider.RoleTool {
   90  			results = append(results, m.Content)
   91  		}
      … 1 lines omitted; exact range 68–102 …
   93  	if len(results) != 2 {
   94  		t.Fatalf("want 2 tool results after pairing, got %d: %v", len(results), results)
   95  	}
   96  	if results[0] == results[1] {
   97  		t.Fatalf("both results collapsed to %q — one was lost from the model's context", results[0])
   98  	}
   99  	if !strings.Contains(results[0], "alpha") || !strings.Contains(results[1], "beta") {
  100  		t.Errorf("results lost their identity: %v", results)
  101  	}
  102  }
查看全部 3 处证据
  • 测试 internal/agent/loop_e2e_test.go:68–102 空 ID 多工具调用的真实 loop pairing。
  • 测试 internal/agent/loop_e2e_test.go:128–202 取消、LocalOnly、stream recovery 与 retry cap。
  • 测试 internal/agent/compact_loop_e2e_test.go:137–180 stuck compaction pause 与健康窗口不循环。
31
L1事实reasonix-tests-002

仓库有面向真实 DeepSeek cache 的 context maintenance 基准,但它需要外部 API key

源码事实

benchmarks/context-maintenance-e2e 使用 DeepSeek v4 flash 与官方 API,构造大量工具结果,比较 cold restart 的 control/pruned 两臂 cache miss tokens,并把 prune 前后 usage 写成 JSON 结果;许可证是 MIT。

白话解释

项目不只在本地 mock 上谈缓存,仓库还提供真实 API 的 A/B 脚本;但它需要密钥和网络,不能把基准当成离线 CI 事实。

对自研 Harness 的含义

缓存优化要同时有离线 deterministic tests 和受控真实 provider benchmark,报告结果时要标注外部依赖。

边界
  • 基准运行需要 DEEPSEEK_API_KEY 和网络;本页没有把远端 API 结果冒充源码内置保证。
关键源码 · 测试
benchmarks/context-maintenance-e2e/main.go · L1–L41
    1  // Drives the context-maintenance E2E scenarios against the real DeepSeek API:
    2  // seed → (idle past cache TTL) → resume A/B-compares cold-restart miss tokens with and without pruning.
    3  package main
    4  
    5  import (
    6  	"context"
    7  	"encoding/json"
    8  	"flag"
    9  	"fmt"
   10  	"os"
   11  	"path/filepath"
   12  	"strings"
   13  	"time"
   14  
   15  	"reasonix/internal/agent"
   16  	"reasonix/internal/event"
   17  	"reasonix/internal/provider"
   18  	_ "reasonix/internal/provider/openai"
   19  	"reasonix/internal/tool"
   20  	"reasonix/internal/tool/builtin"
   21  )
   22  
   23  const (
   24  	model      = "deepseek-v4-flash"
      … 7 lines omitted; exact range 1–41 …
   32  	if key == "" {
   33  		fmt.Fprintln(os.Stderr, "DEEPSEEK_API_KEY not set")
   34  		os.Exit(1)
   35  	}
   36  	p, err := provider.New("openai", provider.Config{Name: "e2e", BaseURL: baseURL, Model: model, APIKey: key})
   37  	if err != nil {
   38  		fmt.Fprintln(os.Stderr, err)
   39  		os.Exit(1)
   40  	}
   41  	return p
查看全部 3 处证据
  • 测试 benchmarks/context-maintenance-e2e/main.go:1–41 DeepSeek API、模型与 key 前置条件。
  • 测试 benchmarks/context-maintenance-e2e/main.go:126–175 resume、prune/control A/B 与 cache miss reduction 输出。
  • 配置 LICENSE:1–20 MIT license。
APPENDIX · SOURCE INDEX

本报告引用过的实现文件

这是一份代码阅读索引,不是仓库文件总表。机器候选扫描覆盖整个仓库;进入结论的文件必须经人工沿调用链复核。

  1. 01internal/boot/boot.goL1–8, 442–474, 200–211, 500–563, 642–681, 756–779, 799–879, 880–909
  2. 02internal/control/controller.goL1–10, 60–76, 298–308, 698–820
  3. 03internal/agent/agent.goL33–62, 1060–1064, 1203–1213
  4. 04internal/provider/provider.goL40–75, 194–221, 179–192, 223–251, 364–443
  5. 05internal/provider/openai/openai.goL279–316, 426–471, 473–611, 627–679
  6. 06internal/provider/anthropic/anthropic.goL1–20, 129–185
  7. 07internal/agent/compact.goL19–36, 82–147, 49–80, 194–203, 216–297
  8. 08internal/memory/memory.goL12–53, 94–128, 175–188, 12–24, 131–155, 94–128
  9. 09internal/agent/execute_one.goL20–80, 83–150, 153–269, 396–431, 272–312, 315–433, 435–550, 552–654
  10. 10internal/evidence/evidence.goL363–420, 348–373, 375–420
  11. 11internal/sandbox/sandbox.goL1–14, 21–83
  12. 12internal/sandbox/escape.goL8–46
  13. 13internal/permission/permission.goL1–5, 142–191
  14. 14internal/permission/bash_approval.goL17–67
  15. 15internal/secrets/redact.goL48–84, 142–180
  16. 16internal/plugin/plugin.goL1–7, 37–50, 243–279
  17. 17internal/plugin/transport_stdio.goL29–56
  18. 18internal/plugin/transport_http.goL18–31, 45–68
  19. 19internal/plugin/security.goL17–66, 127–175, 247–261
  20. 20internal/skill/index.goL10–28, 37–90
  21. 21internal/skill/builtins.goL281–354
  22. 22internal/agent/coordinator.goL36–81, 117–190, 340–399
  23. 23internal/agent/parallel_tasks.goL17–65, 78–115, 163–239, 242–307
  24. 24internal/agent/save.goL26–74, 183–240, 294–329
  25. 25internal/eventwire/wire.goL9–31, 33–125, 197–249
  26. 26internal/agent/loop_e2e_test.goL68–102, 128–202
  27. 27internal/agent/compact_loop_e2e_test.goL137–180
  28. 28benchmarks/context-maintenance-e2e/main.goL1–41, 126–175
  29. 29LICENSEL1–20