Codex review caught three procedure defects: the dependent check used
bare 'gh pr list', whose default pagination can hide an open dependent
and make branch deletion look safe (now --state open --limit 1000); the
refresh step merged local master, which 'gh pr merge' leaves stale (now
fetch and merge origin/master); and the quick checklist omitted pushing
the dependent after the local merge.
Commit the stacked-PR landing procedure as a repo skill so every
collaborator's agent discovers it: merge bottom-up, retarget and refresh
each dependent, and delete no branch until the whole stack has landed
(deleting a base branch auto-closes the open PR that bases on it).
The responding-to-pr-review-on-a-stack cookbook guide's dependent-check
step links the skill as the full landing procedure.
ds-review-bot round 2, both warnings:
- subagent/provider-removed now routes through emitLifecycle (per-listener
containment, the subagent/start|end precedent) instead of raw ctx.emit,
whose dispatch halts on the first throw: a throwing subscriber can no
longer starve a later mirror into keeping a stale tool, nor disrupt the
backend fiber's teardown mid-disposer. provider-added deliberately keeps
propagation (register-time rollback semantics, like the system-prompt
registries); the asymmetry is documented on emitLifecycle, the event
JSDoc, and the provider-lifecycle RFC.
- The documented model-via-agent/request fallback composes with a
{{model}} persona via the ownership rule itself: the plugin supplying
the model late states it early on the system-prompt/assemble waterfall.
Declined re-ordering render after agent/request — it would break the
agent/pre-step contract (compaction must measure the prompt the model
sees). New loop test pins the supply path end-to-end; the RFC's
{{model}} consequence bullet now covers supply as well as switch.
This branch's two RFCs (prompt-variables, provider-lifecycle events)
conform to the new contract in the same merge: 'Status: implemented'
dateless header, 'Rejected alternatives' -> 'Alternatives considered',
'What we give up' folded into '## Consequences' (per the uniform-format
RFC's own rejection of a costs-only closer), and the index regenerated
into the new INDEX.md. docs/rfc/README.md resolved to master's rewrite.
Two findings from the pre-ready review, both verified:
- verify-rfc-format scanned raw lines, so an RFC quoting a Status line,
a banned heading, or the grandfather comment inside a fenced example
would false-positive. The content scans (duplicate Status, H2 headings,
banned headings, grandfather, legacy marker) now ignore fenced blocks;
the positional header-block checks stay raw. Verified: a fenced
'Status: implemented' + '## Plan' + grandfather quote inside a walked
RFC no longer trips the gate.
- The companion RFC's pre-format corpus counts were imprecise: 27
distinct Status spellings (reasons collapsed), not 'some fifteen';
nineteen implemented files carrying thirty proposal-era heading
occurrences, not 'over twenty files'; three English files (plus one zh
counterpart) with no status, not four.
The subagent/provider-added and subagent/provider-removed events carry a
decision of their own — reactive consumer registration instead of a
load-order requirement the cordis Loader never guaranteed — buried in a
section of the prompt-variables RFC. Extract it: the mechanism, the
Loader-concurrency evidence, the rejected alternatives (apply-time
lookup, polling, assemble-time wording, name-keyed wording), and what we
give up (the absent-tool window, the dup-toolName blast radius) move to
2026-07-05-subagent-provider-lifecycle-events.md; the prompt-variables
RFC keeps the context contract and points there; index regenerated.
Review round 2 (tianyicui inline comments):
- dsh-system-prompt itself registers the harness:identity (-100) and
deployment:persona (0) sections — they must survive a swapped loop
plugin, so they leave dsh-agent-loop; the persona text is the plugin's
own validated 'persona' config. The model/cwd variables STAY on the
loop: runtime facts of the agents it drives.
- AgentOptions.systemPrompt is deleted with all its forwarding plumbing:
the app configs' systemPrompt keys become 'persona' routed through
dsh-agent-core (schema = z.intersect of the owners'), the ACP bridge
and tool-subagent stop carrying persona configuration, and subagent
children now render the deployment persona like every other agent.
- Example personas drop transport/interface trivia (ACP, CLI) — facts
irrelevant to the model.
- Root CONTEXT.md removed (not idiomatic); its persona definition was
wrong under the new ownership anyway.
- Docs, READMEs, the prompt-variables RFC, and generated catalogs
updated; new loop test pins the assemble-waterfall escape valve
(an emptied assembly sends NO system field).
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.
Reconciliations beyond textual conflicts:
- product rename (DeepSeek Code -> DeepSeek Harness SDK) applied to the
PR-added assertion in system-prompt.spec.ts that master's rename
commit could not reach
- architecture.md: master's rewrite kept; this PR's prompt-assembly
semantics re-added in the new doc's voice (Turn Flow footnote +
service-spine row), within the 1630-word ceiling
- cordis catalog regenerated into master's split events.md/services.md
(events-and-services.md deleted); module graph and doc graphs
regenerated to pick up this PR's new events and dependency edges
A static harness:identity section at order -100 — the first occupant of
the documented negative band — states that the agent is powered by the
DeepSeek Harness SDK before the deployment's persona renders. Harness
attribution is a harness fact: it lives on the loop plugin, not in each
deployment's persona, so every agent (subagents included) carries it and
no YAML can forget it. A deployment that must drop it can remove the
section in the system-prompt/assemble waterfall.
Order-band docs updated in all five homes (PromptSection JSDoc, the
system-prompt and agent-loop READMEs, architecture.md, the RFC).
The AgentOptions.systemPrompt doc said the persona 'may reference'
variables without saying that every complete {{...}} group IS interpreted,
strictly, and that literal {{...}} prose has no escape syntax yet (the
RFC's recorded deferral). A persona author reads this line first; it now
carries the contract. (ds-review-bot inline finding: the escape mechanism
itself stays deferred per the RFC — pre-release, no external consumers,
and the failure is loud with the fix in the message.)
Codex round-2 findings:
- A committed test now proves the REAL plugin fiber (not a direct apply)
owns the provider-lifecycle listeners: disposing a mounted tool's fiber
unmounts the tool and leaves the provider intact, and a fiber disposed
while WAITING never zombie-mounts when its provider arrives later.
- TODO(subagent-dup-toolname) records the invalid-config blast radius of
two waiting fibers sharing a toolName (the duplicate throw propagates
through subagent/provider-added and rolls back the provider).
- CONTEXT.md drops its creation-history sentence; the RFC's acceptance
checklist becomes present-tense shipped invariants (docs/AGENTS.md
writing rules).
Codex round-1 findings, both confirmed:
- renderPrompt: variable lookup now uses Object.hasOwn (an unregistered
{{constructor}} previously resolved through Object.prototype and spliced
function source into the prompt), and a {{ that opens no complete group
while a }} still follows ({{{model}}}, {{a{b}}) now throws instead of
passing or partially interpolating. A lone {{ with no }} after it stays
verbatim; substituted values are never re-scanned.
- tool-subagent: the apply-time provider lookup assumed a load order the
cordis Loader does not guarantee (siblings start concurrently). The seam
now announces subagent/provider-added/-removed and the tool mirrors the
provider's lifecycle: registers when the provider is (or becomes)
available, unregisters when it goes away, re-derives wording on reload.
No load-order requirement remains.
- loop.spec containment test now proves live continuation: after the
contained render failure, a waterfall listener rescues {{cwd}} and the
same agent completes a real model turn.
RFC/READMEs updated to the shipped contract; cordis catalog regenerated.
The repo's docs are read by people and agents without access to the
company wiki, so external Feishu links are dead weight: the two design-doc
links (MVP requirements, microkernel implementation notes) leave
AGENTS.md's intro and docs/architecture.md (usages + link definitions),
and the microkernel-event-taxonomy RFC drops its plain-text pointer to the
same design doc. The governing principle each reference decorated is
already stated in place; no content moves.
Master split the cordis catalog (d55d4120) while this branch was in review.
Resolution: keep this branch's Service map restructure, adopt master's split
catalog link targets (events.md / services.md), and regenerate the catalog so
the waterfall-semantics anchor fix lands in the split output.
One principle: every fact in the assembled prompt has exactly one owner.
- dsh-system-prompt: merge-extensible AssembleContext on assemble();
a variable(name, provider) registry; {{name}} interpolation in
renderPrompt, strict (unknown/valueless/malformed references throw);
duplicate section and variable names rejected; assembly carries
resolved section text + variables through the assemble waterfall.
- dsh-agent declares AssembleContext.agent; dsh-agent-loop registers
the agent:persona section (order 0 - identity renders before tool
guidance) and the model/cwd variables, and drops its string join:
renderPrompt(assembly) IS the full prompt.
- Tool guidance moves to its owners: descriptions carry per-tool
semantics; sections only cross-call habits (tool:bash exit-code
habit at order 105; read's not-shell nudge). todo/subagent need no
section - their descriptions already carry the contract.
- SubagentProvider.inheritsParentContext (spawn/acp false, fork true);
dsh-tool-subagent derives truthful per-provider wording and resolves
the provider at load (backend must be listed first).
- Example personas shrink to identity + behavior with {{model}} (and
{{cwd}} in the ACP tree); the welcome banner stops enumerating tools.
RFC: docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md
Self-review sweep on the section NAME (not just anchors) found: the docs
index row in docs/AGENTS.md still advertised 'layering' among
architecture.md's contents, and the extract-example-app-packages RFC cited
'docs/architecture.md § Layering' for the dependency rule, which now lives
under § Service map.
The Layering ASCII diagram was cluttered, lumped THE concrete loop plugin
undifferentiated into a grab-bag plugins box, omitted dsh-agent-core and the
app packages, and restated the dependency rule that packages/README.md
already owns — while being the first thing a reader linked from README hits.
- Drop the Layering section; the tier story becomes one intro sentence and
the dependency rule a one-line Service map footer linking
packages/README.md#dependencies.
- Split the Service map into the spine (packages/core/) vs the swappable
capability seams; annotate ctx.agentLoop as THE concrete loop plugin.
- Promote Cordis waterfall semantics to a top-level section placed before
first waterfall use; drop '(important)' from the heading (anchor swept:
AGENTS.md, gen-cordis-catalog.ts + regenerated catalog).
- Promote Event taxonomy to a top-level section (anchor unchanged).
- Retitle 'The vocabulary (dsh-llm)' to 'Content blocks and streaming
(dsh-llm)' so the heading names its content (citation swept:
llm-streaming.md).
Net -29 words (1851 -> 1822); the 1890 ceiling stands, keeping the
manifest's working margin.
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
Merging master brought the producerless-vocabulary prune that removed
TurnTriggerMap's continuation variant: the source pointers above
SessionEventMap shifted by one line (the staleness CI caught on the
merge tree), and the new turn/start JSDoc still named the pruned
variant.
- The owning top-level interface SessionEventMap must now be the SINGLE
EXPORTED declaration in @deepseek-ai/dsh-session: a non-exported local
interface (even inside the owning package) and a second exported copy
are hard errors, so a same-named helper can no longer be catalogued as
the on-disk vocabulary.
- Any SessionEventMap declaration carrying an extends clause is a hard
error: inherited keys join keyof SessionEventMap but have no catalog
row, so heritage is a silent-skip path the gate must reject.
Three new spec cases; RFC and module doc updated to match.
- A SessionEventMap member that is not a property signature with an
explicit payload type is now a hard error instead of silently skipped —
a method-form or type-less member joins keyof SessionEventMap and must
not escape the catalog.
- A top-level interface SessionEventMap outside @deepseek-ai/dsh-session
(ownership read from the package manifest) is now a hard error — an
unrelated same-named local interface was previously catalogued as the
on-disk vocabulary.
- JSDoc tag detection runs on the trimmed line, so an extra-indented
'* @mode' can no longer bypass the forbidden-tag check and leak into
prose.
Four new spec cases cover these; RFC and module doc updated to describe
the enforced (not just assumed) invariants.
docs/persistence-catalog/log-events.md enumerates every SessionEventMap
member — the owning dsh-session vocabulary plus the dsh-compact and
dsh-hook-protocol declaration merges — with payload, surface/log-only badge,
JSDoc prose, and declaration site. scripts/gen-persistence-catalog.ts is a
pure AST pass in the gen-cordis-catalog mold: verify-persistence-catalog
(--check) joins doc-sync, so a stale committed catalog fails pre-push and CI.
The walk enforces JSDoc completeness (every member needs description prose;
@mode is rejected as a category error — log events do not dispatch on the
cordis bus), derives the surface badge from the SurfaceEventType union with a
stale-member cross-check, and hard-errors on duplicate declarations. Payloads
render through the TypeScript printer so newline-separated multi-line type
literals still emit valid one-line fragments.
Documented the five previously JSDoc-less core events (turn/step boundaries,
tool/call), removed the two stray @mode tags on the hook/* merges, and
replaced the hand-restated event enumerations (session.md hook/* table,
compact README table, hook-protocol README bullets, session README name-list
— whose merge note had already drifted) with links to the catalog. RFC:
docs/rfc/implemented/process/2026-07-04-persistence-log-catalog.md.