Retarget the pruning feature onto the merged compaction foundation.
Adapt content-only tool-result rewrite validation to the session-owned surface manager, migrate the demo wiring to repl-agent, and refresh generated type, catalog, and website contracts while preserving pressure and overflow pruning behavior.
Combine agent initiator scoping and exact Cordis JSDoc inspection with post-step compaction and bounded request recovery.
Regenerate the Cordis and website API catalogs, and classify the recovery and compaction types required by the current catalog link-coverage gate.
Master's new duplication gate flagged 4 clones, all from the website scripts
reusing existing gate patterns by copy. Extracted the shared halves instead of
ignoring:
- scripts/cordis-walk.ts — cordisModuleBody / eventMembers / serviceClasses,
one AST walk consumed by both gen-cordis-catalog and gen-website-api (the
two renderers must agree on WHAT exists; now they structurally do).
- scripts/md-fences.ts — fence extractor with per-gate info-string
classification, consumed by doc-typecheck and verify-website-yaml.
Behavior-neutral: regenerated catalogs and website pages are byte-identical
(--check green), doc-typecheck/yaml block counts unchanged. jscpd now reports
0 clones; full pre-push run 34/34 green.
Conflict resolution notes:
- package.json/run-gates: both sides' new doc-sync gates kept (master's
scoped-events/readme gates + this branch's website-api/website-yaml);
js-yaml devDeps deduped (master added them independently).
- pnpm-workspace/knip: website AND python/sdk-runtime entries kept.
- doc-typecheck/verify-type-equiv: master's condensed headers kept, website
glob retained in both scan scopes.
- vendor/cordis/src/fiber.ts: master's lifecycle-hardening code taken; this
branch's richer FiberState JSDoc reapplied on top. vendor/README.md logs
both local modifications (hardening = 6, JSDoc enrichment = 7).
- pnpm-lock: regenerated from master's side (pnpm install).
Post-merge sync the gates forced (the system working as designed):
- verify-website-yaml caught 4 stale plugin names from master's package
reorg (dsh-stdio-agent -> dsh-stdio-demo, dsh-acp-agent -> dsh-acp-demo);
8 references fixed across guide/ and develop/.
- gen-website-api picked up master's 6 new services automatically
(ctx.approval/permission/sandbox/sessionQuery/skills/tasks -> 6 new pages
+ sidebar); api/index.md hub updated to list them.
- AGENTS.md budget ceiling 1370 -> 1400: the website rows (layout line + two
command lines) and master's own growth collided with the old ceiling; all
three website rows are load-bearing (new top-level dir, new CI command).
Comment-only enrichment across cordis/src/*.ts — Context, EventsService (+ the
ctx merges), Fiber, RegistryService, ReflectService, Service, logger — so the
website API generator can render a complete reference and hard-error on any
future undocumented member (vendor sync included). Logged as local
modification 6 in vendor/README.md; retire it when upstreamed to the fork.
INHERITED_SERVICES/EVENTS source pointers refreshed for the shifted lines;
cordis catalogs regenerated.
Refresh the background-task branch onto the latest repository baseline before applying review feedback. This preserves merge ancestry, incorporates the current CI and package-structure changes, and ensures the review fixes are validated against the code that will actually receive the PR.
glob/globSync returns host-separator paths on Windows. Nine scripts that consume these paths for split('/'), manifest-key comparison, startsWith/includes exclusion checks, or committed-output rendering now normalize with .map(s => s.split(sep).join('/')) at ingestion. This replaces the previous replaceAll('\\\\', '/') with an explicit, self-documenting OS-separator-to-POSIX conversion.
Make Cordis construction and teardown ownership reentrancy-safe, then carry caller and provider ownership through reservation, setup, publication, quiescence, and sentinel retirement.
Stabilize registry carriers and factory/workflow boundaries, add adversarial lifecycle regressions, and align the rewritten RFC plus generated contracts with the enforced behavior.
Emits packages/cordis/tool-cordis/src/api-catalog.ts (the data cordis_inspect
serves the model) from the same JSDoc-enforcing AST walk as docs/cordis-catalog
(collectServices/collectEvents, plus the now-exported INHERITED_SERVICES
table): service summaries + method signatures, event modes + signatures, and
the transitive closure of type shapes the signatures reference — so a mounted
plugin reads that a bash run's stdout is { text, truncated } instead of
guessing. verify-cordis-api joins doc-sync as the freshness gate.
Adds the missing core-data-structures coverage the catalog policy
requires for non-spine seam vocabulary: the code-runtime.md sub-page
with drift-checked type-equiv blocks for all six seam types, the core.md
sub-page row, the type-equiv manifest entries, and LINK_MAP entries so
the generated service signature links CodeRunRequest/CodeRunResult;
cordis/config catalogs regenerated.
New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
scripts/gen-config-catalog.ts walks every packages/<group>/<pkg> entry with
the TypeScript compiler API and emits docs/config-catalog.md: per loadable
plugin, the verbatim config declaration (JSDoc included) its apply/constructor
receives in a ts config-catalog fence, the inject requirements, resolved links
for every referenced type (package-local types pasted transitively, other
plugins' config types as intra-page anchors, LINK_MAP names to
core-data-structures, workspace types to source), and terse classification
lists for config-free plugins, abstract seams, and libraries — classification
is total, so a new package cannot go undocumented.
The walk enforces per-field JSDoc prose on every pasted declaration and
statically cross-checks the schemastery schema (z.object keys, z.intersect
composition across packages): every schema-validated key must be a declared
member of the config type. One violation existed repo-wide — the agents[].id
field in dsh-agent-loop — fixed by adding its JSDoc (which shifts the
cordis-catalog services page's source pointers; regenerated).
verify-config-catalog (--check) joins doc-sync; doc-typecheck learns the
ts config-catalog fence; gen-cordis-catalog exports its JSDoc/pointer helpers
and LINK_MAP for reuse. Negative-path spec in
packages/core/agent-core/tests/gen-config-catalog.spec.ts mirrors the
gen-cordis-catalog spec. Decision record:
docs/rfc/implemented/process/2026-07-06-generated-config-catalog.md (includes
the deliberate acceptance of README ## Config overlap).
The loop is now transmission-stateless; a request is a pure function of
(session log, this step's rendered assembly, current AgentOptions):
- The reconstruction boundary is step/start: the messages snapshot is
taken in the same synchronous frame immediately before the step/start
append, so the request's messages are exactly the derivation over
events[0..stepStartSeq) — an inject() from an agent/request listener
(or any concurrent task) lands after the boundary and joins the NEXT
request. This changes behavior for a synchronous step/start
session/event listener that appends content (master derived after the
append, so such a listener could reach the current request):
agent/pre-step is the sanctioned seam for current-request content.
- agent/request is re-typed to config-only: (agent, turn, step,
config: LlmCallConfig, next) → LlmCallConfig. The frozen seed comes
from AgentOptions on a loop instance's first request (explicit options
beat the logged baseline — fork overrides and resume reconfiguration
stay correct) and from the log's folded header afterwards; listeners
return a replacement to switch. Content shaping through the request is
no longer expressible — model-visible content flows through the log
channels.
- recordRequestHeader appends whatever header event the request owes the
log before dispatch: an 'initial'/'resume' snapshot anchoring each
loop instance, a round-trip-verified delta on change, a 'fallback'
snapshot when the encoding cannot express it. Session.requestHeader()
is the log's incrementally-folded baseline.
- Requests are deep-frozen before dispatch (deepFreeze exempts the
AbortSignal — freezing one breaks AbortController.abort() outright);
frozen + sessionId is the loop-built marker the dev invariant keys on.
Ported from #162 and re-anchored on the log: the append-extension /
frozen-end-to-end / compaction-resend / prompt-change property tests,
plus new specs for the boundary semantics, resume anchoring, and the
end-to-end theorem (every recorded request rebuilds byte-equal from the
log alone). Live cache-hit e2e (request-cache.e2e.ts) verified against
the real DeepSeek API. Snapshot goldens intentionally stale until the
single re-record after the compact/summary envelope lands.
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.
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).
gen-cordis-catalog now hard-errors (aggregated, not fail-fast) when an
event lacks description prose or a payload @param, or a public service
method lacks JSDoc, a @param per parameter, a @returns on a non-void
result, or an explicit return type annotation. The this receiver and the
trailing waterfall next are exempt on events (mode machinery owned by
@mode); a stale @param naming no real parameter errors, mirroring the
@mode contradiction check. parseJsDoc now ends prose at the first block
tag (standard JSDoc semantics), so the tags never change the rendered
catalog — only Source: line pointers moved.
Fills the ~139 gaps found across the 15 surface files, extends the spec
with negative-path fixtures for every new guard plus the exemptions,
records the decision as an implemented process RFC, and extends the
AGENTS.md typed-events bullet with the authoring rule. Runs inside
verify-cordis-catalog -> doc-sync, so CI and pre-push enforce it with
zero new wiring.
Rename per review naming decisions:
- package dsh-file-context → dsh-fs-policy (dir, package name, plugin name,
tsconfig refs, importers, type-equiv manifest, generated catalog + module-graph)
- events fs/write-expectation → fs/write-intent, fs/edit-expectation → fs/edit-intent
(fs/observed unchanged); type FsWriteExpectation → FsWriteIntent, "expectation"
wording → "intent" throughout
- exported FileContextExec → FsPolicyExec
Make the implemented RFCs describe what shipped, not the superseded designs:
the 2026-06-17 capability-seam + tool-schemas RFCs no longer place policy on
ctx.fs or use full/partial-view authorization, and the fsspec RFC's ctx.fileContext
service prose is rewritten to the fs/* event-gate reality (freshness-based auth).
Sharpen docs/rfc/implemented/AGENTS.md: a rename is a fact to fix IN PLACE — the
"new RFC" escape hatch is for macro decision reversals only, not renames.
Code fixes from review:
- fsio.ts resolveLocalTarget/probe translate ENOTDIR (a parent path segment is a
file) into the structured FsError taxonomy instead of leaking a raw Node error;
resolve reports FS_NOT_FOUND, probe reports absent. Regression tests proven to
fail on the unfixed code.
- tool-fs HMR test now asserts prompt sections (not just tool schemas) are
withdrawn on disposal.
- fs/observed is a plain (unguarded) ctx.emit: correct the fs-policy comment,
filesystem.md, and tool-fs module doc that wrongly claimed the tool "contains"
a throwing listener; a throw surfaces as the tool's isError result.
- drop the false "loaded by the default product config" claim (no config wires
the fs tools yet), the duplicate ctx.bash service-map row, the stale
FileReadRequest catalog link-map entry, and the fs/fs README EOF blank line;
correct the dsh-fs package.json description.
The Events intro carried "The harness declares N events across M scopes."
and the Services intro "The N `ctx.<key>` services the harness provides."
Both embed counts the generator recomputes from source, so every branch
that adds an event or service rewrites that one line — a guaranteed merge
conflict against any sibling branch that also touched the catalog, for
prose that adds nothing a reader can't get by scanning the page.
Remove the count clauses from the generator's render() and regenerate the
catalog. The freshness gate (verify-cordis-catalog) stays green.
Record the freshness token observed AFTER the read (re-stat post-read, falling
back to the routing stat if the file vanished) so the version returned/recorded
matches the bytes returned — a writer racing between the routing stat and the
read can no longer make a follow-up edit spuriously stale. Stream reads when the
backend reports no size, so a size-less backend never buffers a large file
whole. Update the cordis-catalog link map to the current filesystem API symbols
(FileContextExec/FileReadRequest/FileReadOutcome/FsInfo/FsWriteExpectation).
Codex round 1 CBR-001: a head-anchored compaction checkpoint was
mis-classified by the log-position step-alignment scan, so a second
auto-compaction over a checkpoint-headed surface silently failed.
Root cause: `isStepAlignedStart/End` scanned the LOG by seq, but a
`replace` op lands a checkpoint at a high log seq whose SURFACE position
is the head — its log neighbours (the open step's assistant/message) are
not its surface neighbours, so the forward scan wrongly reported mid-step.
Fix, per the agreed direction:
- Replace the two log-position predicates with one surface-anchored
helper `isToolPairingBalanced(nodes, events, beforeSeq)` in
`dsh-session` (renamed step-boundary.ts → tool-pairing.ts). A cut is
balanced when no unanswered tool-call precedes it on the surface; a
region is collapsible iff both edges are balanced cuts. The open-tail
and free-node cases fall out of the same counter. It also throws on a
corrupt surface (a tool/result with no matching call).
- Move compaction off the in-step seam to a new "pre-step" seam fired
after turn/start and before step/start, so a compaction's log-only
compact/* records and its replacement node land cleanly OUTSIDE any
step (the honest structure crash-safety relies on). Renamed the event
agent/pre-request → agent/pre-step and switched its dispatch from
parallel → serial (listeners mutate the surface as a side effect;
serial isolates them so concurrent appends can't interleave). Extended
the catalog generator to accept @mode serial.
Regression coverage: a real-loop test driving an auto-compaction asserts
the landed checkpoint is a balanced cut on both sides; unit tests pin the
checkpoint case, the mid-step injection case, multi-call steps, and the
corrupt-surface guard. Proven red on the old log-position logic.