docs(rfc): define and enforce a uniform RFC format; adopt it across the corpus

Define the in-file RFC contract in docs/rfc/README.md § The file format:
the header block (`# RFC: <title>` plus a dateless Status enum
cross-checked against the lifecycle folder), the per-lifecycle body
skeleton (a Problem opener everywhere; Proposal/Alternatives considered/
Acceptance criteria/Risks in proposed/; present-tense Decision/
Consequences with proposal-era headings banned in implemented/; the
frozen proposal shape in rejected/), and a mandatory Alternatives
considered section with a date-fenced grandfather comment for pre-format
RFCs whose alternatives are not reconstructible from the record.

Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and
normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the
enum, 29 Context openers become Problem, the 39 legacy-format XXX debt
markers are resolved and banned from reappearing, proposal-era sections
in implemented RFCs are rewritten to shipped reality (including the
web/fs/subagent seam RFCs' migration plans and test checklists, closing
the doc-tiers deferred-work item on the web seam), every RFC gains an
Alternatives considered section or the grandfather comment, and the
bilingual pair is re-mirrored and re-recorded.

Move the generated index tables out of README.md into a fully generated
docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and
verify-rfc-classification checks its freshness and rejects index-shaped
rows in the curated README — which makes room for the format contract to
live in the README front door instead of a separate FORMAT.md.

The decision record, and the first RFC written in the new format, is
docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
This commit is contained in:
Tianyi Cui
2026-07-05 22:58:25 +08:00
parent d5cb3297ad
commit e6fad266a6
123 changed files with 1221 additions and 847 deletions

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Problem
The harness models its core vocabulary — content blocks, message sources, finish reasons, turn triggers, turn-end reasons, and session events — as **merge-extensible maps**: a TypeScript `interface` (e.g. `SessionEventMap`, `ContentBlockMap`) that plugins augment via declaration merging, with the public union derived as `Map[keyof Map]`. This is the repo's universal extension pattern, documented in [docs/architecture.md](../../../architecture.md) ("The same merge-extensible-map pattern is used for `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`") and relied on by the `defineTool` `InferArgs` DSL and the `assertNever` exhaustiveness convention.
@@ -36,7 +34,7 @@ A migration of the event/vocabulary surface to runtime schemas touches, at minim
This is a HUGE change. It is not in scope for the RFC-009 session-persistence work and must not be smuggled in through it.
## Options
## Alternatives considered
### A. Status quo — merge-extensible types + `isJsonValue` at the durable boundary
Keep the compile-time pattern. Persistence stays opaque-JSON + serializability guard. Plugins extend via declaration merging; correctness of event *shape* is the producer's responsibility, enforced by TypeScript at compile time and by the `dsh-invariants` plugin's structural checks in dev.
@@ -56,10 +54,20 @@ Replace the merge-extensible maps with a runtime registry the producers contribu
- **Pros**: real runtime validation at the durable boundary and at plugin seams; one source of truth; enables generic tooling (auto-generated docs, fuzzing, wire-format checks).
- **Cons**: the full blast radius above; **Zod is not currently a direct dependency** (only a transitive dep of `@earendil-works/pi-ai`) and the repo's chosen schema lib is **schemastery** — adopting Zod broadly is itself a dependency decision; declaration-merge ergonomics (one-line plugin extension, full inference) are replaced by runtime registration + manual type wiring; the `assertNever` exhaustiveness guarantee weakens (runtime variants aren't statically exhaustive).
## Recommendation
## Proposal
Defer. Do **not** change #33. If runtime validation is wanted at the durable boundary in the near term, **Option B** (schemastery on the closed header/metadata shapes) is the proportionate step and stays within the existing convention. **Option C** is a genuine architecture decision that should be evaluated on its own merits — including whether the chosen library is Zod or schemastery — and, if accepted, land as its own change with its own RFC, not as a side effect of persistence serialization.
## Acceptance criteria
- The decision state is explicit: Option C proceeds only as its own change with its own implementation RFC — never as a side effect of a persistence PR.
- If Option B is taken up, the closed header/metadata shapes (the JSONL `isHeaderLine` guard and kin) validate through schemastery in place of hand-rolled guards, with the merge-extensible maps untouched.
## Risks
- The deferral leaves event `data` structurally unvalidated at the durable boundary: a malformed-but-JSON datum is caught late, by a consumer's `switch` — the status-quo cost, accepted deliberately.
- If Option C is ever adopted, the ergonomic loss is real: one-line declaration merging becomes runtime registration plus manual type wiring, and the `assertNever` static-exhaustiveness guarantee weakens.
## Open questions
- If a registry is adopted, is the library **schemastery** (already in the tree, already the config schema lib) or **Zod** (richer ecosystem, currently only transitive)? Adopting two schema libraries is a cost in itself.

View File

@@ -34,6 +34,8 @@ A consumer census of the surface the runtime would carve up. Production has two
- ACP and snapshot fixtures render background bash through the shared task vocabulary, not through bash-only lifecycle semantics.
- The [tool cookbook](../../../cookbook/adding-a-tool.md) points long-running tools at the shared runtime instead of telling each tool to invent its own task protocol.
## What we give up
## Risks
The bash package loses local ownership of an already-working background-task implementation, and the implementing PR may temporarily churn model-facing tool names or transcript presentation. That churn is worthwhile if it leaves one background-task contract instead of making every future long-running tool clone bash's private protocol.
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->

View File

@@ -2,7 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
> **Implementation status (MVP landed):** steps 1, 2, 3, 4, 6, 7, 8 are implemented in `packages/ui/acp` + `examples/acp-agent`. **Step 5 (the `session/request_permission` permission gate) is deferred** — the bridge ships a pass-through (tools run with the executor's full authority) marked `TODO(rfc010-permission-gate)`, and lays down only the `WeakMap<Agent, sessionId>` ownership seam the gate will build on. Status stays `proposed` until the gate lands. `session/cancel` is the queue-aware `agent.cancel()`: it aborts a running step, clears queued + steering work, and drops a turn that is about to start, so a queued-but-not-yet-started prompt never runs and a later prompt cannot be batched into the cancelled turn. **Per-session `cwd` is now honored** (lifting the original "launch the server in the workspace root" restriction — see § Deferred): `session/new` accepts any absolute `cwd`, and `session/load` requires the request `cwd` to match the persisted session `cwd` so the editor and bash executor agree on the workspace.
## Problem
@@ -59,6 +58,17 @@ Deferred (each names its owning future work):
- Client `terminal/*` proxying (a live editor terminal) and `fs/*` (editor-rendered diffs) — a future `BashExecutor` over the [capability seams](../../implemented/architecture/2026-06-13-capability-seams.md) bash seam, gated on `clientCapabilities.terminal`.
- Image/audio prompts (blocked on the DeepSeek adapter, which skips `image` blocks today), modes, auth, `available_commands`/slash-commands, `plan`, and `usage_update`.
## Alternatives considered
- **A process-wide stdout hijack inside `dsh-acp`** (defensively monkey-patching `console.log` / `process.stdout.write`) — rejected: it lives outside Cordis' effect-scoped, HMR-friendly plugin model, races the connection's own stdout handoff, and fights the logger. The stdout guarantee is config-only.
- **Injecting `agentLoop` directly instead of the abstract create/resume factory** — the recorded fallback, taken only if the factory seam is judged not worth it, with the architecture-rule exception recorded in `docs/architecture.md`.
## Acceptance criteria
- The `acp-agent` example speaks ACP over stdio end-to-end: `initialize`, `session/new` with a validated absolute `cwd` honored as the session workspace, streamed `session/update` frames per prompt turn, `session/load` re-deriving identical history, and `session/prompt` resolving with the correct wire `stopReason`.
- stdout carries only framed JSON-RPC (asserted by test); the permission gate settles every `session/request_permission` exactly once — on outcome, cancel, or connection close.
- The plan's test set runs green: the property-based protocol invariants, the codec unit tests over an in-memory duplex pair, the HMR-safety test, the failure-path matrix, and the self-skipping real-API e2e that verifies the world.
## Risks
stdout is the protocol — guaranteed by config, not by monkey-patching. The console logger writes through `console.log` to stdout, so any stdout UI/logger plugin corrupts JSON-RPC. The guarantee is config-only: the `acp-agent` example loads no stdout plugin (no console logger, no `stdio-chat`) and, if logging is wanted, uses a stderr exporter. A defensive process-wide `process.stdout.write`/`console.log` hijack inside `dsh-acp` is explicitly rejected — it lives outside Cordis' effect-scoped, HMR-friendly plugin model, races the connection's own stdout handoff, and fights the logger. A test asserts the example emits only framed JSON-RPC on stdout.

View File

@@ -2,7 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
> **Implementation status:** the multi-session bridge (steps 1, 3, 4) and the bash task-ownership isolation are implemented in `packages/ui/acp` + `packages/bash/tool-bash`. **Per-session *permission* ownership is deferred** — it depends on [the ACP support permission gate](2026-06-14-acp-agent-client-protocol.md) (`TODO(rfc010-permission-gate)`), which is itself deferred; the `agent→sessionId` reverse map the gate will route through is in place. Step 2's per-session disposer scope is now implemented (see [agent lifecycle & ownership seams](../../implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md)): the factory returns a per-agent `AgentHandle` whose `dispose()` stops the loop, awaits quiescence, unregisters the agent, and removes its session, so a bare client disconnect leaves no registered agent or session-store entry. Status stays `proposed` until per-session permission ownership lands.
> **Target-client note:** Zed is the current target ACP client, and its ACP client maintains a `HashMap<SessionId, AcpSession>` plus `pending_sessions` for concurrent `session/load` calls. The competing simplification to return to one live session per connection was rejected after checking that target-client shape; this RFC remains the path for finishing multiplexing and per-session permission ownership. See [the rejected simplification](../../rejected/simplification/2026-06-20-single-session-acp-bridge.md).
@@ -30,6 +29,16 @@ The harness core already supports many agents (`AgentRegistry.list()` and `Agent
3. Lift the `session/new` guard; keep `session/load` ([from ACP support](2026-06-14-acp-agent-client-protocol.md)) working per session.
4. Tests for cross-session isolation: two sessions streaming and permission-prompting concurrently never interleave; a cancel/abort in one session leaves the other's stream and pending permission untouched; per-session in-flight-prompt enforcement holds independently; disposing one session leaves the others running.
## Alternatives considered
**A per-session `ctx.extend()` scope** — rejected: in Cordis, `ctx.extend()` only creates a child context/prototype, and `ctx.on()` registered on it is still owned by the current plugin fiber, so disposing it would not remove that session's listeners. A genuine child fiber (or a per-session collection of disposers) is required.
## Acceptance criteria
- N concurrent sessions stream and permission-prompt without interleaving their `session/update` notifications; a cancel in one session leaves every other session's stream, queued prompts, and pending permissions untouched.
- Disposing one session removes exactly its own listeners; connection teardown reaches quiescence across all sessions.
- One session's agent cannot read or kill another session's background bash task.
## Risks
Listener fan-out cost: each session adds listeners; ensure disposal of one session removes exactly its own and the connection teardown ([from ACP support](2026-06-14-acp-agent-client-protocol.md)) still reaches quiescence across all sessions.

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Problem
Today the agent loop advertises every registered tool to the model as a native JSON-schema function definition. `ToolRegistry` feeds its schemas into `ctx.systemPrompt`, the loop puts them on `GenerateOptions.tools`, and the adapter serializes them to the provider's function-calling wire format. The model then invokes one `tool-call` block per step, the loop dispatches each call through `ctx.tools.execute()` **sequentially** (parallel tool execution is an explicit open TODO in `dsh-tools` and [docs/architecture.md](../../../architecture.md)), and **every** intermediate `tool-result` re-enters the model's context on the next request.
@@ -73,7 +71,7 @@ These are illustrations of the seam's reach, **not commitments** — the MVP shi
**Optionality / toggle.** Loading the `code-mode` plugin enables Code Mode for that context; not loading it leaves today's native tool-calling untouched. The two are mutually exclusive within one ctx, because Code Mode rewrites the wire tool list down to `[run_code]`. Per-agent selection via ctx forks, and the visibility tiers above, are future work; the MVP toggle is plugin presence.
## Alternatives
## Alternatives considered
**Result elision / summarization over native tool-calling (the narrower route).** The Problem has two halves — context bloat (every intermediate `tool-result` re-enters context) and serial composition (one tool call per round-trip). The context-bloat half can be addressed *without* any code-execution runtime: keep provider tool-calling exactly as it is, and add a plugin on the `agent/request` waterfall (or a compaction pass akin to [the session-persistence work](../../implemented/architecture/2026-06-14-session-persistence.md)) that elides or summarizes older `tool-result` blocks before they re-enter the model's context — drop them past a window, replace large payloads with a digest, or keep only the blocks the model still references. This is strictly less invasive than Code Mode: no new runtime seam, no model-written programs, no new safety surface. It is the right tool if context growth is the only pain.
@@ -90,6 +88,14 @@ It is insufficient for the **composition / round-trip** half, which is the decis
5. Wire an example: `examples/coding-agent-code-mode` (or a config flag on the existing example) loading the trio. Running it against the node:vm stub requires both opt-ins (`VmCodeRuntime({ unsafe: true })` and `code-mode`'s `allowUnsafeRuntime`); the example sets them explicitly and comments why, or uses a mock model — a real model never reaches the unsandboxed stub without those deliberate flags. Add a `pnpm run demo:*` entry.
6. Docs: update [docs/architecture.md](../../../architecture.md) (a `ctx.codeRuntime` row in the service map, a Code Mode note under the tool pipeline / capability seams sections); add a [cookbook](../../../cookbook) note on writing a `CodeRuntime` backend; and **file the follow-up RFC for the hardened execution substrate** (the isolate/sandboxed-process design, the additional-language backends sketched in §1 — AssemblyScript/WASM, Python — with their per-language SDK generators, plus the tool-visibility-tier design skipped here). On landing, move this file to `implemented/` and update its row in [the RFC index](../../README.md).
## Acceptance criteria
- The three packages exist and pass their suites: `dsh-code-runtime` (the abstract seam), `dsh-code-runtime-vm` (the reference stub whose constructor throws without `{ unsafe: true }`), and `dsh-code-mode` (SDK codegen, the lazy prompt section, the `agent/request` collapse, the `run_code` tool).
- The wire tool list is exactly `[run_code]` under the plugin (spy-adapter test); the generated SDK covers every registered tool, with non-identifier names reachable via quoted access.
- A program calling two tools returns only its curated output; `code/dispatch` events land in the session log and never enter derived history.
- `Promise.all` over SDK calls does not overlap the underlying `ctx.tools.execute` invocations (the per-run serialization queue holds); an abort stops further dispatches.
- With `allowUnsafeRuntime` unset over an unsafe runtime, `run_code` is not registered and the wire tool list is unchanged from native.
## Risks
node:vm is not a sandbox. This is the single biggest caveat. Withholding `require`/`process` is not a boundary; the MVP runs at harness trust only; the hardened substrate is a hard prerequisite before any untrusted use and is the explicit subject of a follow-up RFC. The guard is enforceable, not just documented: the runtime exposes `safe: boolean`, the VM stub throws unless constructed with `{ unsafe: true }`, and `code-mode` refuses to register `run_code` over an unsafe runtime unless separately acknowledged (`allowUnsafeRuntime`) — production misuse requires two deliberate, greppable flags, and the refusal path is tested.

View File

@@ -1,10 +1,8 @@
# RFC: Pre-tool input rewrite — a consistent design
Status: proposed (2026-06-30)
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Context
## Problem
The [interception-seams RFC](../../implemented/feature/2026-06-30-interception-seams.md) added `tools/pre-execute` returning a `PreToolDecision` (allow/deny/ask) — but deliberately NOT input rewrite (a hook changing a tool call's `arguments` before it runs). Claude Code's `PreToolUse` hook offers an `updatedInput`, so a faithful CC bridge wants the same. This RFC designs that, separately, because doing it consistently is a real problem — not a field to bolt onto the allow decision.
@@ -18,9 +16,9 @@ In the loop, a tool call's arguments are committed to the log and read by live c
So an "input rewrite" that changes ONLY what executes would make the UI show one command while another RAN, and render result state against the wrong arguments — a real inconsistency, not a documentable gap. (The existing low-level capability to mutate `exec.arguments` in a listener has exactly this latent inconsistency; it is unadvertised precisely because of this — yet not unused: a tool-bash integration test rewrites a scripted call's arguments through it (`packages/bash/tool-bash/tests/integration.spec.ts`), so this design must either sanction that path with the consistency unit below or seal it — `readonly` arguments at the seam, with the test shim moved onto a behavior-level helper.)
## Proposed design (sketch — to validate against the code when built)
## Proposal
Treat input rewrite as a consistency unit: when a `pre-execute` hook supplies `updatedInput`, the rewrite must be reflected in ALL three readers, atomically, before execution:
A sketch, to validate against the code when built. Treat input rewrite as a consistency unit: when a `pre-execute` hook supplies `updatedInput`, the rewrite must be reflected in ALL three readers, atomically, before execution:
- The `tool/call` audit event records the REWRITTEN arguments (with the original retained in a sidecar field for the audit trail — a hook changed the call, and both the original and the effective arguments are facts worth keeping).
- The `assistant/message` in derived history must agree with what executed — options to evaluate: rewrite the assistant message's tool-call block in place (changes what the model "sees it said"), or record a separate correction the next request carries. The CC model is that the model sees the rewrite took effect.
@@ -28,10 +26,23 @@ Treat input rewrite as a consistency unit: when a `pre-execute` hook supplies `u
The shape would extend `PreToolDecision` with an allow-variant `arguments` (or a dedicated `{kind:'rewrite', arguments}`), and the loop would thread the rewrite through the three readers above rather than only into `ctx.tools.execute()`.
## Why not now
## Alternatives considered
### Why not now
The interception-seams RFC notes input rewrite "fought the code across two review rounds" — the signal AGENTS.md names for an over-reaching change. Shipping allow/deny/ask first keeps the seam honest (no advertised contract that silently desyncs the UI), and a CC/Codex bridge that receives an `updatedInput` logs it and surfaces a faithful-but-degraded warning (like `ask`→deny) until this lands. This RFC is the home for the consistent design; `TODO(pre-tool-input-rewrite)` in the loop's pre-execute call site anchors it.
## Acceptance criteria
- A `pre-execute` rewrite is reflected in all three readers atomically before execution: the `tool/call` audit records the rewritten arguments (the original retained in a sidecar field), derived history agrees with what executed, and presentation renders the rewritten arguments.
- The unadvertised `exec.arguments` mutation path is either sanctioned by this consistency unit or sealed (`readonly` arguments at the seam, the test shim moved onto a behavior-level helper).
- The CC/Codex bridges honor `updatedInput` instead of logging the faithful-but-degraded warning.
## Risks
- Rewriting the `assistant/message` tool-call block changes what the model "sees it said"; whether any provider rejects that on replay is the open question that must be settled empirically before the decision shape freezes.
- Until this lands, the unadvertised mutation path keeps its latent UI-desync inconsistency.
## Open questions
- Does rewriting the `assistant/message` tool-call block corrupt any provider's expectation on replay, or is a separate correction safer?

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
> Split out from the original "Doc-sync and API reports" RFC (2026-06-11). Parts 1-2 (doc-block typechecking, event-taxonomy verification) shipped — see [doc-sync enforcement](../../implemented/process/2026-06-11-doc-sync-enforcement.md). This is the deferred part 3, kept as a standalone proposal.
## Problem
@@ -14,6 +12,19 @@ Public API changes are invisible — nothing makes "this commit changed the publ
api-extractor (or `tsc --emitDeclarationOnly` + a normalized public-surface dump) producing a checked-in `etc/<pkg>.api.md` per package; CI fails if regeneration differs. Every public-API change becomes a diff line a reviewer (or review agent) must see.
## Status / why deferred
## Alternatives considered
**`tsc --emitDeclarationOnly` plus a normalized public-surface dump** — the lighter mechanism if api-extractor proves too heavy; either satisfies the checked-in, diffable report shape the proposal needs.
## Acceptance criteria
- Every package has a checked-in `etc/<pkg>.api.md`; CI fails when regeneration differs from the committed report.
- A public-API change (a new export, a widened field, a shifted signature) is visible as a report diff line in review.
## Risks
The dependency is heavy and finicky — the reason this was deferred — and the report format churns with compiler upgrades, adding a maintenance surface that buys little while the packages stay unpublished.
## Why deferred
Deferred when doc-sync landed: low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency. Revisit if the packages are ever published externally — at that point a stable, diffable public surface earns its keep.

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Problem
Two architectural guarantees currently live only in prose: (1) nothing depends on the concrete loop package ([the microkernel promise](../../implemented/architecture/2026-06-11-microkernel-event-taxonomy.md)), and (2) every LlmAdapter speaks the chunk protocol correctly. Both should be mechanical ([the quality-gates principle](../../implemented/process/2026-06-11-quality-gates.md)).
@@ -24,6 +22,13 @@ Two architectural guarantees currently live only in prose: (1) nothing depends o
dependency-cruiser config + CI step first (an hour of work, permanent guarantee); the conformance kit lands with its first consumer test against MockAdapter, and is a prerequisite for the V4 adapter phase.
## Acceptance criteria
- dependency-cruiser runs in CI with the rule families above; a violating import fails the build.
- The conformance kit runs against the mock adapter and both shipping adapters, and a new adapter package inherits the suite by invoking it with its factory.
## Risks
Dep-cruiser rule maintenance as packages are added — keep rules pattern-based (`dsh-*`) rather than enumerated.
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Problem
The vendor manifest ([the vendoring decision](../../implemented/process/2026-06-11-vendor-cordis-as-source.md)) is enforced at commit time in the *forward* direction (vendored change ⇒ manifest update) but nothing verifies the manifest's *claims*: that vendor/ actually equals upstream-at-SHA plus exactly the logged modifications. And the handful of true npm dependencies have no advisory monitoring or update cadence.
@@ -19,6 +17,17 @@ The vendor manifest ([the vendoring decision](../../implemented/process/2026-06-
3 is trivial — do first. 1 requires network access from CI to the upstream repos (private — needs a token) and converting the two existing logged modifications into patch files. 2 and 4 are config.
## Alternatives considered
- **`pnpm audit` instead of osv-scanner** — either satisfies the advisory-scanning shape; the choice is deferred to implementation.
- **A scheduled agent task instead of Renovate** — equivalent for proposing small update PRs that ride the full gate suite; vendored packages stay excluded either way (their updates follow the manifest sync procedure).
## Acceptance criteria
- The license inventory script runs in CI and fails on a missing LICENSE or a `license` field that contradicts the inventory in `vendor/README.md`.
- The nightly drift job reconstructs `vendor/` from the manifest SHAs plus checked-in patch files and fails on any unexplained diff.
- Advisory scanning runs on the lockfile on schedule and on lockfile-touching PRs.
## Risks
Upstream repos are private mirrors; CI credentials and availability are the main friction for the drift check. If blocked, run it as a local scheduled agent task instead of CI.

View File

@@ -27,6 +27,8 @@ Two of the cataloged items need no generator at all: folding the e2e entry glob
- `knip.json` carries a per-package override only where it encodes real information (an extra entry file, an ignored dependency), never a restatement of the default stanza.
- Snapshot scenarios declare policy, not facts discoverable from their fixture directories.
## What we give up
## Risks
Discovery scripts can become too clever. The implementation should stay boring: read manifests, filter on explicit fields, print the resolved list, and fail loud. The payoff is removing manual inventory drift, not inventing a build system.
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->

View File

@@ -28,7 +28,9 @@ Make an agent BE its session: one id. An agent's registry handle IS its `session
- The config path (`AgentLoop.create`) uses its configured `id` directly as the session id, applying whatever resume-or-create policy it adopts (today it appends a per-run uuid to avoid colliding with an on-disk log; that policy moves onto the single id, e.g. the config id IS the session and a durable backend resumes it — to be settled in the implementing PR).
- The registry's existing unique-`agentId` check becomes, by construction, a unique-session-id guarantee — the bash alias hole is closed with NO new defensive invariant: two agents cannot share a session id because the session id is the agent id.
## Why not just enforce session-id uniqueness in `AgentRegistry.register()`?
## Alternatives considered
### Why not just enforce session-id uniqueness in `AgentRegistry.register()`?
That was the review's first suggestion. It would couple the generic registry to a session-uniqueness assumption (the registry tracks *agents*, not sessions) and entrench the very separation this RFC removes. Unifying the ids closes the hole more cleanly — there is nothing left to enforce.

View File

@@ -16,7 +16,9 @@ Delete the method and its test; delete the three export lines and their `package
Sequencing: the in-flight surface-cache work (tool-pairing balance caching) neither uses nor touches `invalidate`, so that removal lands after or alongside it mechanically. The execute pipeline is `tools/pre-execute` → dispatch → `tools/post-execute`, and post-execute listeners receive the execution object alongside the result — nothing needs the result's own id.
## Why not keep them?
## Alternatives considered
### Why not keep them?
A future consumer that swaps a session's log in place would want a reset primitive — it re-adds `invalidate` with itself. A replacement-loop author might want to reuse the inbox or the driver — the architecture already answers that a replacement loop is a different bundle. An isolated result-logging listener might want self-contained correlation on the result — the execution object is in scope at every listener, and a field that exists only to be ignored is worse than absent: it invites exactly the orphaned-pairing bug the loop comment warns about.

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Problem
Several loop tests synchronize with `setTimeout(30)` sleeps — flakiness debt that wastes agent cycles on retries and can mask ordering bugs. Separately, our core architectural promise (any session log replays to identical derived history) is asserted in two tests but is cheap to assert *everywhere*. And the inbox wakeup race was verified by hand exactly once; nothing re-verifies it continuously.
@@ -20,6 +18,14 @@ Three measures:
Land 1 and 2 together (they touch the same helpers); add the nightly job after the suite is sleep-free so repeats are fast.
## Acceptance criteria
- No `setTimeout` remains in `packages/*/tests` outside the allowlisted helper module, enforced by the lint rule.
- The shared harness replays every test's session log into a fresh `Session` and asserts `deriveMessages()` equality automatically, across the whole suite.
- The nightly job runs the agent-loop and inbox suites with `--repeat` and `--shuffle`; a flake it finds is triaged as a bug, never retried away.
## Risks
Fake timers interact subtly with Promise scheduling in the loop — prefer event-driven waits; reserve fake timers for timer-service behavior itself.
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->

View File

@@ -2,8 +2,6 @@
Status: proposed
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Problem
The per-file 100% coverage gate ([the quality-gates decision](../../implemented/process/2026-06-11-quality-gates.md)) proves every line *executes* under test — not that any assertion would notice if the line were wrong. Under agent-written tests, coverage pressure can produce execution-without-assertion. Mutation testing measures what coverage cannot: whether the suite *kills* deliberately injected bugs.
@@ -23,6 +21,14 @@ Stryker (`@stryker-mutator/vitest-runner`) over `packages/*/src`:
2. Expand to all packages; record baseline scores in the config.
3. Wire the nightly job; add the incremental PR job once runtime is acceptable.
## Acceptance criteria
- A Stryker config runs over `packages/*/src` with the vitest runner; a nightly job records the mutation score, and a ratcheting threshold fails the run when the score drops below the recorded baseline.
- PR-scoped incremental runs gate merges once runtime is acceptable — or are explicitly kept nightly-only, with that outcome recorded here.
- Equivalent mutants carry annotated exclusions with reasons, mirroring the `/* v8 ignore */` policy.
## Risks
Runtime: mutation testing is expensive; per-file 100% coverage helps (every mutant is at least reached). If PR-scoped runs stay too slow, keep them nightly-only and rely on the score ratchet.
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->