Commit Graph
58 Commits
Author SHA1 Message Date
Tianyi Cui eed43abe66 docs: address final model experience review 2026-07-14 00:22:52 +08:00
Tianyi Cui d77f797d89 docs: replace model experience tables with sections 2026-07-13 22:26:33 +08:00
Tianyi Cui 646f3d2d0c docs: apply verbatim model experience guidance repo-wide 2026-07-13 21:33:23 +08:00
Tianyi Cui 7195d9eda2 docs: gate concise model experience summaries 2026-07-13 15:47:46 +08:00
Tianyi Cui 7b539a0f8b docs: align model experience with runtime visibility 2026-07-13 15:00:47 +08:00
Tianyi Cui 5158cc3e0b Merge remote-tracking branch 'origin/codex/package-readme-limitations-audit-20260712' into codex/model-experience-readmes-20260712
# Conflicts:
#	packages/AGENTS.md
2026-07-12 23:54:00 +08:00
Tianyi Cui 6967c584d1 Merge remote-tracking branch 'origin/worktree-agent-scope-design' into codex/package-readme-limitations-audit-20260712
# Conflicts:
#	packages/core/scope/README.md
#	packages/session-persistence/session-persistence-jsonl/README.md
#	packages/session-persistence/session-persistence-sqlite/README.md
#	packages/subagent/subagent-acp/README.md
#	packages/subagent/subagent-fork/README.md
#	packages/subagent/subagent-inprocess/README.md
#	packages/subagent/subagent/README.md
#	packages/subagent/tool-subagent/README.md
#	packages/support/invariants/README.md
#	packages/support/subagent-mock/README.md
#	packages/workflow/workflow-workerthread/README.md
#	packages/workflow/workflow/README.md
2026-07-12 23:35:47 +08:00
Tianyi Cui 738054562d fix: complete scoped lifecycle simplification 2026-07-12 23:15:07 +08:00
Tianyi Cui bb3f6bd736 refactor(subagent): unify async readiness and cancellation 2026-07-12 22:41:59 +08:00
Tianyi Cui 875c3d62d8 fix(workflow): reap children after settled dispose 2026-07-12 10:39:55 +08:00
Tianyi Cui 9fc2260bb6 fix(workflow): harden terminal cleanup races
Queue worker results before settlement cleanup, claim terminal and death boundaries before provider callbacks, and close late-message admission.

Make child cancellation and disposal reentrancy-safe across the workflow bridge and generic subagent wrapper, with adversarial regression coverage and RFC documentation.
2026-07-12 10:17:31 +08:00
Tianyi Cui c5b1a7941f fix(scope): harden lifecycle ownership foundation
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.
2026-07-12 08:57:05 +08:00
Tianyi Cui 197f7237d2 fix(workflow): bootstrap source worker transforms 2026-07-12 06:19:53 +08:00
Tianyi Cui a9cb70d896 fix(scope): harden final ownership boundaries 2026-07-12 05:13:17 +08:00
Tianyi Cui 36b8370027 fix(scope): close remaining ownership boundaries 2026-07-12 03:51:55 +08:00
Tianyi Cui b5cd511f35 docs: address model experience review 2026-07-12 02:55:26 +08:00
Tianyi Cui a08485fc80 Merge remote-tracking branch 'origin/codex/package-readme-limitations-audit-20260712' into codex/model-experience-readmes-20260712
# Conflicts:
#	docs/cookbook/adding-a-package.md
#	docs/rfc/INDEX.md
#	package.json
#	packages/AGENTS.md
#	packages/bash/bash-sandbox/README.md
#	packages/sandbox/sandbox-local/README.md
#	packages/sandbox/sandbox/README.md
#	packages/session-persistence/session-persistence-sqlite/README.md
#	packages/support/acp-snapshot/README.md
#	packages/ui/app-boot/README.md
#	packages/ui/user-approval/README.md
#	packages/workflow/tool-workflow/README.md
2026-07-12 02:48:49 +08:00
Tianyi Cui 027970043c docs: document package model experience 2026-07-12 02:12:36 +08:00
Tianyi Cui 41a7c70cb1 docs: audit package limitations on current stack 2026-07-12 02:08:35 +08:00
Tianyi Cui 35715423f8 fix(workflow): honor subagent readiness boundary 2026-07-12 01:35:09 +08:00
Tianyi Cui 3529b3c166 fix(scope): close final ownership races
Drain idle injection flushes before agent teardown, snapshot approval and subagent provider inputs, and gate subagent lifecycle events on real child readiness. Align the RFCs and generated contracts with the hardened behavior.
2026-07-12 00:31:46 +08:00
Tianyi Cui 56e34619d7 Merge remote-tracking branch 'origin/master' into codex/pr224-rfc-rewrite
# Conflicts:
#	docs/architecture.md
#	docs/capability-seams.md
#	docs/config-catalog.md
#	docs/cookbook/extension-cookbook.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	docs/rfc/implemented/feature/2026-06-30-interception-seams.md
#	docs/rfc/proposed/feature/2026-06-14-acp-agent-client-protocol.md
#	docs/tool-execution-pipeline.md
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-core/README.md
#	packages/core/agent-loop/README.md
#	packages/core/tools/README.md
#	packages/core/tools/src/index.ts
#	packages/core/tools/tests/tools.spec.ts
#	packages/core/tools/tsconfig.json
#	packages/ui/acp/src/index.ts
#	scripts/doc-budgets.manifest.json
#	scripts/gen-cordis-catalog.ts
#	scripts/gen-doc-graphs.ts
2026-07-11 23:14:09 +08:00
Tianyi Cui 3263dab822 fix(core): enforce agent-scoped ownership boundaries 2026-07-11 22:55:26 +08:00
Tianyi Cui fc0357a100 Merge remote-tracking branch 'origin/master' into codex/pr224-rfc-rewrite
# Conflicts:
#	docs/architecture.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	scripts/doc-budgets.manifest.json
#	scripts/gen-doc-graphs.ts
2026-07-11 20:38:25 +08:00
Yichen Jiang e4be19b29e Merge remote-tracking branch 'origin/master' into codex/skill-system
# Conflicts:
#	packages/workflow/workflow-workerthread/tests/workflow-workerthread.spec.ts
2026-07-10 21:10:58 +08:00
pku-xht dd2f37b80f fix(workflow-workerthread): tighten post-result promptness waits back down
Review follow-up: the blanket 10s default correctly targets worker-thread
cold-start races (starting, first-script-line, async child-registration
messages — genuinely CPU-bound under CI contention), but four waits assert
something different — that the HOST reacted PROMPTLY to an event that
already happened (a settled result, an observed worker death). Those had no
cold-start left to wait on, so the generous default just widened the window
a real regression could hide in.

Verified by injecting a 6s delay into the settle-reap's abort call: the
un-overridden helper's test still passed in ~6s. The same mutation now fails
in ~1s with the explicit 1000ms override restored on all four sites (the
abort-on-settle test's own assertion, the two worker-death cancel/dispose
reap checks, and the dispose-ack-race check). The other 12 waits keep the
10s default — they run BEFORE a result is awaited, waiting on the worker to
actually start rather than on a host reaction. Doc comment corrected to
describe the split instead of claiming every wait is a cold-start race.
2026-07-10 20:48:14 +08:00
pku-xht 64b4e2ed2d test(workflow-workerthread): flake-proof the lifecycle spec's waits under CI load
The spec's 16 vi.waitFor sites used the 1s default timeout to wait for
worker-thread startup and child registration — CPU-bound work that blows
past 1s on a contended runner. The CI coverage lane (4 vitest workers plus
suites that spawn real subprocesses) hit this 3 times across 4 recent PR
runs, each a different subset of the cancellation/worker-death tests, each
green on rerun.

Every wait now goes through a shared helper with a 10s bound, and the file
sets a 30s test timeout to make room for it. The one deliberately tight
wait keeps its 800ms bound through the helper's override — it proves the
host (not the wedged worker's later loop turn) delivered the cancel, so a
generous bound would erase what it tests. No behavior under test changed.
2026-07-10 16:43:41 +08:00
Yichen Jiang b5b17a7f65 fix(ci): stabilize static and coverage gates 2026-07-10 15:21:22 +08:00
Yichen Jiang 680ae53bb6 Merge remote-tracking branch 'origin/master' into codex/skill-system
# Conflicts:
#	docs/architecture.md
#	docs/config-catalog.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	packages/core/agent-core/src/index.ts
#	packages/core/tools/tests/gen-tool-catalog.spec.ts
#	packages/support/acp-snapshot/src/suite.ts
#	packages/ui/acp-agent/src/index.ts
2026-07-10 14:43:33 +08:00
imccyu a53be53d64 workflow: forward TSX_TSCONFIG_PATH through the worker env scrub
The empty-env hardening wiped the one variable the UNBUILT worker's
loader depends on: tsx finds a tsconfig by searching up from the
worker's cwd, and a parent running outside the repo (the ACP snapshot
harness pins the repo tsconfig through TSX_TSCONFIG_PATH exactly
because its child cwd is a temp dir) lost the dsh-* paths map - the
worker then resolved workspace imports to unbuilt lib/ bundles and died
on CI with ERR_MODULE_NOT_FOUND (green locally only because stale built
lib/ masked the wrong resolution).

Forward exactly that variable when the parent carries it - loader
plumbing, not a secret; the built shape stays fully empty - and pin the
whole contract with an escape-based test: the worker env is exactly
{TSX_TSCONFIG_PATH}, the credential canary still never crosses.
2026-07-10 00:23:11 +08:00
imccyu aff657cc28 fix: ci run without build lib for snapshot 2026-07-10 00:16:29 +08:00
imccyu d12cb45838 workflow: spawn the worker with an empty environment
The documented vm escape reaches process, and the worker inherited the
harness's env - so a buggy or prompt-injected script could read and
exfiltrate ambient credentials (DEEPSEEK_API_KEY et al.) without
touching a single file (ds-review-bot finding on #233).

Spawn with env: {} and a hermetic execArgv on both runtime shapes, the
same stance as dsh-code-runtime-worker and stronger than the scrubbed
env the defensive-patterns rule requires for spawned commands (a shell
needs PATH; this worker needs nothing). Ambient-channel hardening only:
an escapee keeps the process-wide privileges the trust premise already
admits - the genuine sandbox remains an engine swap.
2026-07-09 23:57:43 +08:00
imccyu 64d0703c40 workflow: host-guarantee the agent-start/agent-end pairing on every stop path
agent-end was worker-authored only, so a start already forwarded to
observers lost its paired end whenever the worker could no longer speak
- the grace force-settle terminating a wedged script, or an unexpected
worker death - stranding progress consumers with agents that never
finish (ds-review-bot finding on #233).

The host now keeps a ledger of forwarded starts and funnels every
agent-end through one gate: worker-reported ends pair (and clear) their
entry, and both termination paths drain the remainder as synthesized
'cancelled' ends BEFORE the run settles, so ends always precede
workflow/end. A real settlement racing the force-settle loses to the
synthesized cancellation - the same first-wins override onResult applies
to the run's own result.
2026-07-09 23:05:43 +08:00
imccyu 6f86f6081b workflow: dispose() host-drives child disposal inside the grace 2026-07-09 21:14:30 +08:00
imccyu 0d0f0204f2 workflow: meta rides the seam as data — the engine never evaluates it
P1 review finding: extractMeta timed only the literal's vm evaluation;
materializing the RESULT then read properties ordinarily on the HOST
stack, so a meta literal smuggling a getter (get name() { while(true){} })
could wedge the host outside any timeout — defeating the exact spin
isolation the worker thread exists for.

Rather than harden the evaluator (descriptor walks, AST validation),
delete the mechanism: the workflow's identity now reaches the seam as a
plain JSON field (WorkflowStartRequest.meta), carried by the tool as a
schema-validated `meta` object parameter the model fills directly. The
engine only shape-validates data (validateMeta, every violation named)
and pre-parses the body; the scanner, the vm evaluation, and the
host-side materialization are gone, and with them the hole. A body
still opening with a Claude Code-style `export const meta` statement
gets a pointed SCRIPT_PARSE message (the likeliest authoring slip; a
CC script's body stays drop-in, only its meta header moves into the
parameter). syncTimeoutMs now governs exactly one thing: the initial
synchronous slice inside the worker.

The RFC's decision section is rewritten in place (implemented-RFC
rule); the embedded-meta format moves to alternatives-considered with
the hole as the reason. Tool description, presentation (title now reads
meta.name directly — the textual sniff is gone), seam vocabulary docs,
and catalogs follow.
2026-07-09 20:09:10 +08:00
imccyu af9616f47d test: let the wedged-worker regression post its child-start first
The regression's script spun immediately after calling agent(), but the
agent() continuation (which posts the child-start RPC) only runs on a
microtask tick — the spin seized the worker's loop before the post, so
the host never saw a child inside the waitFor window. A few await-null
yields before the spin let the RPC out; the posted message needs no
further worker-loop turns to reach the host, so the wedge still holds
for the Cancel message the test is about.
2026-07-09 19:33:08 +08:00
imccyu 0d11a3afc7 workflow: rename dsh-workflow-vm to dsh-workflow-workerthread
Pure mechanical rename now that the package's internals are the
worker-thread engine: directory, package name, spec/e2e filenames,
module tags and logger prefixes, tsconfig/knip/run-gates/AGENTS.md
references, example cordis.yml plugin ids, doc links; catalogs
regenerated and the lockfile refreshed.
2026-07-09 19:33:08 +08:00
imccyu d5c65e2b4c docs: describe the workflow engine as worker-thread first
The outer ring catches up with the engine swap (the package's own
README/JSDoc rode the port commit):

- Seam module doc and README name the worker-thread engine as THE
  implementation, with isolated-vm/separate-process sandboxing as the
  deferred hardening; the seam service doc states the holder-owned-runs
  contract (engine-fiber disposal deliberately leaves live runs to
  their holders).
- Seam contract precision: agentsStarted documents the termination-path
  degradation to the host-observed count; the events section scopes the
  agent-start/agent-end pair to calls that STARTED a child run;
  WorkflowRun wording drops the vm-era abandonment language.
- The dynamic-workflows RFC is rewritten in place to the shipped
  mechanism (implemented-RFC rule): why worker threads, the thread's
  concrete buys, the in-process node:vm first cut recorded under
  alternatives considered; the tool section describes the usage policy
  as the tool's own prompt section.
- gen-doc-graphs: six workflow/* DYNAMIC_EVENT_DISPATCHERS entries (the
  catalog no longer claims nothing dispatches them) and the seam-note
  wording; core-data-structures gains its workflow.md index row;
  packages/README + AGENTS.md layout line + example cordis.yml comments
  say worker-thread; catalogs regenerated.
2026-07-09 19:31:56 +08:00
imccyu b5f618bcfb workflow: swap the engine's internals to node:worker_threads
In-place port of dsh-workflow-vm from the in-process node:vm execution
to one worker thread per run (the workflow-workerthread engine of
PR #215, adopted as THE engine): the script's vm context moves inside
the worker, agent() bridges to ctx.subagents over the message port
(host.ts/protocol.ts/session.ts/worker.ts are new; runtime.ts loses the
abandon channel — the host's grace timer force-settles and TERMINATES
instead), start() pre-parses the body host-side to keep the seam's
synchronous SCRIPT_PARSE throw, and a ready→go handshake keeps a run
cancelled before start from ever executing the body. start() no longer
blocks the host, termination is real, and the value boundary is
serialization by construction. The package keeps its name until the
follow-up rename commit; scripts see the identical hook surface, and
the seam-contract tests hardened ahead of this swap pass unchanged.

The run and child-RPC surfaces are class-shaped rather than literal
bundles: WorkerRun IMPLEMENTS the seam's WorkflowRun (id/meta are its
own clone, separate from event payloads') and start() returns the
instance directly — interface parity with the seam is compiler-checked;
worker-side, ChildRpcBridge (implements ChildPort; callId allocation +
pending book-keeping settled by onChild* entry points) and
RpcChildHandle (every member an RPC keyed by its callId) carry names in
stacks. ChildPort's method is startAgent — it names what it starts,
matching the script-side agent() hook and the agentsStarted /
workflow/agent-* vocabulary; the Child* type names deliberately stay
(the worker side is cordis- and subagent-free; these are reduced JSON
projections, not the seam's types).

Review findings from the reference PR are folded in rather than
re-introduced:
- cancel() drives BOTH child-cancel channels host-side: the request
  signal aborts AND each registered child's explicit cancel() is
  called — a worker wedged in a synchronous spin cannot relay its own
  ChildCancel RPCs (regression: cancel-only provider + wedged worker).
- All host warn paths render through the total renderThrown; a child
  dispose() rejecting a value whose coercion throws still acks
  ChildDisposed instead of wedging the script's finally (regression).
- built-worker.e2e.ts is wired into builtBinSmokeGate and the AGENTS.md
  CI sequence — the built lib/worker.js resolution contract now runs in
  an automated gate.
- workflow/end payload pinned on the worker-death path (with the
  cancelled and grace-force-settle pins riding the ported spec).
- Real-Worker scripted timing budgets widened (50-300ms → 150-1000ms)
  for starved CI hosts.

Workspace plumbing: the "./worker" subpath export sanctions the second
runtime bundle (check-workspace-constraints), tsdown builds two
single-entry passes, tsx becomes a devDependency for the unbuilt worker
spawn.
2026-07-09 19:31:14 +08:00
imccyu fbc9eb313c workflow: harden the seam-contract tests ahead of the engine swap
Three engine-agnostic pins, landed BEFORE the worker-thread port so the
port commit demonstrates contract preservation against them:

- tool-workflow: the tool:<toolName> prompt-section registration was
  entirely unasserted — assemble() now pins the section present under
  the CONFIGURED name and gone after fiber dispose (the packages
  AGENTS.md dispose-and-assert-cleanup rule; tool-bash is the template).
- tool-workflow: drop the dead `??` re-defaulting of already-
  schemastery-resolved config (the hidden-fallback shape AGENTS.md
  bans) and the direct-apply test that existed only to cover those
  branches; both engines' `config as ResolvedConfig` is the pattern.
- workflow-vm: workflow/end was asserted only on completed runs — the
  cancelled path and the grace force-settle path now pin the event and
  its stopReason/error/agentsStarted payload (an observer's only death
  signal on those paths).
2026-07-09 18:16:51 +08:00
imccyu 4a15c8a479 workflow: make the seam's listener containment total
emitWorkflowEvent's catch rendered the thrown value with a bare
String(error), which itself throws when the value's toString /
Symbol.toPrimitive throws — breaking the documented containment
guarantee: such a listener could fail the run mid-emit, starve later
listeners, and turn the detached workflow/end settle hook into an
unhandled rejection. Render through a local total fallback instead
(String in a try, a fixed label when even coercion throws); local
because the seam sits below every engine and cannot import an engine's
renderer. Regression: a listener throwing a coercion-trap value — the
emit does not propagate and later listeners still run.
2026-07-09 18:13:34 +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 1f5db6e0b0 docs: satisfy the export-JSDoc gate across the workflow packages
Master's verify-export-jsdoc landed mid-stack; complete the six missing
@param/@returns on the workflow trio's public surface (and the services
catalog they regenerate into).
2026-07-07 22:39:05 +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
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
Tianyi Cui db4a39b024 workflow: close the review-found cancellation and child-lifecycle gaps
Three review findings on the engine's child seam, one mechanism each:

- Cancellation now bridges to run.cancel() on every in-flight child, not
  just the shared request signal — the subagent seam leaves a provider
  free to honor either channel, so the consumer drives both (listener
  removed in the child finally).
- A child result REJECTION (an infrastructure fault the seam allows) now
  emits the paired workflow/agent-end before propagating, and propagates
  as a fatal WorkflowError with the new AGENT_RESULT code — previously it
  skipped agent-end (permanently open child for seq-matching observers)
  and dissolved to a per-item null inside parallel()/pipeline(), letting
  a broken provider read as an ordinary failed child. A rejection landing
  after cancel stays a cancellation (cancelled outcome + CANCELLED).
- Every hook now guards its entry with a shared throwIfCancelled():
  phase()/log() no longer emit observer events after a script caught an
  earlier cancelled rejection, and parallel()/pipeline() refuse entry —
  cancellation is the next HOOK boundary, not just the next agent().
2026-07-06 21:02:41 +08:00
Tianyi Cui e70227d581 workflow: pin the spec engine's concurrency — the auto default is one slot on small CI runners
The two tests that wait for two children IN FLIGHT (the pipeline
no-barrier test and the dropped-promise cancellation test) inherited the
engine's auto-resolved maxConcurrentAgents, which is
min(16, max(1, cores - 2)) — exactly 1 on the 2-core CI runner, so the
second child never started and vi.waitFor timed out. Reproduced locally
under taskset -c 0,1; the full spec passes there with the helper pinning
a fixed ceiling. Tests about the ceiling itself keep their explicit
overrides, and the auto-resolve arm stays covered by the
default-config provider tests.
2026-07-06 20:59:47 +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 80250a8f2e workflow: drop the dead abandon-timer guard in drive()'s finally
The per-file branch gate caught it: drive()'s finally always cancels
first, and every first cancel() arms the abandon timer, so the
`!== undefined` guard's false arm was unreachable. clearTimeout
tolerates undefined by contract — call it unguarded.
2026-07-06 01:53:44 +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