You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Readability refactor of the V2 session runner and the SessionPending surface. The runner hierarchy now reads top-down as drain → runSteps → runStep → callModel, and the pending-input module exposes two deep combinators instead of five overlapping ones.
flowchart TD
W[wake / resume] --> D[drain]
D -->|settle stale tools, run pending compaction| S[runSteps]
S -->|one logical step at a time| R[runStep]
R -->|"retry / compaction-restart loop"| C[callModel]
C -->|"Completed | Retry | Restart"| R
has(db, sessionID, scope) with scope "any" | "input" | Delivery replaces hasWork, hasEligible, and the delivery-only has. "any" counts every row including compaction; delivery scopes are blocked behind a pending compaction barrier.
promote(db, events, sessionID, "input" | "steer") replaces promoteEligible({ boundary }) and promoteSteers; promoteNextQueued deleted (zero callers). PromotionBoundary becomes Promotable, retiring the boundary: "step" / step counter name collision.
Runner (packages/core/src/session/runner/llm.ts)
drain reads as its phases: dismiss spurious wake → settleStaleToolCalls → runPendingCompaction → run steps until no promotable input. Stale tool settlement now happens before compaction reads history.
runStep retry restructured: Schedule.toStepWithSleep + a waitForRetry loop arm replace Effect.suspend + tapError closure mutation + retryOrElse. Outcomes are a Data.taggedEnum (Completed | Retry | Restart), collapsing the two ad hoc restart tags.
callModel: recoverOverflow is a plain boolean instead of a function-typed one-shot flag; step-end helpers regrouped so the stream definition sits directly against the settlement mask that runs it; phase comments added ("make room", settlement contract).
Title generation extracted to startTitleOnce.
Retry (packages/core/src/session/runner/retry.ts)
Schedule input narrowed to RetryableFailure, dropping the setInputType union, passthrough, Schedule.while, and both instanceof hedges that existed only because retryOrElse funneled every error through it.
Semantics
Two deliberate behavior changes, both small:
Retry budget scope: the 4-retry allowance now spans one logical step including compaction restarts. Previously each restart rebuilt a fresh schedule and reset the budget.
Drain entry: a spurious wake with no pending work now returns before settling stale tool calls; previously stale-tool repair ran whenever eligible input existed but not on compaction-only wakes. Stale tools are now always settled before any compaction or model call in the same drain.
Also removed: the Effect.yieldNow between restart attempts (no observable dependency; suites pass repeatedly) and a redundant post-loop runPendingCompaction.
Scope
Not covered, planned as follow-ups:
Extracting pure settlement judgments (classifyToolExits etc.) from callModel's uninterruptible block
Top-down declaration reordering of llm.ts (drain first)
The loaded.model.model resolved-model naming in model.ts/context.ts
Testing
bun typecheck (packages/core) — clean
bun run test test/session-runner.test.ts test/session-runner-recorded.test.ts test/session-prompt.test.ts test/session-create.test.ts test/tool-subagent.test.ts — 206 pass, 0 fail
Retry tests with --rerun-each 3 — 30 pass; previously timing-sensitive steering test hardened to await real session idleness and passed 5 consecutive runs
Full packages/core suite: 1415 pass / 5 fail — the same 5 failures (Mistral provider ×4, LocationServiceMap ×1) reproduce on the clean v2 base and are unrelated
While exercising the new retry-budget scope, I found a pre-existing history anchor issue across retry + overflow compaction.
A deterministic sequence is:
a retryable pre-output failure creates assistant A and schedules attempt 2;
attempt 2 reports context overflow, so compaction completes and the step restarts;
a later attempt succeeds and durably ends A;
the next prompt's model request contains the compaction checkpoint and the new user input, but not A's successful text.
StepStarted inserts A before the compaction row. The restart reuses A's assistantMessageID, and later step/text events update that row without moving its message sequence. SessionHistory then filters at the newer compaction sequence, so the successful response remains in the full message/event log but falls out of active model context.
I reproduced this at 8547e6f2 and on base 7456598c, so it is not introduced by this refactor. The PR's shared retry schedule does work: the combined trace keeps attempt 2 at 2s and attempt 3 at 6s, and a schedule-reset mutation makes the test fail. Would a compaction restart need a post-anchor assistant identity or an explicit re-anchoring transition?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Readability refactor of the V2 session runner and the
SessionPendingsurface. The runner hierarchy now reads top-down asdrain → runSteps → runStep → callModel, and the pending-input module exposes two deep combinators instead of five overlapping ones.How
SessionPending(packages/core/src/session/pending.ts)has(db, sessionID, scope)with scope"any" | "input" | DeliveryreplaceshasWork,hasEligible, and the delivery-onlyhas."any"counts every row including compaction; delivery scopes are blocked behind a pending compaction barrier.promote(db, events, sessionID, "input" | "steer")replacespromoteEligible({ boundary })andpromoteSteers;promoteNextQueueddeleted (zero callers).PromotionBoundarybecomesPromotable, retiring theboundary: "step"/stepcounter name collision.Runner (
packages/core/src/session/runner/llm.ts)drainreads as its phases: dismiss spurious wake →settleStaleToolCalls→runPendingCompaction→ run steps until no promotable input. Stale tool settlement now happens before compaction reads history.runStepretry restructured:Schedule.toStepWithSleep+ awaitForRetryloop arm replaceEffect.suspend+tapErrorclosure mutation +retryOrElse. Outcomes are aData.taggedEnum(Completed | Retry | Restart), collapsing the two ad hoc restart tags.callModel:recoverOverflowis a plain boolean instead of a function-typed one-shot flag; step-end helpers regrouped so the stream definition sits directly against the settlement mask that runs it; phase comments added ("make room", settlement contract).startTitleOnce.Retry (
packages/core/src/session/runner/retry.ts)RetryableFailure, dropping thesetInputTypeunion,passthrough,Schedule.while, and bothinstanceofhedges that existed only becauseretryOrElsefunneled every error through it.Semantics
Two deliberate behavior changes, both small:
Also removed: the
Effect.yieldNowbetween restart attempts (no observable dependency; suites pass repeatedly) and a redundant post-looprunPendingCompaction.Scope
Not covered, planned as follow-ups:
classifyToolExitsetc.) fromcallModel's uninterruptible blockllm.ts(drainfirst)loaded.model.modelresolved-model naming inmodel.ts/context.tsTesting
bun typecheck(packages/core) — cleanbun run test test/session-runner.test.ts test/session-runner-recorded.test.ts test/session-prompt.test.ts test/session-create.test.ts test/tool-subagent.test.ts— 206 pass, 0 fail--rerun-each 3— 30 pass; previously timing-sensitive steering test hardened to await real session idleness and passed 5 consecutive runspackages/coresuite: 1415 pass / 5 fail — the same 5 failures (Mistral provider ×4, LocationServiceMap ×1) reproduce on the cleanv2base and are unrelated