Files
HomeAgent/docs/en/design-summaries.md
JianFeeeee 06f3161e1d docs(en): 建立英文文档目录 —— 索引 + 教训全文 + 设计摘要
## 问题

仓库是公开的,README / README_EN 已经双语,但 `docs/` 只有 `docs/zh/`:
英文读者读完首页就撞墙。

## 做法:摘要而非全译,并写明为什么

`docs/zh/` 有 27 份文档、约 7000 行设计。**全译会烂得比审得还快**,
而一份陈旧的译文比没有更糟 —— 读者不知道它落后于原文多少。

所以按"内容变化速度"分级:

| 类型 | 译法 | 理由 |
| --- | --- | --- |
| 教训 | ✅ 全文 | 复用价值最高,失败形态会反复出现 |
| 索引 | ✅ 全文 | 没有它其余都不可发现 |
| 设计文档 | ⚠️ 一页摘要 + 状态行 | 7000 行全译跟不上演进 |
| 历史快照/证据 | ❌ 不译 | 时点产物,翻译无增益 |
| 未实施计划 | ❌ 不译 | 会变;且按 §4.3 本就不该进仓 |

判据写在 `docs/en/README.md` 里:

> 如果一份文档的内容变化速度快过译文能被审阅的速度,就译它的**摘要**而不是正文。

## 三份文件

**docs/en/README.md** —— 索引。含分类表(哪些已译、哪些只有中文摘要、
哪些不译)、状态行约定、以及"本索引必须在同一提交里更新"这条硬规则
(滞后的索引比没有索引更糟:它教读者信任错的地图)。

**docs/en/engineering-lessons.md** —— §1(判据会骗人)+ §5(判据形态)
全文翻译,§2–§4 摘要。§5 是本轮新增的核心内容:

    5.1    判据必须把被测模块当黑箱(含三个正当例外)
    5.2    禁止读源码做断言(附"想保证什么 → 错误做法 → 正确做法"表)
    5.2.1  判据不得以文档为依据
    5.3    测试与业务代码分离
    5.3.1  三种测试位置的实测边界
    5.3.2  覆盖率必须用 -coverpkg=./...
    5.4    CI 必须覆盖每个独立 module
    5.5    CI 与本地必须跑同一套门

每个小节都带实测数据与变异验证结果,不是原则罗列。

**docs/en/design-summaries.md** —— 6 份核心设计的一页摘要:

    kernel-modularization   6 层 20 模块;依赖图在构建期算好并嵌进二进制;
                            depgraph.bin 必须与二进制同目录,放数据目录会崩溃循环
    secret-firewall         security 是外围到核心之间的防火墙,自持数据结构、
                            只接受事实不接受策略;出入向都要洗
    input-scheduler         L1–L4 + 现场保存;工具执行是临界区不可抢占
    resident-subagent       与 rtchild 语义相反;单数/复数共用 Cleanup
    toolcall-contract       assistant(tool_calls) 后必须紧跟每个 id 的 tool
                            消息;批内不得插入任何东西
    context-overflow-l4     下沉到冷上下文而非截断;InactivePages 是本次渲染
                            的临时标记(json:"-")

另有 13 份(插件相关 / 数据报告型)列为 "Chinese only" 并说明理由。

## 中文索引加了英文入口

`docs/zh/README.md` 顶部增加指向 `../en/README.md` 的一行,并明确:

> 本目录是**中文原文,也是唯一权威**;英文侧只做摘要,与本文冲突时以本文为准。

这一条很重要 —— 否则两份文档会逐渐分叉,而读者不知道该信哪个。

## 验证

    go build ./...        PASS
    go vet ./...          PASS
    gofmt -l              干净
    go test ./...         65 包全过,0 FAIL
2026-11-04 20:05:10 +08:00

9.7 KiB
Raw Blame History

Design summaries (English)

Status: summaries only. Each design document exists in full only in Chinese: docs/zh/*-design.md. Those are the authority. This file gives an English reader enough to know what problem each design solves and whether it is in effect, plus a pointer for the details.

Why summaries instead of translations: the design docs total ~7,000 lines. A full translation would rot faster than it could be reviewed, and a stale translation is worse than none. For content that changes quickly, translate the summary.


kernel-modularization

Source: docs/zh/kernel-modularization-design.md (1,502 lines) Status: implemented and deployed (designed 2026-10-16, landed 2026-10-24, fully revised 2026-10-31). This document is the single authority on assembly architecture.

Problem it solves: the kernel had 17 serial startup steps. Any module that needed another module's result had to be started later than it, which produced a hand-maintained ordering that silently broke whenever a dependency was added.

Shape: every kernel module declares Requires() (facilities) and Provides() (facilities). A dependency graph is computed at build time and embedded in the binary as depgraph.bin. At startup the kernel assembles in layers — independent modules in the same layer start concurrently. Current production shape: 6 layers / 20 modules, peak concurrency 4.

Two invariants worth knowing:

  1. The dependency graph and the binary must match. The kernel compares the embedded graph digest against depgraph.bin on disk and refuses to start on mismatch (★ 依赖图与本二进制不配套). This is deliberate: a mismatched graph means the assembly order is wrong, and a wrong order produces silent nil reads.
  2. depgraph.bin lives next to the binary, not in the data directory. Deploying it into the data dir produces a crash loop.

Key concepts for reading the code:

  • facility — a named capability one module provides and others require
  • layer — computed from the graph; modules in a layer have no dependencies between them
  • kstate — the single mount point for shared data. Other packages mount onto it; it never knows about them (leaf package).

secret-firewall

Source: docs/zh/kernel-secret-firewall-design.md (952 lines) Status: implemented and deployed; architecture converged 2026-10-31.

Problem it solves: secrets (API keys, platform credentials) must not reach the model's context, logs, or disk in plaintext — but plugins legitimately need the real values to make API calls. So there must be a translation layer at the boundary.

Shape: security is a firewall between the peripheral kernel and the core kernel. It owns its own data structures and makes its own decisions. Callers supply facts only (CheckTool(name), ScanContent(text)) — never a policy, never a "trust me" flag.

Three properties that matter:

  1. Aliases, not values. The model sees sec_xxx; the firewall substitutes the real value inside the executor, after the argument has left the model's context.
  2. Direction matters. Inbound (model → plugin) fills aliases. Outbound (plugin → model) scrubs any secret accidentally echoed back — e.g. enc_shell running curl whose response body contains the key. Both directions have separate tests because only inbound was implemented at first, and the outbound leak was found in production logs.
  3. Store delegates encryption to Codec, and domains must differ. Store ciphertext uses domain security/variables; config-at-rest uses config-at-rest. Sharing a domain means one leak informs the other.

Judgement criterion the design insists on: CheckPath / CheckNetwork / CheckTool are only real if something in the execution chain calls them. A policy that exists but has no caller is indistinguishable from no policy — and this happened here twice.


input-scheduler

Source: docs/zh/input-scheduler-design.md (642 lines) Status: implemented and deployed (internal/agent/core/scheduler.go).

Problem it solves: a single agent receives messages from many channels (chat, CLI, device bridge) with wildly different urgency. A naive FIFO queue either delays urgent interrupts behind long tasks, or lets interrupts destroy work in progress.

Shape: four interrupt levels (L1–L4) plus a queue for non-interrupting work; preemption with state snapshots, so a suspended task resumes exactly where it stopped rather than restarting. Interrupts form a strict LIFO stack bounded by the number of levels.

Invariants (each has a test):

  • L4 does not preempt L4 (same-level interrupts queue rather than nest)
  • Stack depth is bounded; exceeding it demotes rather than unbounded recursion
  • A suspended task and its resumed counterpart see the same state (SuspendedEqualsResumed)
  • Tool execution is a critical section: interrupts do not cancel mid-tool (side effects are not rollback-able). The interrupt is recorded and applied after the tool completes.
  • The notify hook fires on push and pop, while holding the lock (a hook that takes the lock itself would self-deadlock)

resident-subagent

Source: docs/zh/resident-subagent-design.md (712 lines) Status: implemented and deployed.

Problem it solves: some work spans many messages over hours (a project conversation, a phone call). Stateless sub-agents lose everything between turns; giving every sub-agent a full agent loop is too heavy.

Shape: a resident child has its own input queue, its own two-level memory, and a parent-child interrupt relationship. Residual tasks left at teardown have four states (keep / drop / park / return) rather than being silently lost.

Why it is separate from rtchild: the rt (realtime) child has the opposite lifetime semantics — no task queue, no residuals, a fresh context window per call. They share the registry skeleton but not the semantics.

Known trap (this is why child.Registry exists): the singular stop path used to unregister the inbound channel while the plural path forgot it, so residue accumulated monotonically with each create/destroy until restart. Both paths now call the same Cleanup.


toolcall-contract

Source: docs/zh/toolcall-contract-and-sequence-design.md (839 lines) Status: implemented and deployed.

Problem it solves: with parallel tool execution, the wire format has hard constraints that are not optional — an assistant message declaring tool_calls must be followed immediately by one tool message per tool_call_id. Anything inserted in between (multimodal attachment, memory recall, a system note) makes providers reject the whole request with HTTP 400.

Shape: the batch executor collects all tool results first, then writes messages sequentially, then flushes deferred attachments (flushBatchAttachments). Nothing may be inserted inside the batch.

Measured failure modes (all three happened):

  1. Parallel path appended ToolResults but never ToolCalls ⇒ results outnumbered calls
  2. Budget truncation in L1 rendering cut between assistant(tool_calls) and its tool messages ⇒ dangling calls
  3. A rejected/skipped tool recorded a call but not a result ⇒ same mismatch, persisted into context.json and replayed every turn

The third is the instructive one: "skipped" ≠ "no result". The rejection reason is the result, and the model already saw it. The context copy must match what the model saw.


context-overflow-l4

Source: docs/zh/context-overflow-l4-design.md (163 lines) Status: implementation record; terminology in the document is outdated (it still says doc_query / doc_commit, renamed to coldcontext_*).

Problem it solves: when the context window is exhausted, the two conventional options are both bad — truncate (lose information) or ask the model to summarize (costly, lossy, and the model may summarize wrongly).

Shape: instead of discarding, low-relevance events are sunk into a cold-context layer, and from there distilled into a graph. Nothing is deleted; it changes storage tier. Overflow handling recurses to L4 when trimming alone cannot help.

Why it matters for reading the code: InactivePages in ContextEvent is the "this page is hidden for this render" marker. It is deliberately json:"-" — hidden pages are a per-render decision, not persisted state. The constraint is that a whole tool call and its results are hidden or shown together; hiding one half produces the 400 in toolcall-contract above.


Other design docs (Chinese only, not summarized here)

Document Scope
commondev-design.md The commondev plugin (multi-repo supervisor)
commondev-agent-runtime-design.md Runtime wiring for that plugin
triple-distill-llm-design.md Triple extraction/distillation pipeline
triple-extract-attention.md Attention weighting for extraction
recall-as-association.md Recall as spreading activation rather than lookup
recall-capability-tiers.md What each recall tier can and cannot do
multimodal-space.md Unified multimodal embedding space
vector-provider-decision.md Choice of embedding provider and why
abstention-criterion.md When the agent should decline to answer
legacy-issues.md Known legacy problems
legacy-table-retirement.md Old-table retirement plan (cited by production code)
kernel-performance.md Performance measurements and budgets
production-recall-validation.md Recall validation on production data

These are not translated because they are either plugin-specific or data-report shaped — their value is in the numbers and the local context, both of which the Chinese original carries adequately.