# DeepSeek-Reasonix source analysis: how “cost anxiety” grew into an “architectural constitution”

> **Subject**: [esengine/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix) (product name **Reasonix**)
> **Baseline commit**: `627b051` (`Merge pull request #7368 … remote-workbench-reconnect-hardening`, default branch **`main-v2`**)
> **License**: MIT (`LICENSE`)
> **Code size (baseline)**: **1686** `.go` files; `internal/` non-test **216,079** lines; whole repo including tests **553,005** lines
> **Product in one sentence**: a **DeepSeek-native** terminal coding agent—a **config- and plugin-driven extremely thin harness**, a single static Go binary (`CGO_ENABLED=0`), **tuned around DeepSeek’s automatic prefix cache**, so long-session token cost stays controllable.
> **Audience**: product managers and engineers who have read at least one of this series’ Claude Code / Open Design / Hermes / Codex / OpenAI4S analyses.
> **Rewrite note**: this edition is rearranged in a five-beat **motivation → constraint → rejected alternatives → choice → cost**. First answer “why the developers built it, and what the spine in their head was,” then go into the code; every key design tries to say “what would happen if we didn’t.”
> **Local baseline path**: `参考项目/DeepSeek-Reasonix @ 627b051`

---

## Contents

**Part I · Origin: why the developers built this**
1. [Pain and insight: cost anxiety meets an already-there saving mechanism](#ch1)
2. [Product position and motive evidence: the original words in README / REASONIX / SPEC](#ch2)
3. [Repo map and stack: numeric shape and dependency direction](#ch3)

**Part II · Mind map: how one constraint yields ten designs**
4. [Developer mind map: from “the prefix must stay stable” to ten designs](#ch4)
5. [Cache-first constitution: stable prefix vs turn tail](#ch5)
6. [Why dual models must split session](#ch6)

**Part III · Implementation: from constraint to code**
7. [Main loop: Agent.Run → runToolLoop](#ch7)
8. [Compose: drive volatile content into the turn tail](#ch8)
9. [Compaction three rungs: soft → snip → summary (a cache reset with a posted price)](#ch9)
10. [Tool ABI: builtin + MCP + use_capability fixed proxy](#ch10)
11. [Permissions, sandbox, workspace fence: application rules × OS fence](#ch11)
12. [Checkpoint / Delivery / Goal: delivery evidence and a completion protocol](#ch12)
13. [control.Controller: one face behind every frontend](#ch13)
14. [Memory, instructions, extensions, desktop/ACP](#ch14)

**Part IV · Taste and bounds**
15. [Ten decisions, five-beat recap](#ch15)
16. [Cross-cut (coding-agent table)](#ch16)
17. [Honest bounds](#ch17)
18. [Source-tour index and local reproduction](#ch18)

---

# Part I · Origin: why the developers built this

<h2 id="ch1">Chapter 1 · Pain and insight: cost anxiety meets an already-there saving mechanism</h2>

### 1.1 Answer “why” first

Before reading Reasonix’s source, read its README. The Chinese README does not open with “how smart our Agent is”; it pins the product position on cost (`README.zh-CN.md:42-43`):

> A DeepSeek-native AI coding agent for the terminal.
> A config- and plugin-driven extremely thin harness—a single static Go binary, tuned around DeepSeek’s prefix cache, so even long sessions keep token cost down.

This is not marketing copy. It is the starting point of the whole architecture:

1. **Pain**: in a long session, token cost grows linearly with turns; DeepSeek’s pricing makes “keep chatting” expensive.
2. **Already-there mechanism**: DeepSeek automatically prefix-caches—as long as the request-prefix bytes stay stable, the hit portion is discounted.
3. **Insight**: the key to saving money is not “call the model less,” but “**keep hitting the prefix**.” Cache is thereby promoted from an “optimization” to an “architectural constraint.”

```mermaid
flowchart LR
    P["Pain: long-session token cost is high"] --> I["Insight: DeepSeek auto prefix cache<br/>stable prefix = hit = save money"]
    I --> C["Constraint: system prefix must be byte-stable"]
    C --> D["Corollary: volatile content out to turn tail<br/>stable tool schema · compaction priced in the open · miss diagnosable"]
```

### 1.2 Three verifiable engineering claims

| Claim | What it looks like in code |
|---|---|
| **DeepSeek-native** | Provider layer keeps `reasoning_content` round-trip; on missing reasoning, silent retry (`observeMissingToolCallReasoning` at `agent.go:1388-1394`) |
| **Cache-first** | System prefix (base + tools + memory) is **byte-stable across turns**; volatile content rides `control.Compose`’s turn tail (`REASONIX.md:14-16`, `control/input.go:181-183`) |
| **Thin harness + single binary** | `docs/SPEC.md` contract: the core only knows interfaces; `CGO_ENABLED=0`; capability comes from config / plugin / `init()` self-registration |

### 1.3 This constraint was written as a “constitution,” not a comment

The repo’s own standing instructions (`REASONIX.md:14-16`):

```text
- 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`).
```

Even the PR template forces `Cache-impact` / `Cache-guard` metadata (`REASONIX.md:64-71`).
In this series only Hermes’s “prefix cache is sacred” is in the same family; Reasonix’s difference is welding it into **Go runtime diagnostics + CI scripts**, so “do not break the cache” becomes an executable collaboration discipline.

---

<h2 id="ch2">Chapter 2 · Product position and motive evidence: the original words in README / REASONIX / SPEC</h2>

### 2.1 Motive-evidence table

| Evidence | Source | What it proves |
|---|---|---|
| “tuned around DeepSeek’s prefix cache, so even long sessions keep token cost down” | `README.zh-CN.md:42-43` | Cost is the product starting point |
| “must stay byte-stable… Never mutate it mid-session” | `REASONIX.md:14-16` | The constraint is written in constitutional voice |
| “switching models inside one shared conversation would break the prefix and tank cache hits, so we don't” | `docs/SPEC.md:238-240` | Even dual models are reverse-derived by the constraint into independent sessions |
| Compaction is a “cache-reset point”; cache hit rate is a “key observability signal” | `docs/SPEC.md:328-329` | Compaction is a cache reset with a posted price |
| PRs force `Cache-impact` / `Cache-guard` | `REASONIX.md:64-71` | The constraint is welded into the collaboration process |
| “Memory added mid-session rides the turn (never the cached system prefix)” | `internal/control/input.go:181-183` | Every implementation point explains why |
| Economy tier is for “cost-sensitive tasks” | `docs/COLLABORATION_MODES.zh-CN.md:86,136` | Product tiers are cut by cost |

### 2.2 Product-manager view: what experience it is selling

| Selling point | Engineering landing |
|---|---|
| Long sessions use fewer tokens | Stable prefix → DeepSeek automatic prefix cache; `PrefixShape` / `CompareShape` explain cache miss (`agent/cache_shape.go`) |
| Usable after install | Single static binary; npm package `reasonix`; `reasonix.example.toml` |
| Many frontends, one set of behavior | `control.Controller` behind TUI / HTTP-SSE / Wails desktop / ACP (`REASONIX.md:11-13`) |
| Verifiable delivery | Delivery Profile: `complete_step` evidence sign-off + readiness gate (`docs/GOAL_ENFORCEMENT.zh-CN.md`) |
| Collaboration modes switchable | Economy / Balanced / Delivery; optional `planner_model` dual model (`docs/COLLABORATION_MODES.zh-CN.md`) |

> 🧠 **One sentence**: others are optimizing “how smart the tool call is”; Reasonix is optimizing “**in this same session, can the prefix keep hitting cache**”—and treating that as a product contract that cannot be breached.

---

<h2 id="ch3">Chapter 3 · Repo map and stack: numeric shape and dependency direction</h2>

### 3.1 Digitized project shape (baseline 627b051)

| Metric | Magnitude |
|---|---|
| Go source files | **1686** `.go` |
| `internal/` non-test | **216,079** lines |
| Whole repo including tests | **553,005** lines |
| Giant-file hearts | `control/controller.go` 6,560 · `cli/chat_tui.go` 5,154 · `agent/agent.go` 4,362 |
| Builtin tool contract | `docs/TOOL_CONTRACT.md` (compile-time builtins + full boot surface, guarded by tests) |

Layout contract given by `docs/SPEC.md`:

```text
cmd/reasonix/main.go          # entry; blank-import providers + builtin tools
internal/
  cli/                        # subcommands, assembly, exit codes
  control/                    # transport-agnostic Controller (behind every frontend)
  agent/                      # Session + harness loop + compact + cache shape
  provider/                   # Provider interface + openai/anthropic/responses
  tool/ + tool/builtin/       # Tool interface + init() self-registration
  permission/                 # allow / ask / deny + bash decompose
  sandbox/                    # OS sandbox wrap
  memory/ · instruction/      # background facts vs standing instructions
  plugin/ · mcp* / capability # MCP / capability proxy
  checkpoint/ · evidence/     # change rewind and delivery evidence
  acp/ · bot/ · desktop*      # editor protocol, bots, desktop
```

Dependency direction (`docs/SPEC.md:54-56`): `cli → {agent, plugin, config} → {tool, provider}`; parent packages do not import self-registering child packages.

### 3.2 Stack

| Layer | Tech | Location |
|---|---|---|
| Language / distribution | Go, `CGO_ENABLED=0` single static binary | `Makefile`, `cmd/reasonix` |
| Config | TOML (`BurntSushi/toml`) | `internal/config`, `reasonix.example.toml` |
| TUI | Bubble Tea family fullscreen chat | `internal/cli/chat_tui.go` |
| Desktop | Wails (Go backend + frontend) | `desktop/` |
| Protocols | ACP, HTTP/SSE `serve`, MCP stdio JSON-RPC | `internal/acp`, `internal/control`, `internal/plugin` |
| Provider | OpenAI-compatible / Anthropic / Responses | `internal/provider/*` blank-import |

Entry is extremely thin (`cmd/reasonix/main.go`):

```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 Relation to Claude Code / Hermes / Codex

| Dimension | Claude Code | Hermes Agent | OpenAI Codex | **Reasonix** |
|---|---|---|---|---|
| Language shape | TypeScript (closed source) | Python brain + TS face | Rust workspace | **Go single binary** |
| Model strategy | bound to Anthropic | model-agnostic + learning loop | bound to OpenAI stack | **DeepSeek-first, OpenAI-compatible pluggable** |
| Context philosophy | strong prompt engineering | prefix cache sacred + memory on disk | two-layer turn + compaction | **Cache-first written into the constitution + runtime shape diagnostics** |
| Frontend | terminal-primary | CLI/TUI/desktop/20+ IM | CLI + app-server | **Controller-unified: TUI / serve / Wails / ACP** |
| Permissions | popup + rules | environment isolation, many backends | AskForApproval × OS sandbox | **Policy rules + bash decompose + sandbox + workspace confine** |
| Completion judgment | natural end + tools | skill/memory closed loop | three-party stop protocol | **natural end + maxSteps/todo stall; Delivery evidence sign-off** |

> If you remember only one sentence: **Reasonix = Hermes’s cache philosophy × Codex’s engineering hardness × Claude Code’s terminal-coding category, welded onto DeepSeek’s automatic prefix cache.**

---

# Part II · Mind map: how one constraint yields ten designs

<h2 id="ch4">Chapter 4 · Developer mind map: from “the prefix must stay stable” to ten designs</h2>

> This chapter is the “map” of the whole lecture: first see the spine in the developer’s head, then go into the code. The ten items below can all be derived from “the prefix must stay byte-stable”; each also has a clear cost.

| # | Corollary | Choice | If we didn’t |
|---|---|---|---|
| 1 | System prefix must be stable | Volatile context goes into the turn tail, not system (`control.Compose`) | Every interjection / memory update would cold the whole prefix |
| 2 | Tool schema is also prefix | Schema hashed after sort (`normalizeToolSchemas`) | Tool-registration-order jitter causes a “false miss” |
| 3 | Interjection is inevitable | Steer explicitly pays a cache tax once (`run_loop.go:276-283`) | Pretend zero-cost interjection; cache stats go false |
| 4 | Context will eventually grow | Compaction splits soft/snip/summary; compaction = cache-reset point | Unrestrained summary frequently resets cache |
| 5 | MCP inventory will change | `use_capability` fixed proxy isolates dynamic schema | Installing one MCP tool colds cache once |
| 6 | bash semantics must be precise | Runtime read-only degrade `BashCommandIsReadOnly` | Schema stable but semantics fuzzy; parallel/evidence get hurt |
| 7 | DeepSeek protocol has quirks | Missing reasoning silent-retry once | In thinking mode a tool round fails outright |
| 8 | Hitting the budget should not count as a crash | maxSteps → Pause, not Fatal | Long tasks lose session assets at the budget boundary |
| 9 | Many frontends must be consistent | Behavior goes into `control.Controller`, not the face | Each frontend patches itself; behavior drifts |
| 10 | Constraints must be executable | SPEC / TOOL_CONTRACT / tests same-source + PR metadata | The constitution is only a wiki prop |

---

<h2 id="ch5">Chapter 5 · Cache-first constitution: stable prefix vs turn tail</h2>

### 5.1 Two tracks

| Track | Contents | Rule |
|---|---|---|
| **Cache-stable prefix** | base system + tool schemas + standing memory/instructions | **Forbidden** to byte-drift inside a session |
| **Turn tail (Compose)** | user body, goal block, plan marker, language preference, `<memory-update>`, background-jobs, hook context, retrieval recall | Variable every turn; **must never** write back into the prefix |

`Controller.Compose` (`control/input.go:137-212`) writes this into a comment (`input.go:181-183`):

```text
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.
```

Assembly order (conceptually):

```mermaid
flowchart TB
    IN["user text"] --> G{"running goal?"}
    G -->|yes| GB["activeGoalBlock + autoResearch"]
    G -->|no| P
    GB --> P{"planMode?"}
    P -->|yes| PM["PlanModeMarker"]
    P -->|no| L
    PM --> L["response/reasoning language wrap"]
    L --> M["drainPending → memory-update"]
    M --> J["background-jobs note"]
    J --> H["hook context"]
    H --> R["memory.recall Block (real user turn)"]
    R --> OUT["hand to Agent.Run"]
```

This is the same philosophy family as Open Design’s “assemble the system prompt in bands by cache frequency” and Hermes’s “memory writes to disk take effect immediately, enter the prompt only next session.” Reasonix’s distinctive move: diagnose a miss **as a structured event**, rather than only warning in docs.

### 5.2 `PrefixShape`: why a miss?

```13:48:参考项目/DeepSeek-Reasonix/internal/agent/cache_shape.go
// 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` **sorts then hashes** tool schema (`normalizeToolSchemas`), avoiding “order jitter” false misses. `CompareShape` gives a `system` / `tools` / `log_rewrite` reason list, plus cache hit/miss tokens from usage.

Before sending to the provider it also strips UI metadata that would cold the cache: `LocalOnly` display records do not enter `ModelMessages` (`LocalOnly` mark in `provider.go`); wall-clock and cancelled half-streams never pollute the prefix.

### 5.3 Tension with dynamic MCP tools

Once dynamic `mcp__*` tools enter the main Registry, they change the tools hash → cold cache. Reasonix’s answer (`docs/TOOL_CONTRACT.md:45-96`):

- Delivery / Planner / most sub-agents: expose a **fixed-name** proxy tool `use_capability` (list / inspect / call / decline);
- **Connecting MCP on demand does not change** that proxy’s provider-visible schema;
- Balanced’s Executor **deliberately keeps** direct `mcp__*` (accepting possible prefix change)—this is a conscious product trade-off, not an omission.

> 🧠 **Load-bearing wall**: Cache-first is not “change the prompt as little as possible.” It is “**which changes are allowed to pay the cache-miss tax**” classified in the open: steer pays the tax, memory-update does not touch the prefix, MCP inventory change is isolated by a proxy.

---

<h2 id="ch6">Chapter 6 · Why dual models must split session</h2>

### 6.1 Motive

Want plan-then-execute cost/quality balance, but switching models would break the prefix.

### 6.2 Constraint and choice

`docs/SPEC.md:238-240` says it plainly:

> switching models inside one shared conversation would break the prefix and tank cache hits, so we don't.

When `planner_model` is configured, **Planner and Executor each have an independent session** (`agent/coordinator.go`): two prefixes each cache-stable; the sessions never mix-write. This is not the same layer as UI Plan Mode (a turn-tail marker).

### 6.3 Cost

- Two prefixes, two tool surfaces, first-round cost higher;
- Capabilities discovered in the planning phase must still be directly callable after handoff (`use_capability`’s fixed proxy exactly carries this);
- Routing more complex: `executor_only` | `plan_and_execute` | `plan_for_approval` | `plan_only` (`planner_route.go`).

---

# Part III · Implementation: from constraint to code

<h2 id="ch7">Chapter 7 · Main loop: Agent.Run → runToolLoop</h2>

### 7.1 Lifecycle entry

`Agent.Run` says it clearly itself (comment starts at `agent.go:1310`, function body `1319-1378`):

1. Resolve this turn’s `maxSteps` (overridable by context);
2. Open workspace lease / steer queue / background evidence submit defer;
3. `interceptAgentStart` (extension may abort);
4. `beginRunTurn` initializes state;
5. **`return a.runToolLoop(ctx, state)`**.

No mysterious “framework base class”—an explicit state machine; policy is split across `beginRunTurn` / `handleFinalResponse` / `handleToolRound`.

### 7.2 `runToolLoop`: what happens in one round

The core loop is at `run_loop.go:274-355`:

```mermaid
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? (write into session, may accept one cache miss)
        A->>A: CaptureShape(system+tools)
        A->>P: streamWithMissingReasoningRecovery
        P-->>A: text + reasoning + tool_calls + usage
        A->>A: CompareShape → cache diagnostics
        alt no tool_calls
            A->>A: handleFinalResponse (incl. maybeCompact)
        else has tool_calls
            A->>T: handleToolRound → parallel read-only / serial write
            T-->>A: tool results written into session
        end
    end
    A-->>U: maxStepsPause or error or nil
```

A few heavily flavored details:

| Detail | Meaning | Location |
|---|---|---|
| **Steer** | Mid-turn interjection into a queue; on consume, write a user message with a guidance prefix; the comment admits “one cache miss is inevitable” | `run_loop.go:276-283` |
| **Missing reasoning recovery** | In DeepSeek thinking mode a tool call missing `reasoning_content` → silent replay of the same request at most once | `run_loop.go:357-364`, `agent.go:1388-1394` |
| **Stream recovery** | Interrupted stream at most `maxStreamRecoveries=3`, and **does not consume maxSteps** (`step--`) | `run_loop.go:300-309` |
| **Cache diagnostics** | Every round `CompareShape` compares system/tools/log_rewrite | `run_loop.go:285-294` |
| **Pause, not crash** | `maxStepsPause` / `todoStallPause`: the work is already in the session; the user sending one more message continues | `agent.go:1469-1494` |
| **Delivery readiness failure** | Before a final answer the host checks todo/criteria/verify/signoff → `FinalReadinessError`, open the next round | `handleFinalResponse` |
| **Empty final / thinking-only** | At most `maxEmptyFinalBlocks` nudges | `run_loop.go`, `agent.go:42` |
| **Recovery grace** | After a recovery episode is exhausted, one summarize-only lap, then `RecoveryPauseError` | recovery path |

### 7.3 Assembly entry: `boot.Build` → the same Runner

| Mode | Trigger | Turn API |
|---|---|---|
| TUI | bare `reasonix` / `chat` | async `Controller.SendWithRaw` |
| Headless | `reasonix run` / `-p` | run one round synchronously; Ask **fail closed** against the writer; needs `--auto`/`-y` |
| ACP | `reasonix acp` | blocking `Controller.RunTurn` (`session/prompt`) |
| serve / desktop / bot | their own entries | SSE / Wails / IM → the same Controller |

`boot.Build` (`internal/boot`) freezes the system prefix in one shot (output style, environment-probe snapshot, memory, skill index), then wraps an optional `planner_model` into a `Coordinator`.

### 7.4 Single-tool execution pipeline

`executeOne` (`execute_one.go:65-101`) is a fixed five beats:

1. **`parseToolCall`** — Resolve, ambiguous MCP names, duplicate success/fail loop guard, stale-anchor edit intercept; bash may degrade to read-only by args (`permission.BashCommandIsReadOnly`);
2. **`interceptToolBefore`** — extension may rewrite the call;
3. **`resolveToolPolicy`** — permission gate;
4. **`prepareToolExecution`** — preview, mutation bookkeeping, parent write lock;
5. **`finishToolExecution`** — actually execute + receipt.

Read-only tools may run in parallel; writes are serial—same family as Claude Code / OpenAI4S “read-only waves,” but landing on Go’s mutation observer / evidence ledger.

---

<h2 id="ch8">Chapter 8 · Compose: drive volatile content into the turn tail</h2>

### 8.1 Motive

The system prefix must stay byte-stable, but goal, plan, language preference, memory updates, retrieval results may change every turn.

### 8.2 Constraint

Anything unstable, or that need not appear, cannot enter the cache-stable prefix.

### 8.3 Rejected alternatives

- “Paste all dynamic content into the system prompt”: convenient but colds cache every turn, directly against the constitution;
- “Dynamic content never enters context”: goal/plan/memory updates cannot take effect immediately.

### 8.4 Choice

`control.Compose` (`control/input.go:137-212`) assembles in a fixed order before the real user turn: activeGoalBlock → PlanModeMarker → language wrap → `<memory-update>` → background-jobs → hook context → `memory.recall` Block.

### 8.5 Cost

- One extra assemble-and-strip per turn (`StripComposePrefixes`);
- If a frontend/extension bypasses `Compose` and sends a message directly, the cache constitution is broken—so every frontend goes through the same Controller.

---

<h2 id="ch9">Chapter 9 · Compaction three rungs: soft → snip → summary (a cache reset with a posted price)</h2>

`compact.go:20-37` nails the policy:

| Stage | Default threshold (fraction of context window) | What it does |
|---|---|---|
| Soft | 0.5 | Report that context is growing; **still keep the cache-stable prefix** |
| Tool-result snip | 0.6 | Cheaply rewrite stale tool results |
| Summary compact | 0.8 (force 0.9) | Summary fold; keep a recent tail of a fixed token budget (default 16384) |

Design points:

- **Trigger by ratio, retain by absolute token budget**—a large window will not over-compact; a small window will not get stuck compacting near the threshold;
- Summary uses structured headings (Standing facts / Goal / Decisions / Files / Commands / Errors / Pending), wrapped in `<compaction-summary>`;
- Ablation switches can turn off cache-friendly strategy for a contrast experiment (`internal/ablation`);
- Compact bumps `LogRewriteVersion` → `CompareShape` will mark `log_rewrite`—**honestly admitting this is a cache-reset point**.

Compared with Raven’s “lossless archive” and Claude Code’s “summary discard”: Reasonix explicitly stands on **“cheap snip first, then pay the cache tax of one summary.”**

---

<h2 id="ch10">Chapter 10 · Tool ABI: builtin + MCP + use_capability fixed proxy</h2>

### 10.1 Compile-time builtins (contract table)

`docs/TOOL_CONTRACT.md` is guarded by tests (`TestBuiltinToolContractDocumentation`), same-source as the runtime registry. Core surface:

| Category | Tools |
|---|---|
| Read | `read_file` `grep` `glob` `ls` `code_index` `web_fetch` |
| Write | `write_file` `edit_file` `multi_edit` `move_file` `delete_range` `delete_symbol` `notebook_edit` |
| Shell | `bash` + `bash_output` / `wait` / `kill_shell` |
| Task/delivery | `todo_write` `complete_step` `update_goal` |

Implementation side is all `init() { tool.RegisterBuiltin(...) }` (e.g. `builtin/readfile.go:27`).

### 10.2 Full boot surface vs Economy

Default full-token boot also hangs session / memory / skill / subagent / LSP / install / slash etc.: `ask`, `task`/`fleet`/`parallel_tasks`, `memory`/`remember`/`forget`, `run_skill`, LSP four-piece, `explore`/`research`/`review`/`security_review`, etc. (`docs/TOOL_CONTRACT.md`).

Delivery additionally:

- **`use_capability`**: stable MCP proxy;
- **`review_report`**: structured review of medium/high-risk changes;
- Host-side evidence: verification / diff / files / manual; a `complete_step` with no evidence is **refused outright**.

**Economy** deliberately exposes only a thin surface (about `ask`/`bash`/`read_file`/`write_file`/`edit_file`/background three-piece + **`connect_tool_source`**); the rest mount on demand—trading “cannot see all tools at once” for tokens and prefix stability.

### 10.3 Tool-interface flavor

Tools carry capability bits such as `ReadOnly`, preview (`Previewer`), images, PlanMode classification—permissions and parallel scheduling read these bits, rather than relying on the model to “behave.” Checkpoint only tracks editing tools that carry `Previewer` (see Chapter 12).

---

<h2 id="ch11">Chapter 11 · Permissions, sandbox, workspace fence: application rules × OS fence</h2>

### 11.1 Permission Policy + Ask / Auto / Yolo

`permission.Policy` (`permission.go`):

- Rule shape aligned with Claude Code: `Tool` / `Tool(glob)` / legacy `Tool=literal` (`ParseRule`);
- `Decide(toolName, readOnly, args)`; bash takes **segmented decompose** (`DecomposeBashCommand`);
- `BashCommandIsReadOnly`: degrade “schema-writable bash” to read-only on the concrete argv (affects parallel, mutation, evidence);
- `BashSubjectRequiresExplicitApproval`: indirect execution, dangerous git, etc. force explicit approval.

Product approval modes (`docs/TOOL_APPROVAL_MODES.md`) are **orthogonal** to collaboration mode (normal/plan/goal):

| Mode | Behavioral points |
|---|---|
| **Ask** | Writes default to ask; headless `reasonix run` **fail closed** against the writer |
| **Auto** | Ordinary writers auto-allow; `deny`/`ask` rules, Plan confirm, nested/indirect Bash, MCP destructive, sensitive `remember`/`forget` still ask |
| **Yolo** | The only mode that can bypass the nested-Bash human gate; **still does not bypass** `deny` and sandbox |

There is also `internal/guardian`: an independent security-review sub-session (with a denial circuit breaker)—**cannot** replace user confirmation of memory writes. Hooks (`internal/hook`) provide `PreToolUse`/`PostToolUse`/`PostToolUseFailure`, plus Claude tool-name compatibility mapping.

### 11.2 Sandbox + confine

`builtin/bash.go` wraps the command in `sandbox.Command`:

| Platform | Bash OS sandbox |
|---|---|
| macOS | `sandbox-exec` (Seatbelt) |
| Linux | `bwrap` |
| **Windows** | **product-fixed off**, command unconfined; file-class builtins still have workspace confine |

On `enforce` with no backend, **fail closed** (do not run naked). Escape goes through `sandbox.EscapeApprover`. MCP stdio by default does not inherit the Bash sandbox (product default in `internal/plugin/plugin.go` is the host process; comments state the reason).

`SECURITY.md` writes the boundary clearly: workspace fence, permissions, sandbox, secrets, HTTP serve’s localhost/CORS, desktop/bot isolation, updater verification—and what does not count as a vulnerability.

### 11.3 Checkpoint / Rewind

Aligned with Claude Code’s Esc-Esc / `/rewind` narrative (`docs/CHECKPOINTS.md`):

- **Mechanism**: file snapshots (**not git**); sidecar `<session-id>.ckpt/`;
- **Tracked range**: editing tools that carry `Previewer` (`write_file` / `edit_file` / `multi_edit`);
- **Not tracked**: `bash` side effects; `move_file` has not yet implemented Previewer;
- **API**: `Controller.Checkpoints` / `Rewind` / `PrepareRewind`+`CommitRewind` (including coverage confirmation);
- **Explicitly not done**: git-backed rollback (docs mark out of scope).

---

<h2 id="ch12">Chapter 12 · Checkpoint / Delivery / Goal: delivery evidence and a completion protocol</h2>

### 12.1 Three orthogonal axes

`docs/GOAL_ENFORCEMENT.zh-CN.md` opens:

> Goal is the only cross-turn scheduler; Delivery is a pure quality gate; tool permissions and sandbox are unaffected by the Goal switch.

| Axis | Duty |
|---|---|
| **Goal** (`/goal`) | Cross-turn advance, budget, pause/resume |
| **Delivery** | readiness: todo / criteria / verification / review / signoff / capability… |
| **Permission / Sandbox** | always independent |

The model declares intent via `update_goal(continue|complete|blocked)`; the host uses a **structured `ReadinessResult`** (`agent.go:1497+`) to decide whether it is truly complete. Declaring `complete` without evidence → open the next round, rather than believing the model’s mouth.

### 12.2 `complete_step`

The builtin tool description is fierce (`TOOL_CONTRACT.md`): completion **must** carry evidence; the host advances the todo on its behalf. Same spectrum as grok-build Goal Mode and MiMo “do not trust self-report”—Reasonix lands it with **host-observed receipts**.

### 12.3 Collaboration Profile

(`docs/COLLABORATION_MODES.zh-CN.md`)

| Profile | Points |
|---|---|
| Economy | single model, thinner tool surface, for cost-sensitive tasks |
| Balanced | full tool surface; optional independent `planner_model` |
| Delivery | Balanced + `use_capability` + acceptance contract + review gate |

---

<h2 id="ch13">Chapter 13 · control.Controller: one face behind every frontend</h2>

### 13.1 Design iron law

`REASONIX.md:11-13`:

> One transport-agnostic `control.Controller` sits behind every frontend (chat TUI, HTTP/SSE serve, Wails desktop). Add behavior to the controller, not a frontend, so all three inherit it.

In practice there are also ACP / bot entries. Unified assembly: `boot.Build` → `Controller` → `agent.Runner` (or `Coordinator`).

### 13.2 Path of one user message

```mermaid
flowchart LR
    TUI["chat_tui"] --> SUB["Submit / Send"]
    HTTP["serve HTTP/SSE"] --> SUB
    DESK["Wails desktop"] --> SUB
    ACP["ACP"] --> SUB
    SUB --> ADM["admitGuardedTurn<br/>anti-reentry / 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
```

Key API family (`controller.go`): `Send` / `Submit` / `SubmitHTTP` / `SubmitDisplay` / `RunTurn` / Goal-loop variants; `runGuarded` does turn admission so a double-open cannot run away.

### 13.3 Why did Controller become a 6k-line giant file?

Because it swallowed: session restore, checkpoint, memory slash, MCP lifecycle, capability, steer fallback, yolo/plan, attachments, goal usage……**all product behavior shared by every frontend**. The cost is a giant file; the gain is “fix one place, TUI/desktop/HTTP all correct.” In this series OpenAI4S’s `gateway.py` and Codex’s `turn.rs` are the same “giant heart” shape.

---

<h2 id="ch14">Chapter 14 · Memory, instructions, extensions, desktop/ACP</h2>

### 14.1 Context Engine v2: instructions ≠ memory

Central law of `docs/SESSION_MEMORY_RETRIEVAL.md`:

- **Standing instructions** (`REASONIX.md` / `AGENTS.md` / `CLAUDE.md` + `.local` + ancestor directories + global): must appear on relevant turns; enter the **cache-stable prefix**;
- **Background memory**: facts that can go stale; BM25 auto-recall (defaults `defaultAutoRecallLimit=4`, `defaultAutoRecallChars=2400`) enters the **turn tail**; the index merges into the prefix only next session;
- `#note` / `/remember` write instruction files; the `remember` tool writes background facts—**do not mix**;
- Safe writes: some scopes create-only; the rest need confirmation—**Auto/Yolo/Guardian cannot bypass**;
- Instruction parse: deeper directories override broader; same directory `.local` wins; `@path` import limited to 5 layers and forbidden to escape.

### 14.2 Three extension layers: MCP · Plugin Package · Extension Protocol v1

| Layer | What it is | Trust model |
|---|---|---|
| **MCP** (`internal/plugin`) | JSON-RPC; stdio / Streamable HTTP; tool names `mcp__<server>__<tool>`; compatible with project `.mcp.json` | precise rules + `mcp_connect__*`; OAuth etc. still deferred |
| **Plugin packages** (`internal/pluginpkg`) | `reasonix-plugin.json`; compatible with `.claude-plugin` / `.codex-plugin`; contribute skills/hooks/mcp/commands/agents/themes | after install-and-enable, take effect by contribution surface |
| **Extension Protocol v1** (`reasonix.extension.v1`) | NDJSON JSON-RPC sidecar; **17 frozen hook points** (`tool.before`, `permission.decision`, `system_prompt.build`, `compaction.*`…); schema generation + CI drift-check | **Full trust**: install is highest privilege, can override host deny; UI marks `FULL TRUST` |

MCP and Extension are different layers: the former attaches tools/resources; the latter attaches host-lifecycle intercepts. **Full-trust Extension** is an independent attack surface, and also the hardest landing of an “Open Design-style public contract” inside Reasonix (`docs/EXTENSION_PROTOCOL.md`).

### 14.3 Subagent profiles

Skill frontmatter `runAs: subagent` (`docs/SUBAGENT_PROFILES.md`): builtins `explore` / `research` / `review` / `security_review`; CLI `reasonix subagent …`; invocation via `/<profile>` or `task`/`fleet` + `write_paths` parallel-write isolation. Default concurrency `max_subagent_concurrency=6`, `max_parallel_writers=3`. Claude plugin `agents/*.md` can map to `/<plugin>:agent:<name>`.

### 14.4 Desktop and ACP

- **Desktop** (`desktop/`): nested Wails module (keep parent module `CGO_ENABLED=0`); React+TS webview ↔ the same `Controller` (no HTTP hop);
- **ACP** (`internal/acp`, `docs/ACP.md`): `reasonix acp [--profile …]`; NDJSON JSON-RPC; `loadSession` + `embeddedContext`; capability advertisement **no image/audio**; MCP `http: true`, `sse: false`; vendor extensions include `_reasonix.io/session/steer`;
- VS Code extension `SivanLiu.reasonix-agent` brings up local `reasonix acp`;
- Remote-SSH / workbench: baseline PR #7368 reconnect hardening—the product has grown from a local TUI to a remote workbench.

---

# Part IV · Taste and bounds

<h2 id="ch15">Chapter 15 · Ten decisions, five-beat recap</h2>

| # | Choice | Motive | Constraint | Rejected | Cost |
|---|---|---|---|---|---|
| 1 | Volatile context into turn tail | prefix stable = save money | DeepSeek auto prefix cache hits by bytes | dump everything into system | per-turn assemble/strip cost; bypassing Compose breaks the constitution |
| 2 | Tool schema hashed after sort | avoid false miss | tool registration order is uncontrollable | hash by registration order | the sort logic itself must be test-guarded |
| 3 | Steer pays a cache tax once | interjection must be immediately visible | the model must see the new instruction | don’t interject / pretend zero cost | every interjection misses one round |
| 4 | Compaction three rungs | delay the summary reset | context will eventually grow | one-shot summary | extra snip logic and threshold tuning |
| 5 | `use_capability` fixed proxy | dynamic MCP cannot move schema | MCP inventory changes anytime | register mcp__* directly into the Registry | one extra parse layer and permission names (`mcp_connect__*`) |
| 6 | bash runtime read-only degrade | schema stable, semantics precise | whether bash is read-only depends on argv | judge only by schema | `BashCommandIsReadOnly` must maintain a dangerous-command list |
| 7 | Missing reasoning silent retry | DeepSeek thinking occasionally missing reasoning | provider requires thinking replay | fail outright | one extra same-request replay; need debounce cooldown |
| 8 | maxSteps → Pause | session assets first | budget limited but work already on disk | error and start over | more pause state machine and restore paths |
| 9 | Behavior into Controller | many frontends consistent | frontend count growing | each frontend implements itself | Controller becomes a 6k-line giant file |
| 10 | Contracts executable (SPEC/TOOL_CONTRACT/PR) | constitution cannot rely on conscience | the repo will keep growing | wiki docs | CI/test cost; PR metadata requirements |

---

<h2 id="ch16">Chapter 16 · Cross-cut (coding-agent table)</h2>

| Dimension | Claude Code | Open Design | OpenAI4S | Hermes | Codex | **Reasonix** |
|---|---|---|---|---|---|---|
| Category | terminal coding | design host | research dual-plane | personal learning agent | official Rust CLI | **DeepSeek terminal coding** |
| Main loop | own | **none (plugs into others)** | Engine+Cell | Python loop | `run_turn` | **`runToolLoop`** |
| Context | strong prompt engineering | 20-layer banded cache | science state in the kernel | prefix sacred | two-layer turn | **Cache-first + shape** |
| Completion signal | natural end | artifact files | finalize / submit_output | skill sedimentation | three-party stop | **natural end + Delivery readiness** |
| Extension | MCP/Skills | CLI adapters | Skills recipes | 74-tool plugins | MCP+crate | **MCP + Plugin packages + Full-trust Extension v1** |
| Distribution | closed-source product | daemon+web | Python daemon | uv/Python | Rust binary | **Go static binary** |
| Permission product surface | popup+rules | host delegated | Notebook approval | environment many backends | Ask×sandbox | **Ask/Auto/Yolo × sandbox (orthogonal)** |

---

<h2 id="ch17">Chapter 17 · Honest bounds</h2>

- **Giant-file cost**: `controller.go` / `chat_tui.go` / `agent.go` are all thousands of lines; newcomer onboarding is steep;
- **DeepSeek-first tax**: reasoning recovery and cache assumptions do not necessarily hold for other gateways (there is a compatibility path, but product mindshare still leans DeepSeek);
- **Executor direct MCP**: Balanced Executor can still cold cache because of MCP inventory change—the docs already admit this;
- **Windows Bash OS sandbox**: product-fixed unconfined; file tools still have confinement;
- **Checkpoint coverage**: does not track bash side effects; no git-backed; `move_file` preview unfinished;
- **Extension full trust**: install is highest privilege—capability is strong; the threat model needs the user to understand it;
- **MCP long tail**: OAuth, `list_changed`, some scopes still deferred (`docs/SPEC.md:877`);
- **ACP capability advertisement**: no image/audio; agent-side MCP SSE narrowed;
- **Old branch**: `v1` only critical fixes; analysis / secondary development should align with `main-v2`.

---

<h2 id="ch18">Chapter 18 · Source-tour index and local reproduction</h2>

| Order | Path | What to look at |
|---|---|---|
| 1 | `README.zh-CN.md` + `docs/SPEC.md` | product position and contract |
| 2 | `REASONIX.md` | the team’s own cache constitution |
| 3 | `cmd/reasonix/main.go` + `internal/boot` | entry and `boot.Build` |
| 4 | `internal/control/input.go` → `Compose` | turn tail |
| 5 | `internal/agent/run_loop.go` | main loop |
| 6 | `internal/agent/cache_shape.go` | miss diagnostics |
| 7 | `internal/agent/compact.go` + `prune.go` | compaction three rungs |
| 8 | `internal/agent/execute_one.go` | single-tool pipeline |
| 9 | `internal/agent/coordinator.go` | dual-model dual session |
| 10 | `docs/TOOL_CONTRACT.md` + `internal/tool/builtin/` | tool surface |
| 11 | `docs/TOOL_APPROVAL_MODES.md` + `permission/` + `sandbox/` | Ask/Auto/Yolo × fence |
| 12 | `docs/CHECKPOINTS.md` | rewind snapshots |
| 13 | `docs/GOAL_ENFORCEMENT.zh-CN.md` | completion protocol |
| 14 | `docs/SESSION_MEMORY_RETRIEVAL.md` | instructions vs memory |
| 15 | `docs/EXTENSION_PROTOCOL.md` + `PLUGIN_PACKAGES.md` | three extension layers |
| 16 | `docs/ACP.md` / `desktop/README.md` | embed and desktop |
| 17 | `internal/control/controller.go` (optional) | multi-frontend orchestration |

Local reproduction:

```bash
cd "参考项目/DeepSeek-Reasonix"
git fetch origin main-v2
git checkout main-v2
git reset --hard 627b051
# optional: run the contract test (needs local Go)
# go test ./internal/tool -run TestBuiltinToolContractDocumentation
```

---

## Appendix A · Contrast with this series’ “Catalog of agent engineering patterns”

| Pattern-catalog chapter | Reasonix landing |
|---|---|
| I Prompt-cache engineering | **Textbook**: stabilize the prefix / move the volatile out / stabilize tools / compact with care / telemetry visible (all five axes) |
| II Stop judgment | natural stop + maxSteps/todo stall Pause + Delivery readiness |
| III Permission gates | Ask/Auto/Yolo + rules-first + bash decompose + OS sandbox (Win Bash gap) |
| IV Context compaction | snip first, summary pays the tax; soft explicitly keeps the prefix |
| V Memory system | instruction files (prefix) ≠ background facts (BM25/tail); confirmation gate cannot be bypassed by Yolo |
| VI Tool system | fixed proxy isolates dynamic MCP; Economy `connect_tool_source` |
| VII Sandbox | Seatbelt/bwrap; application fence → permissions → OS sandbox |
| VIII Sub-agents | Skill profile + task/fleet; write_paths parallel-write isolation |
| XII Persistence | file checkpoint rewind (not git); does not cover bash side effects |
| XIII Extension ecology | MCP + Plugin packages + **Full-trust Extension Protocol v1** |
| XV Model strategy | DeepSeek-first; `planner_model` dual session keeps each cache |

---

## Appendix B · One-sentence close

**Reasonix is not “another Go-written Claude Code imitation.”**
It promoted DeepSeek’s automatic prefix cache from a billing leftover to the **architectural spine**: Compose split, Shape diagnostics, Capability proxy, PR metadata, Delivery evidence—all turn around “do not cold the cache.” If in this series you already understand Hermes’s cache-sacred and Open Design’s banded assembly, reading Reasonix is watching the same philosophy executed to what hardness inside a **Go single-binary coding agent**.

---

## Appendix C · Restoring developer motive: from “cost anxiety” to “architectural constitution”

> Chapters 1, 2, and 4 already unfolded the motive chain; here a condensed contrast table is kept for looking back.

| Motive | Evidence | Architecture it grew into |
|---|---|---|
| Long-session token cost is high | `README.zh-CN.md:42-43` | product position “tuned around prefix cache” |
| Prefix must stay byte-stable | `REASONIX.md:14-16` | constitution + PR metadata + CI |
| Volatile content must move out | `control/input.go:181-183` | `Compose` turn tail |
| Dual models cannot share a session | `docs/SPEC.md:238-240` | Planner / Executor independent sessions |
| Compaction must post an honest price | `docs/SPEC.md:328-329` | soft → snip → summary + `LogRewriteVersion` |
| Miss must be explainable | `cache_shape.go` | `PrefixShape` / `CompareShape` diagnostics |
| Cost is the product axis | `COLLABORATION_MODES.zh-CN.md:86,136` | Economy / Balanced / Delivery three tiers |

---

*Analysis baseline: `参考项目/DeepSeek-Reasonix @ 627b051` (`main-v2`).*
*Series position: a coding-agent sample; nearest neighbors Hermes (cache), Codex (sandbox orthogonal), Claude Code (category).*
