* 1. rejects corrupt parent chains in array-backed readers
* 2. does not decode SQLite branch entries outside query bounds
* 3. validates SQLite entries before filtering and limiting branch results
* 4. does not validate SQLite ancestors beyond newest-first stop bounds
* 5. rejects repository operations and session writes after disposal
* 6. supports lexical ownership with await using
* 7. uses listing as a barrier between accepted session operations
* 8. waits for every accepted session operation during disposal
* 9. waits for accepted appends before disposal and rejects later writes
* 10. repository disposal closes its owned storage
* 11. stops branch traversal at retained-tail compaction
* docs: mark QA2 done
Load storage for each create or open without caching it in the repository. Remove repository-wide serialization and disposal because write queues belong to returned sessions.
Store sessions under cwd-encoded directories with timestamped filenames, scan project directories for listing and explicit-id collisions, and skip global scans for generated UUIDv7 ids. Document and test the shared layout.
The harness v2 plan is a bit underspecified for the state needed in R1. LaneState was missing the lane's config (model, thinking, tools). And the LaneReductionResult was missing, which is needed so we have failure information. Here's a suggestion at resolving.
Implements R0 of the harness v2 plan, which adds RecordQuery.operationKind filtering and adds Session.findOpenOperations() for recovery purposes. The in-memory implementation keeps the open-operation projections in memory. SQLite implementation adds an index idx_records_session_lane_run_id_type.
Section 18 no longer references observability.md, which described the
superseded ALS/global-context mechanism and is being removed. Absorbed
the two load-bearing points: the multi-runtime rationale for rejecting
ambient context (explicit arguments are the only portable abstraction),
and the adapter framing - the application supplies ExecutionContext to
bridge spans into OTel/Sentry/logs/metrics, pi ships no exporter and no
vendor dependency, adapters allocate their own span/trace ids, and may
use AsyncLocalStorage internally. Dropped the reading-list entry.
Full-document review by a fresh-context subagent found 2 blockers, 15
minors, and 12 nits; a second subagent verified every fix. All applied:
- autoCompact ran before_compaction only when no step was unfinished,
making the hook unreachable on the live overflow path where the
abandoned assistant step still occupies LaneState; now keyed on
"not resuming a compaction step".
- Hook-supplied overflow compactions wrote no step_attempt, so the
once-per-input guard never counted them and hook-driven compact-and-
retry was unbounded; for reason overflow the hook path now writes the
compaction step_attempt first.
- Empty compaction preparation is a real code branch, terminal for
overflow. before_resume is invoked in the resume() dispatch. Pending
deferred re-park verifies handle equality. resume() re-tags operation
results as ResumeResult. MessageEntry declares terminate; options gain
toolExecution; before_navigation loses its orphaned result fields;
AbortRequestedRecord loses its unreachable reason field.
- Reduction bullets restated (newest-attempt closure, newest-own entry);
Tier B records lane moves and covers the overflow and cancellation
traces; hook-measured usage is written as hook ledger records at all
three append sites; assorted stale wording from before the deferred-
closure, ledger, and B2 changes brought current.
- Tier B: executable append-only-context assertion - every request in a
run extends the previous request as an exact prefix, except across a
compaction entry.
- Tier C: crash sites derived mechanically - snapshot the backend after
every executeAction() of each section 6 trace, reopen and resume every
snapshot, run recovery twice per snapshot.
- Section 15: fixed-point self-check as a production rule - after every
resume() outcome, recompute the section 7 reduction and compare to live
LaneState; mismatch faults the harness. Matching Tier C invariant.
- Section 19 becomes "Resolved questions": per-lane hook registration
rejected (harness-global with lane field stands); replication stays out
of scope - the append-only single-writer log replicates by log shipping
if ever wanted. Cross-references in sections 1 and 11 updated.
- Section 21: implementation goes to src/harness/experimental/ with tests
in test/harness/experimental/; everything under src/harness/ outside
experimental/ is removed when the experimental implementation replaces
it wholesale.
Choose the effects variant as the harness v2 design: rename
harness-v2-effects.md to harness-v2.md with the decision rationale, delete
the rejected generator variant (preserved at 01eeafd1).
Fold in everything decided since the split:
- run/turn/step nomenclature and XxxRecord suffixes for op-log records
- restore rework decisions: TaggedError/matchError result surface, section
18 ExecutionContext telemetry contract (with in-effect usage example),
step_attempt.resultEntryId with stable structural ids, hook-durability
wording, Models-level deferred API, typed record plumbing
- context-overflow/length handling per the superseding PR-7540 review:
isRecoverableLength against the intended output cap, discard-don't-append,
one recovery per conversational input, decline/nothing-to-compact
terminal, Responses incomplete_details mapping, neutral truncation
wording, crash-site table and test shapes
- UsageRecord cost ledger: union-shaped record (request/tool/hook/
adjustment causes), cost durability independent of result durability,
immutable entry usage snapshots with read-time effective cost,
lane.recordUsage(), harness-global usage event carrying session totals,
stats as ledger sum via maintained projection, fork/v3-import rules
- prompt normalization, deferred-fulfillment nuance, overflow guard
reduction, ledger and overflow test coverage
Queued steer/followUp/nextRun items get a durable identity and retraction,
mirrored in both design variants so the diff stays mechanism-only:
- queue_enqueued acceptance returns the provisioned entryId; snapshots and
queue_update events carry QueuedItem { entryId, message }.
- New queue_cancelled record: without it, recovery would resurrect a
cancelled item (record-without-entry restores as pending). Validity rules
reject cancels of unknown or already-consumed items.
- cancelQueued(entryId) is one conditional mutation-line job; race-catalog
row 12 covers cancel-vs-consume with exactly two histories. Reduction now
also excludes abort-killed items (baseline omission).
- Adds the LaneRecord/NewRecord unions that sections 12/13 already
referenced but section 5 never defined.
Adopted from the effect-durable-agent comparison (pending-message identity
and cancellation), trimmed to the minimal slice.
Replace harness-v2.md with two candidate designs that share Parts I and II
verbatim and differ only in the Part III stepping mechanism:
- harness-v2-effects.md: straight-line async procedures; deterministic
stepping via a gated effect boundary (drive: "manual").
- harness-v2-generator.md: synchronous state machine; stepping via yielded
actions and an execution controller.
Both fold in the fixes for the baseline defects found during the audit:
per-lane mutation line with conditional commits (finish/steer/abort/queue
races), nextRun capture at acceptance, terminal-failure recovery, terminate
persisted on tool-result entries, move-first navigation commit (closes the
open question, removes the only multi-object atomic write), corrected v3
normalization, and #7339-aligned deferred redemption.
Only one variant will be implemented; diff the two files to see the
mechanism trade-offs in isolation.