## 问题 仓库是公开的,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
9.7 KiB
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:
- The dependency graph and the binary must match. The kernel compares the embedded
graph digest against
depgraph.binon disk and refuses to start on mismatch (★ 依赖图与本二进制不配套). This is deliberate: a mismatched graph means the assembly order is wrong, and a wrong order produces silentnilreads. depgraph.binlives 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:
- 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. - Direction matters. Inbound (model → plugin) fills aliases. Outbound (plugin → model)
scrubs any secret accidentally echoed back — e.g.
enc_shellrunningcurlwhose 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. Storedelegates encryption toCodec, and domains must differ. Store ciphertext uses domainsecurity/variables; config-at-rest usesconfig-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):
- Parallel path appended
ToolResultsbut neverToolCalls⇒ results outnumbered calls - Budget truncation in L1 rendering cut between
assistant(tool_calls)and itstoolmessages ⇒ dangling calls - A rejected/skipped tool recorded a call but not a result ⇒ same mismatch, persisted into
context.jsonand 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.