Commit Graph
5560 Commits
Author SHA1 Message Date
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 65d644b161 invariants: a loop-built request must be exactly what the log reconstructs
The dev-mode cross-check on llm/stream: a frozen request with a live
sessionId (the loop-built marker; hand-built one-shots stay unfrozen
and skipped) must carry messages deep-equal to the derivation over the
log prefix strictly before the in-flight step's step/start, and header
fields equal to the fold of the log's request/header* events. The
messages side rebuilds through a FRESH Session over the boundary
prefix — same projection code, zero shared state — so the live cache
cannot vouch for itself, and the seq-bounded rebuild is boundary-
correct: content appended after step/start (an agent/request-window
inject) legitimately belongs to the next request and cannot false-fire
the check. prepend:true only defends against the replay adapter's
short-circuit (append-registered); correctness never rests on listener
ordering. There is no divergence-allowance caveat: nothing can shape
request content outside the log.
2026-07-06 03:14:07 +08:00
Tianyi Cui 3464f88297 test: shorten compaction e2e smoke 2026-07-06 03:12:37 +08:00
Tianyi Cui 1b9408268b ci: cache eslint lane 2026-07-06 03:07:35 +08:00
Tianyi Cui 2093a8898b loop: every request is built from the log — boundary snapshot, header events, config-only waterfall
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.
2026-07-06 03:07:34 +08:00
Tianyi Cui d71c96a44c ci: keep lint lane serial 2026-07-06 02:59:50 +08:00
Tianyi Cui 09326fd84a ci: split lint lane internally 2026-07-06 02:56:23 +08:00
Tianyi Cui a4f7e757fa session: cache the derived history — one projection per node, frozen and shared
deriveMessages() no longer re-derives and re-clones the whole surface
per call: each node is projected exactly once, when first seen (O(new
nodes) per access), through the now-public deriveEventMessage — the
per-node pure function external reconstructors and the dev invariant
fold over a log prefix, so no two paths can disagree about a request's
messages. A surface rewrite (replace, invalidate) rebuilds, signalled
by SurfaceManager.replaceGeneration — monotonic, bumped by every folded
replace and by invalidate(), never reset. Callers get a fresh array
snapshot per call over SHARED deep-frozen messages: isolation of the
append-only log moves from per-call cloning to unrepresentability
(mutation throws), which the two isolation tests now pin directly.
2026-07-06 02:51:20 +08:00
Tianyi Cui 133be74b15 ci: tune static lane concurrency 2026-07-06 02:46:58 +08:00
Tianyi Cui 5efc449af0 ci: keep static lane source-only 2026-07-06 02:43:42 +08:00
Tianyi Cui a7b569850b session: the request header becomes logged state — request/header events + fold/diff/apply
Every conversation request's non-content half (system prompt, tool
schemas, call config — the EpochHeader) is now recorded in the session
log: a 'request/header' full snapshot (reason 'initial' | 'resume' |
'fallback') anchors the fold at conversation birth and process
boundaries, and 'request/header-delta' events (system line-trim,
name-keyed tools delta, whole config) encode mid-run changes. The pure
trio — foldRequestHeader / diffHeader / applyHeaderDelta — reconstructs
the header any request was built under from the log alone; the writer
contract round-trip-verifies every delta with a 'fallback' snapshot
when the encoding cannot express a change (pure tool reordering), so a
well-formed log always folds cleanly. Canonical absence: empty system
and empty tools normalize to absent fields, matching request builds.

Persistence and cordis catalogs regenerated; SessionEventMap paste and
EpochHeader added to the core-data-structures session page.
2026-07-06 02:42:51 +08:00
Tianyi Cui 2d80d868be ci: isolate lint and tune coverage lane 2026-07-06 02:39:26 +08:00
Tianyi Cui 3f09159ab8 llm: LlmCallConfig + callConfigEquals + deepFreeze — the stateless request vocabulary
The call configuration (model + sampling scalars) becomes named
vocabulary: per-conversation state that the session log records as part
of the request header (the reconstructability RFC on this branch), with
callConfigEquals as the real-change detector behind logged header deltas
and deepFreeze as the ownership helper the loop applies to every built
request. dsh-llm stays stateless — request in, chunks out; no
conversation object lives here.
2026-07-06 02:27:54 +08:00
Tianyi Cui 7cd4868056 ci: split primary gates into broad lanes 2026-07-06 02:22:22 +08:00
Tianyi Cui 8510986909 compact: summarize is a direct one-shot llm/stream call, not a loop step
The summarization request no longer dispatches agent/request — that seam
shapes the loop's conversation requests; a hand-built one-shot's
interception surface is llm/stream, like every other direct call. The
model comes from summarizationModel falling back to the agent's own. The
turn/step parameters existed on the compact seam only to feed the
agent/request dispatch and leave compactIfNeeded/compactRegion.

Groundwork for making every conversation request a pure function of the
session log (reconstructability RFC, this branch): the seam split keeps
the loop's request path as the single thing the log must explain.

Ported from worktree-prompt-prefix-stability (PR #162) where it was
review-converged; catalog and producer/consumer graphs regenerated.
2026-07-06 02:16:11 +08:00
Tianyi Cui c64eca451c ci: ignore doc-typecheck temp dirs during lint 2026-07-06 02:04:32 +08:00
Tianyi Cui d9c401d27a Merge remote-tracking branch 'origin/master' into codex/parallelize-ci 2026-07-06 01:59:49 +08:00
Tianyi Cui 44664551cf test: wait for ACP stdout frame under e2e parallelism 2026-07-06 01:54:09 +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 0452261b5b Merge parallel pre-push gates into CI scheduler 2026-07-06 01:47:13 +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 9c0cd392b9 Merge pull request #176 from deepseek-harness/worktree-add-merging-stacked-prs-skill
docs: add the dsh-merging-stacked-prs skill
2026-07-06 01:28:43 +08:00
Tianyi Cui 282215f3fa Merge remote-tracking branch 'origin/worktree-add-merging-stacked-prs-skill' into worktree-add-merging-stacked-prs-skill 2026-07-06 01:21:48 +08:00
Tianyi Cui 89e7005cfd docs: state the dependent check's contract as zero-versus-non-zero
Verified against gh: --base filters server-side and exactly, so any
open dependent guarantees a non-zero print, but --jq length counts the
fetched page and gh's default --limit is 30 (--limit 5 prints 5 when
the true count is 20). The printed number is min(actual, limit), so the
skill no longer claims it as the auto-close count; only 0 clears a
delete.
2026-07-06 01:19:46 +08:00
Tianyi Cui 42bdf4f8fa Merge branch 'master' into worktree-add-merging-stacked-prs-skill 2026-07-06 01:18:36 +08:00
Tianyi Cui 3b145bff5c docs: check dependents per-branch with --base, not a capped sweep
Review comment on the verify step: dumping every open PR under
--limit 1000 wastes agent context and re-truncates past 1000. Ask the
exact question server-side instead: gh pr list --state open
--base <branch> --json number --jq length, whose output is a single
count that is exact at any number of open PRs. Skill, checklist, and
the cookbook dependent-check line all use the per-branch form.
2026-07-06 01:15:32 +08:00
Tianyi Cui 6ea7d7af53 ci: parallelize real-api e2e files 2026-07-06 01:10:27 +08:00
Tianyi Cui c0af59f804 docs(rfc): record parallel pre-push gates 2026-07-06 01:04:06 +08:00
Tianyi Cui 08e09217bf docs(rfc): record parallel GitHub CI gates 2026-07-06 01:04:06 +08:00
Tianyi Cui 60f7a592b3 Merge remote-tracking branch 'origin/master' into worktree-add-merging-stacked-prs-skill 2026-07-06 01:01:35 +08:00
Tianyi Cui f9cd84286f Merge pull request #177 from deepseek-harness/codex/move-acp-snapshot-replay-doc
Move ACP snapshot replay doc into package
2026-07-06 01:01:27 +08:00
Tianyi Cui aa866e377b Merge remote-tracking branch 'origin/codex/parallelize-ci' into codex/parallelize-ci 2026-07-06 00:58:04 +08:00
Tianyi Cui dc8bf36400 Merge remote-tracking branch 'origin/master' into codex/parallelize-pre-push 2026-07-06 00:56:43 +08:00
Tianyi Cui d64fe7b8b3 Merge remote-tracking branch 'origin/master' into codex/parallelize-ci 2026-07-06 00:56:35 +08:00
Tianyi Cui 7cdf8676dd Merge branch 'master' into codex/move-acp-snapshot-replay-doc 2026-07-06 00:56:13 +08:00
Tianyi Cui 89303ae656 Merge pull request #165 from deepseek-harness/worktree-system-prompt-variables
System prompt: prompt variables, persona-as-section, tool-guidance ownership
2026-07-06 00:55:56 +08:00
Tianyi Cui 1c05c41f47 docs: wait for MERGED before advancing the stack
Second review round: 'gh pr merge' can return after merely enabling
auto-merge when required checks are pending or a merge queue is active,
so retargeting the next PR could run against a master that does not yet
contain the parent. The procedure and checklist now require
'gh pr view <n> --json state' to report MERGED after every merge.
2026-07-06 00:55:09 +08:00
Tianyi Cui 2ba4964aba docs: regenerate the services catalog for the seam-contract JSDoc 2026-07-06 00:54:21 +08:00
Tianyi Cui 8cb5e15544 chore: undo the knip.json reformat noise
Review nit: the feature commit exploded every single-line array in
knip.json to multi-line, burying the one semantic change (the
workflow-vm workspace entry) under ~110 lines of mechanical reformat.
Restore the file to master's formatting with only that entry added —
the diff against master is now the 4 lines that mean something.
2026-07-06 00:53:17 +08:00
Tianyi Cui 5caceee380 Move ACP snapshot replay doc into package 2026-07-06 00:52:06 +08:00
Tianyi Cui 72c83c771a chore: parallelize pre-push gates 2026-07-06 00:52:00 +08:00
Tianyi Cui ed3972a9c6 workflow: linear meta-prefix scan (the regex backtracked exponentially)
Review finding, measured: the leading-trivia prefix regex
(`^\s*(?:comment|comment|\s+)*export …`) partitions a whitespace run
ambiguously between its outer `\s*` and the starred `\s+` alternative,
so a script that ultimately FAILS the match backtracks exponentially —
~19 ms at 35 leading whitespace characters, ~174 ms at 38, ×2.2 per
character; a realistic near-miss (a comment header, blank indented
lines, then `const meta` missing its `export`) did not finish in 10
seconds. The regex ran on the HOST stack inside the synchronous
`start()`, where no vm timeout applies and no abort can interleave — a
benign one-token typo, exactly what SCRIPT_PARSE exists to bounce back
to the model, hung the whole process instead of reaching that designed
recovery.

Replaced with a hand-rolled linear trivia scan (whitespace + `//` and
`/* */` comments — the module already scans characters for the literal)
followed by an anchored `^export\s+const\s+meta\s*=\s*` on the
remainder, whose quantifiers cannot backtrack ambiguously. An
unterminated block comment before the statement now gets its own
SCRIPT_PARSE message. Regressions: the near-miss shape must reject in
under a second (the old regex would trip the suite timeout), plus the
unterminated-leading-comment and comment-to-EOF edges.
2026-07-06 00:51:04 +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 dd44402871 docs: fix review findings in the stacked-PR merge skill
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.
2026-07-06 00:46:22 +08:00
Tianyi Cui 8e649792b7 Merge branch 'master' into codex/parallelize-ci 2026-07-06 00:41:41 +08:00
Tianyi Cui bb4cc0ff60 docs: add the dsh-merging-stacked-prs skill
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.
2026-07-06 00:35:54 +08:00
Tianyi Cui 3633cf90c8 fix(review): contain provider-removed listener failures; pin the model-via-request path
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.
2026-07-06 00:34:47 +08:00
Tianyi Cui 80358aed9f ci: parallelize github workflows 2026-07-06 00:15:19 +08:00
Tianyi Cui bffdee88e4 Merge origin/master: adopt the uniform RFC format
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.
2026-07-06 00:13:09 +08:00
Tianyi Cui d10d94a640 Merge pull request #174 from deepseek-harness/worktree-rfc-uniform-format
docs(rfc): uniform RFC format — contract, gate, and full-corpus adoption
2026-07-06 00:06:14 +08:00