Skip to content

feat(agent-harness): recover queued runs and stream checkpointed steps - #5688

Open
iscekic wants to merge 1 commit into
shared-agent-harness-3bb0-s11from
shared-agent-harness-3bb0-s12
Open

feat(agent-harness): recover queued runs and stream checkpointed steps#5688
iscekic wants to merge 1 commit into
shared-agent-harness-3bb0-s11from
shared-agent-harness-3bb0-s12

Conversation

@iscekic

@iscekic iscekic commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

No new behavior — the product does not use this new work yet.


Summary

SchedulerStateSchema persists ownership epochs and reservations so recovery resumes stored work, rejects stale results, and never blindly replays dispatched mutations.
SchedulerAdapter requires fixed model selection, authorization, policy, token counting, and dispatch; deadlines bound external waits even when adapters ignore cancellation.
Named Stop aborts inference and reads, cancels remaining calls, and preserves completed effects or waits for mutation reconciliation.

Files
  • services/agent-harness/src/scheduler.ts — Source, added (+862/-0 lines). Reserves step zero for scheduler state and starts executable model steps at one. Prearms recovery before external work, preserves projection deadlines, and serializes alarm deletion after checking for remaining work or leases. Atomically commits validated checkpoints and immutable ordered calls; persists partial text, input digests, definition versions, and dispatch attempts. Rechecks epochs, deadlines, context, call order, and current authority before dispatch. Requires the admitted model and variant without fallback or a client bearer. Keeps approval, question, client, and reconciliation waits ahead of queued runs without polling. Marks interrupted reads failed and retains uncertain mutations with provider references for reconciliation. Stop preserves a live mutation lease for confirmed completion, cancels remaining calls even after definitions disappear, and leaves unrelated queued runs unchanged. Gives each call a separate bounded message and a stable timestamp after the step text, including after restart.

ModelTool definitions omit executors, and TokenCounter must bound canonical history, including system instructions and tool schemas.
PartialStepSchema keeps display text non-executable; CompleteStepSchema requires one fully drained, validated response before calls can execute.
Legacy text stays untrusted; failed or cancelled turns retain canonical text and settled call/result pairs, but their unfinished calls cannot enter later prompts.

Files
  • services/agent-harness/src/model-step.ts — Source, added (+486/-0 lines). Strips executors and input callbacks from tool definitions; rejects duplicate, provider-defined, and provider-executed tools. Enforces the admitted model identifier, a single model step, and no automatic library retries. Validates stream events, invalid-call flags, finish reasons, ordered calls, schemas, token usage, and stored tool outcomes before checkpointing or reuse. Reads stream errors without logging provider objects. Throttles partial writes at 250 milliseconds or 4 kibibytes, and caps display text at 64 kibibytes. Caps streamed output and checkpoints at 256 kibibytes and validates up to 32 citations. Reads at most four 50-message history pages, excludes display partials and future queued inputs, and trims only whole prior turns. Preserves system instructions and the current turn; unresolved active calls block inference. Wraps legacy text, including old assistant messages, as untrusted user data until legacy writers and imported records disappear. Stop can preserve validated outcomes without requiring their old tool definitions, but this exception cannot authorize dispatch or inference.

Admission and RunLimits reuse accepted SendResultSchema values; missing price bounds or mismatched admissions raise RuntimeError before execution.
ReservationSchema defines Reservation records that retain token, request, time, and cost ceilings across retries and restarts without adding gateway billing charges.
Each model step permits at most two attempts; lost responses keep their request and cost reservations rather than applying new defaults.

Files
  • services/agent-harness/src/limits.ts — Source, added (+125/-0 lines). Validates the stored admission against the run, accepted message, and conversation context. Calculates model input/output reservations from trusted token counts, remaining context space, and admitted prices. Bounds model requests, per-step regeneration, tool calls, web requests, each attempt, and total active execution time. Tracks reserved, finished, interrupted, and released attempts. Keeps crashed-attempt time reservations conservative, while finished attempts record elapsed time. Released non-dispatch requests retain elapsed time but no longer consume tool or web request slots; durable waits consume no active execution time. Adds byte sizing and portable errors, with retryability disabled unless explicitly allowed.

Tests: 1 test file added — services/agent-harness/src/model-step.test.ts (+1,971/-0 lines). Fake model streams and real SQLite cover crash recovery, partials, validation, budgets, Stop, unresponsive waits, and terminal-failure history. Shared selectors verify event, snapshot, and paged-history ordering after restart.
Generated: 0 files changed.


Verification

Manual runtime verification did not run because this level adds no user interface or production wiring; verification remains pending at the completed stack tip.

Visual Changes

Visual Changes: N/A

Reviewer Notes

Human steps

No human steps are required before merge or after merge for this level.

Recorded checks

The handoff records passing results for the focused Vitest suite, changed-file oxlint, oxfmt, and git diff --check.

Scope

  • Repository: Kilo-Org/cloud.
  • Worktree: /Users/igor/Projects/.worktrees/shared-agent-harness-3bb0.
  • Review scope: level 12, from shared-agent-harness-3bb0-s11 to shared-agent-harness-3bb0-s12.

Notes

Runtime verification remains pending at the completed stack tip. This level adds no user interface or production wiring.

Stacked PRs — merge bottom to top. Each level shows only its own diff.

Runtime verification (E2E, user advocacy, simplify) runs on the tip PR over every level.
Every level keeps its own checks, its own bot review, and its own threads; each one is answered on its own PR.
Each level is its own deliverable: it builds and passes its own checks alone.
A finding on a level is repaired on that level, then carried upward with stack.sh forward.

  1. shared-agent-harness-3bb0chore(agent-harness): register workspaces and enforce CI boundaries #5632
  2. shared-agent-harness-3bb0-s2feat(agent-harness): define portable domain and snapshots #5637
  3. shared-agent-harness-3bb0-s3feat(agent-harness): define commands tools and permission policy #5639
  4. shared-agent-harness-3bb0-s4feat(agent-harness): share client state and cursor recovery #5643
  5. shared-agent-harness-3bb0-s5feat(agent-harness): persist command intents and execution receipts #5647
  6. shared-agent-harness-3bb0-s6feat(db): add harness ingress grants and retirement fences #5655
  7. shared-agent-harness-3bb0-s7feat(agent-harness): deliver legacy history and project durable text #5659
  8. shared-agent-harness-3bb0-s8feat(agent-harness): authorize durable grants and registered clients #5662
  9. shared-agent-harness-3bb0-s9feat(agent-harness): fence retirement and retry payload cleanup #5667
  10. shared-agent-harness-3bb0-s10feat(agent-harness): persist authoritative state in SQLite #5675
  11. shared-agent-harness-3bb0-s11feat(agent-harness): admit durable runs and revisioned commands #5678
  12. shared-agent-harness-3bb0-s12feat(agent-harness): recover queued runs and stream checkpointed steps #5688 ← this PR
  13. shared-agent-harness-3bb0-s13feat(agent-harness): resolve interactions and dispatch tools sequentially #5693 (tip)

fail('limit_exceeded', 'The model inference cost limit is exhausted.');
} else {
if (
previous.filter(item => item.kind === 'tool' && item.status !== 'released').length >=

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Interrupted tool leases consume limits.calls, so recovery cannot re-reserve the same pending call at capacity

commitModel and validateResponse treat limits.calls as the max stored tool calls, but reserve() counts every non-released tool reservation against the same ceiling. claim() marks an expired lease interrupted (still counted) and then immediately tries to reserve again for that pending call. After a crash during the last in-flight tool attempt (a 32-call checkpoint at the default cap, or calls: 1), that retry hits length >= limits.calls and fails the run with limit_exceeded without dispatching the stored call. Model steps explicitly allow two attempts per step; tool recovery has no equivalent budget.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

signal.addEventListener('abort', onAbort, { once: true });
});
try {
return await Promise.race([work(), aborted]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Promise.race leaves work() unhandled after abort, which can fail the isolate after durable writes

When the abort promise wins, work() is still running with no .catch(). execute() uses this helper around authorize, runModelStep, and dispatch. An abort (Stop or deadline) then finishFailure / maintainAlarm can commit, after which a later rejection from streamText or the adapter is an unhandled rejection on the Durable Object alarm. That can fail the Worker after the epoch has already moved, and the loser can still be in flight when the next alarm claims new work.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

};
});
if (!dispatched) return true;
fence(job);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Post-commit fence() treats Stop as epoch loss after the mutation is already executing, so dispatch never runs and Stop cannot cancel

insertAttempt and compareAndSetCall(..., executing) already committed inside store.transition. blockConcurrencyWhile is released before this fence(job) (which does not allow stopping). A cancelRun in that gap makes fence throw; finishFailure then stopRun sees an executing non-read and keeps the lease instead of cancelling. Dispatch never starts. At deadline the call is recovered as outcome_unknown / reconciliation for an effect that was never sent, so a named Stop can strand the run waiting for a13 instead of cancelling.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
services/agent-harness/src/limits.ts 95 Interrupted tool leases consume limits.calls, blocking recovery re-reserve at capacity
services/agent-harness/src/scheduler.ts 191 Promise.race leaves work() unhandled after abort
services/agent-harness/src/scheduler.ts 737 Post-commit fence() after marking a mutation executing can strand Stop on reconciliation
Files Reviewed (4 files)
  • services/agent-harness/src/limits.ts - 1 issue
  • services/agent-harness/src/scheduler.ts - 2 issues
  • services/agent-harness/src/model-step.ts
  • services/agent-harness/src/model-step.test.ts

Reviewed by grok-4.6 · Input: 258.4K · Output: 25.2K · Cached: 1.3M

Review guidance: REVIEW.md from base branch shared-agent-harness-3bb0-s11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant