Commit Graph

327 Commits

Author SHA1 Message Date
imccyu db857f62c0 docs(tool-cordis): state the sandbox stance as steering, not containment
The sandbox docs overclaimed a containment contract the design never makes:
"capability access is routed through cordis services, never Node built-ins,
so everything a mounted plugin does stays inspectable and disposable". The
host-realm helpers on the sandbox global (harness, console, btoa) are
reachable functions, so mount code that goes looking can reach the host realm
through one of them — accepted under the trust stance, because the ctx a
mount ultimately receives is fully privileged anyway. Reword the sandbox
module doc, the README trust stance, and the RFC sandbox-semantics section to
say exactly that: the traps and small global surface STEER honest code onto
the cordis services; they are not a security boundary.
2026-07-09 18:51:44 +08:00
Yichen Jiang 1e06fdbb86 refactor: colocate user interaction with ui packages 2026-07-09 17:59:02 +08:00
imccyu 3e9527278a fix(tool-cordis): gate façade services on inject, and make tools.get read-only
Two review findings (#220) on the sandbox context façade:

- Undeclared services were reachable: the façade resolved any live global via
  ctx.get(name), so ctx.bash worked without inject: ['bash']. A cross-mount
  consumer could then depend on a provider cordis never saw — unmounting the
  provider would neither park the consumer nor unwind its registered tools,
  leaving a model-visible tool that fails only at execution. The façade now
  reads ctx.fiber.inject and refuses any service the mount did not declare
  (with a teaching error naming the inject fix), so the dependency is always
  visible to cordis and its activation/unload semantics bind.

- ctx.tools.get returned the live ToolDefinition, including execute — mount
  code could call another tool directly and bypass ToolRegistry.execute and
  its pre/post-execute hooks and accounting. get now returns the same
  read-only name/description/parameters view as schemas(), never an invocable.

Adds inject-gate and schema-view regression cases to sandbox-context.spec.ts
(undeclared property/get denied, declared allowed, the cross-mount zombie-tool
scenario refused at call time, get exposes no execute). Package stays at
per-file 100% coverage. RFC, mount description, and tool-catalog updated.
2026-07-09 13:57:03 +08:00
imccyu 1b1ba96d4f fix(tool-cordis): replace the pass-through ctx proxy with a whitelist façade
Review finding (#220): the guarded proxy only special-cased ctx.tools, so
mount code could reach an UNGUARDED context through ctx.root, ctx.extend(), or
a service instance's .ctx, then ctx.root.tools.register({…}) to bypass the
marker check and host-realm normalization — a raw vm-realm result would later
error a real agent turn at the session-log plainness check.

The sandbox ctx is now a whitelist façade, not a pass-through proxy: it exposes
only what a mount needs — tools.register (marker-guarded), on/once, provide, the
timer helpers, and injected services resolved through a guarded get — and denies
every framework-plumbing member (root, parent, fiber, reflect, registry, extend,
isolate, intercept, plugin, set, mixin, …) with a teaching error. Injected
services are wrapped so a method returning a Context is rejected on the way back
(the .ctx escape), closing the one indirect leak. There is no context-valued
member left to reach; cross-mount provide/inject is untouched (the plugin's own
inject and the fiber's pending/active gating are unchanged). ctx.plugin (child
plugins) and ctx.set are denied by design; ctx.effect is deferred (FIXME).

Adds tests/sandbox-context.spec.ts covering the escape class (root/extend/fiber/
plugin/set/… denied, the classic root.tools.register bypass, the .ctx escape,
read-only writes) plus the async-service and symbol/in-operator paths for 100%
coverage. RFC/README/tool-catalog/config-catalog updated; api-catalog.ts
regenerated (also picks up the codeRuntime service that entered on the master
merge and was left stale).
2026-07-09 13:57:03 +08:00
imccyu ea66641b84 feat(tool-cordis): flatten the inspect plugins section to a capability list
The tree SHAPE was the wrong surface for the model: what it needs from
cordis_inspect is what services, plugins, and capabilities are loaded, not the
fiber hierarchy. The plugins section is now a flat name + lifecycle-state list
from ctx.registry (deterministically sorted, one line per instance); the ASCII
tree renderer, the parent→child rebuild, and the dyn-id tree annotation are
deleted — dynamic mounts keep their own richer dynamic section (id, state,
provides, waits). Net -49 lines; RFC and READMEs state the flat-list contract.
2026-07-09 13:57:03 +08:00
imccyu a500c791f7 fix(tool-cordis): normalize the JSON-Schema dialect at the defineTool boundary
Field sessions showed models writing tool schemas in the JSON-Schema dialect
by strong prior — type: 'integer', required: false, then the full
{ type:'object', properties, required: [...] } wrapper — and the rejection
text itself pushed a nearly-correct DSL attempt BACK to raw JSON Schema: one
stats tool cost three consecutive schema errors before mounting. The boundary
now normalizes wherever the input has exactly one meaning (wrapper unwrapped
with the required array becoming per-property flags at any nesting level,
integer → number, required: false → optional, all rebuilt as fresh host-realm
objects) and rejects only genuinely meaningless input, enumerating the valid
vocabulary in the error. Re-running the failing session mounts first-try.
The mount description documents both accepted forms.
2026-07-09 13:57:03 +08:00
imccyu 68ebc76af7 docs(rfc): the self-referential cordis toolset
The design record for tool-cordis: the three-tool contract, the vm sandbox
trust stance and boundary mechanisms, the dynamic-group lifecycle, cross-mount
provide/inject composition, the generated runtime API catalog, and the
alternatives weighed (per-capability registration tools, hand-maintained API
tables, a mount provenance event, a hardened sandbox).
2026-07-09 13:43:31 +08:00
Tianyi Cui 9fddbac095 refactor: unify the Code Mode demos on base-plus-overlay
Both demo:code-mode UIs now share one mechanism: the base example plus a
same-shaped code-mode.cordis.yml include overlay (insert the worker
runtime, flip tools.mode). Previously the REPL side was a hand-forked
example (examples/code-agent) that had also silently diverged — it
dropped compaction and the subagent stack — so the demo's UI argument
switched agents, not just surfaces. The fork is retired: coding-agent
gains the overlay, a Code Mode README section absorbing code-agent's,
and both of its tests (the keyless boot guard, retargeted at the
overlay; the with-key RFC proof, which hand-mounts its own harness and
moves untouched). The RFC's composed-surface and e2e-tier lines, the
examples index, the AGENTS.md smoke table, and the dsh-tools README
link now describe the overlay shape.

Verified live: overlay keyless smoke, with-key code-mode e2e from its
new home, demo:code-mode banner + EOF exit, and the acp handshake.
2026-07-09 13:02:19 +08:00
Tianyi Cui bc7da642d4 feat: fold the Code Mode demos into demo:code-mode with a UI argument
Code Mode is the point; the UI is just the surface it happens to wear.
demo:code and demo:acp-code collapse into one dispatcher
(scripts/demo-code-mode.mjs): `pnpm run demo:code-mode [repl|acp]` —
repl (default) boots the stdio REPL over examples/code-agent, acp
serves examples/acp-agent's code-mode overlay; each UI runs the exact
node invocation its standalone script ran, and an unknown argument
fails loud with usage. All nine references across READMEs, the RFC,
the overlay header, and the keyless-smoke comment renamed. Smoked all
three paths: usage exit 2, ACP initialize handshake, REPL boot + EOF.
2026-07-09 12:16:37 +08:00
Tianyi Cui 673f20c990 Merge remote-tracking branch 'origin/master' into code-mode-tools
# Conflicts:
#	docs/event-producer-consumer.md
#	packages/support/acp-snapshot/tests/suite.spec.ts
2026-07-09 11:06:50 +08:00
Yichen Jiang 56091d5b5d fix review findings: keep ask-user opt-in for acp app 2026-07-09 00:42:36 +08:00
Yichen Jiang f2c333e510 docs(rfc): record the session-prefix decision
The seam shipped without its own RFC — the reconstructable-requests RFC
was amended with the mechanics, but the decision record (why a
compose-once frozen prefix, and what the per-request before/after shape,
a system-prompt section, a durable history opener, per-turn composition,
and a dedicated session event each lost to) had no home. Implemented
lifecycle, feature class, dated to the first commit of the work.
2026-07-09 00:40:57 +08:00
Yichen Jiang b31399bdc4 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/module-graph.md
2026-07-08 22:22:25 +08:00
Yichen Jiang f453ba77a2 refactor: split skill providers 2026-07-08 15:50:38 +08:00
kingwl e491759f30 fix review finding: cap the detailed reminder's argument payload 2026-07-08 14:40:08 +08:00
kingwl a581963070 docs(rfc): promote the repeat-tool-guard RFC to implemented 2026-07-08 14:24:20 +08:00
Tianyi Cui b59d245c7c feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.

run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).

The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
Yichen Jiang d3f86996f7 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/config-catalog.md
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	pnpm-lock.yaml
2026-07-08 11:09:20 +08:00
imccyu 773ecf03f5 workflow: drop the determinism bans (unimplemented-resume pre-support)
The Date.now()/Math.random()/argless-new-Date() bans existed solely to
keep scripts resume-compatible for the deferred journaling/resume
feature. Pre-support for an unimplemented feature is speculative cost:
scripts may now read the clock freely; implementing resume reintroduces
the bans as a script-contract tightening. The RFC's shipped-state
description is updated in place, the tool DESCRIPTION drops the
constraint sentence (the pinned text-turn header follows), and the
engine README's trust-premise paragraph now leans on absent globals
alone.
2026-07-08 00:51:19 +08:00
Tianyi Cui a706ec7966 Merge branch 'structured-output-subagent-seam' into worktree-dynamic-workflows
# Conflicts:
#	docs/rfc/INDEX.md
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
2026-07-07 23:10:50 +08:00
Tianyi Cui 8db99d36e8 Merge branch 'structured-output-subagent-seam' into worktree-dynamic-workflows
# Conflicts:
#	examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-policy-reject/stdout.golden.jsonl
2026-07-07 22:21:44 +08:00
imccyu 161275e287 feat: add assembly-time validation rejects placeholder 2026-07-07 22:03:12 +08:00
imccyu adbba0deb2 fix(system-prompt): reject a toolOrder that names an unregistered tool
Review follow-up (#196): a listed name with no registered tool was silently
ignored; misconfiguration must block work instead. The check lives in the
assembly — the earliest moment the registered tool set exists (tool plugins
register after the service constructs) and the only universal one (cordis has
no "all plugins loaded" event; registrations change at any time). assemble()
is now async so the throw surfaces as a rejection rather than a synchronous
escape from a Promise-returning method.

Blast radius, pinned by a loop-level test: the rejection reaches the turn's
outer catch — the turn closes balanced with an `error` reason, agent/error
mirrors it, no step opens, no request/header is logged, no request reaches
the adapter, and the agent returns to idle; every turn fails identically
until the config is fixed. A boot-time validation pass was considered and
rejected (recorded in the RFC). The general principle — misconfiguration
fails loud, never a silent skip — is added to AGENTS.md.
2026-07-07 20:49:42 +08:00
imccyu 72933ec558 refactor(system-prompt): rename TOOL_ORDER_REST from '...' to '<unlisted-tools>'
A three-dot rest entry reads as elision in a cordis.yml; the spelled-out
sentinel says what lands there. The literal now appears once in code (the
constant) and once in the value-pinning test; every other reference — the
forwarding tests included — imports TOOL_ORDER_REST, which adds the
dsh-system-prompt devDependency to the two app packages.

Review follow-up on #196.
2026-07-07 20:49:35 +08:00
Yichen Jiang 7065b32e0b Merge branch 'master' into codex/ask-user-question 2026-07-07 19:42:25 +08:00
Tianyi Cui e930dd328a Merge branch 'master' into worktree-llm-tool-order 2026-07-07 17:01:32 +08:00
imccyu b67cda482a fix: update snaphsot 2026-07-07 16:45:27 +08:00
Tianyi Cui afab88b139 docs(rfc): record the schema-validation alternatives considered
The #192 review discussion, preserved where design rationale lives: a
schema-object library (zod/schemastery) cannot sit at a wire-data boundary;
ajv replaces only the value walker while the subset gate — the module's
point — stays hand-written; provider JSON mode guarantees valid JSON, not
schema-conforming JSON, and would trade away mid-run tools and in-turn
validation retry for it. Strict tool schemas are named as the accepted
upgrade path when the provider ships them.
2026-07-07 16:22:57 +08:00
Yichen Jiang cebf781d69 fix review findings: polish ask-user question 2026-07-07 14:57:06 +08:00
imccyu 42e7a2f691 fix: add tools reorder to system prompt 2026-07-07 11:03:11 +08:00
Tianyi Cui 4641f1a851 chore: absorb the no-nudge seam into the workflow tree
The parent branch dropped structuredNudgeRetries; the two workflow-side test
setups stop passing it, and the RFC's foundation paragraph now describes the
current design (final-ASSEMBLY enforcement logged via request/header, the
post-capture pre-execute deny, the start() schema snapshot, no re-prompt)
instead of the retired agent/request + nudge shape.
2026-07-07 09:26:47 +08:00
Hypatia May c7fba41ba9 Inline session fork parameters 2026-07-07 09:04:49 +08:00
Tianyi Cui 9688870da3 docs(workflow): admit the node:vm escape concretely — absent globals are surface, not containment
The review's critical finding: the engine README and the tool
description read as if the missing filesystem/network/Node globals were
enforced, but a script can reach the host Function constructor via
globalThis.constructor.constructor and from it process and every Node
builtin. Per the trust premise this is ACCEPTED (model-written scripts,
bash-equivalent trust; genuine sandboxing is the deferred engine swap
already listed) — but the docs must say so instead of implying a wall.
The trust-premise sections (engine README, module doc, RFC) now name
the escape and its acceptance; the model-facing tool description says
the APIs are not PROVIDED rather than implying they are prevented.
2026-07-06 21:04:20 +08:00
Yichen Jiang 0ba14f4a3c Merge remote-tracking branch 'origin/master' into codex/skill-system
# Conflicts:
#	docs/event-producer-consumer.md
2026-07-06 17:11:35 +08:00
Yichen Jiang 426d65a2a2 fix: honor fs when locating skill project roots 2026-07-06 15:59:12 +08:00
Yichen Jiang a3b0da0255 Merge origin/master into project instruction files 2026-07-06 15:44:42 +08:00
Hypatia May 71f71816b9 fix: keep fork boundary check simple 2026-07-06 14:17:31 +08:00
Hypatia May 37aac7f313 fix: collapse session fork to one api 2026-07-06 13:57:59 +08:00
Yichen Jiang 901315d8ab Preserve instruction symlink guard through fs seam 2026-07-06 13:55:20 +08:00
Hypatia May 0bf8749128 fix: fold session fork into session store 2026-07-06 12:35:34 +08:00
Hypatia May cf036299f3 Merge remote-tracking branch 'origin/master' into session-fork
# Conflicts:
#	docs/architecture.md
#	docs/cordis-catalog/events-and-services.md
#	docs/core-data-structures/core.md
#	docs/module-graph.md
#	docs/rfc/README.md
#	packages/README.md
#	tsconfig.base.json
2026-07-06 12:20:00 +08:00
Yichen Jiang 3c42352310 Fix project instructions fs provider seam 2026-07-06 11:15:58 +08:00
Yichen Jiang 1934035d04 docs: update skill system RFC format 2026-07-06 10:31:45 +08:00
Yichen Jiang 6a6cb43c71 Merge remote-tracking branch 'origin/master' into codex/project-instruction-files
# Conflicts:
#	docs/event-producer-consumer.md
#	docs/rfc/README.md
#	packages/core/agent-core/src/index.ts
#	packages/ui/acp-agent/src/index.ts
2026-07-06 10:14:47 +08:00
Yichen Jiang 545b2775db Merge remote-tracking branch 'origin/master' into codex/skill-system
# Conflicts:
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	docs/rfc/README.md
#	packages/core/agent-core/package.json
#	packages/core/agent-core/src/index.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/index.ts
#	packages/ui/acp-agent/src/index.ts
#	packages/ui/acp-agent/tests/acp-agent.spec.ts
#	packages/ui/stdio-agent/README.md
#	packages/ui/stdio-agent/src/index.ts
#	packages/ui/stdio-agent/tests/stdio-agent.spec.ts
2026-07-06 10:09:26 +08:00
Yichen Jiang 7d7c4a3c16 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/rfc/README.md
#	packages/ui/acp-agent/src/index.ts
2026-07-06 10:08:58 +08:00
Tianyi Cui a8986c2c8a Merge remote-tracking branch 'origin/master' into worktree-dynamic-workflows
Beyond the mechanical conflicts (provider capability lines vs master's new
inheritsParentContext field; generated catalogs regenerated rather than
hand-merged; knip/lockfile), three master-side reworks required semantic
adaptation of this branch:

- The persona rework removed AgentOptions.systemPrompt, which was the
  structured-output instruction's channel. The instruction now rides the
  SAME final-request enforcement listener that injects the schema'd tool:
  appended per request to final.system (per-request wire state, not agent
  prompt state). Tests assert the wire request (adapter.requests) instead
  of child.options; the bare-direct-dispatch test pins the no-system arm.
- Tool guidance moved out of deployment prompts into per-tool prompt
  sections; the examples' workflow paragraph became a tool:<toolName>
  section contributed by dsh-tool-workflow (explicit-ask-only policy),
  and both example personas resolve to master's minimal identity+behavior
  form. tool-workflow gains inject: systemPrompt (+ peer dep, tsconfig
  ref); the export-shape guard updated.
- The uniform-RFC-format gate: the dynamic-workflows RFC restructured to
  the implemented/ skeleton (bare Status line; Proposal -> Decision;
  What-was-rejected -> Alternatives considered; new Consequences), and
  the overall-run-timeout deferral is now recorded in the RFC's Deferred
  list. The doc-graphs atlas classification gains the workflows seam
  (workflow-vm implementation, tool-workflow consumer).

Master's harness-identity section made "empty assembled prompt" states
unreachable through the loop, so the instruction-append is a plain
undefined-ternary and the structured tests assert append-not-replace.
All snapshot goldens (including workflow-run) replay unchanged. Full
local CI-equivalent gate sequence green on the merged tree.
2026-07-06 03:14:07 +08:00
Tianyi Cui 706691a7df workflow: re-check cancellation after the slot acquire
Codex convergence round 1 on the review-response commits: agent()'s
post-acquire window was real and unguarded. `await acquireSlot()` yields
at least one microtask tick even when a slot is free (and a queued
waiter resumes a tick after its release), so a cancel() landing in that
tick let the continuation start a child carrying an ALREADY-aborted
signal — the in-code comment claimed the window could not exist. A
provider that subscribes only to future abort events (the test stub;
the seam does not promise pre-aborted-signal handling) would never
settle such a child, leaking it until the dispose grace abandoned the
run, and a backend that misses the pre-aborted signal would burn a full
model turn after the user cancelled.

agent() now re-checks isCancelled() immediately after the acquire
(inside the slot-owning try, so the finally still releases), making
every post-cancel path reject before subagents.start. New deterministic
regression: cancel() in the same synchronous frame as start() lands in
the free-slot await tick — the run settles cancelled with ZERO children
started (previously: one leaked child and a grace-delayed settle). The
raced-release test's comment now states what it actually pins (the
queued-waiter rejection path). Also aligns the RFC's auto-concurrency
formula with the code (min(16, max(1, availableParallelism() - 2))).
2026-07-06 01:39:01 +08:00
Tianyi Cui 2accf85714 workflow: simplify to the trust premise; settle result on cancellation
Two review responses that belong together — the same review argued the
engine was defending the wrong threat while a benign-input bug wedged
the product.

1) Drop hostile-value containment; state the trust premise.

Scripts are model-written — the same trust level as the model's bash
access — yet successive pre-push review rounds had ratcheted in defenses
that only matter against an adversarial author: trap-free proxy
rejection, accessor-never-invoked descriptor walks, realm-side
pre-rendering of thrown values, realm-built promises/arrays/error clones
with structural fatal recognition. That same author keeps a documented,
accepted, unkillable event-loop spin, so containing its error VALUES is
cost without a threat model — and the planned hardened engine
(worker/isolated-vm) gets value isolation by serialization and deletes
all of this machinery anyway.

What stays, because benign scripts hit it constantly: result never
rejects; dropped hook promises cannot become unhandled rejections; the
value boundary rejects LOUD everything JSON cannot carry (now a plain
recursive walk — getters are read ordinarily and their result is what
crosses; a throwing read fails loud); a "__proto__" key still copies as
a data property; the fatal-vs-null combinator discipline (now host
instanceof — unforgeable from the realm and simpler than clone-shape
recognition). What changes for scripts (documented in the engine
README): hooks hand back host values and host errors — in-script
`instanceof Error` on a hook failure is false (branch on e.name/e.code)
— and args are host-cloned once so a script cannot mutate the caller's
object. realm.ts drops 289 → 173 lines; the hostile-value test tables go
with it. The premise now leads the engine module doc, the README, and
the RFC's engine section, with the removed machinery recorded under
What was rejected.

2) result settles within the dispose grace of a cancellation.

Review finding (verified through the real registry + tool + engine): a
script parked on a promise no hook owns — `await new Promise(() => {})`,
`await Promise.race([])`, a returned never-settling thenable — could not
be settled by cancel(): hooks reject and children abort, but nothing
touches a promise the engine does not own, so `result` stayed pending
FOREVER (the previous cut even pinned that as intended). The tool awaits
run.result BEFORE its disposing finally, the registry awaits the tool,
the loop awaits the registry — one such script wedged the whole agent
turn past any abort, unrecoverable in-process; the mock engine in the
tool's abort test settles result on cancel, which is exactly the
behavior the real engine lacked, so no existing test could see it.

The seam contract now says it out loud: once a run is cancelled, result
SETTLES within the implementation's bounded grace even if the script
never does. The vm engine arms an abandon channel in cancel(); drive()
races the script against it, force-settling 'cancelled' at the grace
(the abandoned settlement stays contained; a post-slice synchronous spin
remains the documented limitation). dispose()'s outer race now exists
for child quiescence only, and `workflow/end` again fires exactly once
per started run. The old 'result stays pending' pin is FLIPPED to the
new contract (the pinned behavior was the bug); new regressions cover
cancel-then-settle on a parked script, a never-settling returned
thenable, and the full composition through the REAL registry + tool +
vm engine (tool-workflow gains workflow-vm/subagent devDeps for it).
agentsStarted JSDoc clarified while touching the vocabulary (accepted
calls, including ones still queued at cancellation).
2026-07-06 00:48:49 +08:00
Tianyi Cui e6fad266a6 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.
2026-07-05 22:58:25 +08:00