Skip to content

feat(retry): park quota-blocked sessions until provider reset and auto-resume (#2375, #2280, #2502) - #82

Open
rynfar wants to merge 29 commits into
pylonfrom
feat/prime-quota-park-auto-resume
Open

rynfar wants to merge 29 commits into
pylonfrom
feat/prime-quota-park-auto-resume

Conversation

@rynfar

@rynfar rynfar commented Sep 25, 2026 •

Copy link
Copy Markdown

Summary

Adopts upstream PR PrimeIntellect-ai#2375 (Quota park and auto-resume), PR PrimeIntellect-ai#2280 (Provider backup model and bounded ping waits), and PR PrimeIntellect-ai#2502 (Past-due wake recovery and branch navigation preservation) into the Prime fork.

Note: Stacked on PR #81 (feat/prime-abort-and-send-queued). Merge PR #80 and PR #81 first.

Features & Behavior

  • Quota Parking: When a provider returns a 429 quota or rate-limit error with a reset timestamp exceeding standard retry delays, the session enters a parked state (isQuotaParked: true).
  • Durable Auto-Resume: Arms both an in-process wake timer and a durable one-shot background job via AgentCronJobStore, surviving daemon restarts.
  • Resume Probes: Automatically probes the model upon reaching the reset time with a synthetic marker prompt (<provider_quota_resumed>), continuing the interrupted task.
  • Provider Backup Model: Supports configuring providerBackupModel (e.g. provider.backupModel) to route requests to a secondary model when the primary model encounters quota limits or unavailability, restoring the primary model once quota returns.
  • Event Typing: Extends auto_retry_start (reason, backupModel) and auto_retry_end (restoredModel) in public and daemon wire event interfaces while keeping fields strictly backward-compatible.
  • Audit Hardening:
    • Prevents mid-turn model flip-flopping across tool execution steps.
    • Fixes false success: true on aborted retry loops.
    • Ensures primary model restoration if a backup model hits quota limits and parks.
    • Preserves exact attempt counters on retry completion events.

Invariant Checks

Test Plan

  • npm run build: clean build across all packages.
  • npm run check: all pre-commit checks passed (biome, tsgo, check:installer, check:browser-smoke).
  • Vitest suites passed:
    • packages/coding-agent/test/suite/agent-session-retry-events.test.ts (60 tests passed)
    • packages/coding-agent/test/provider-retry.test.ts (25 tests passed)
    • packages/coding-agent/test/settings-manager.test.ts (38 tests passed)
  • Full adversarial audit conducted and recommendations addressed.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

rynfar and others added 29 commits September 22, 2026 05:48
…s like stdin/stdout (PrimeIntellect-ai#2383)

* fix(coding-agent): absorb kernel child stderr stream errors like stdin/stdout

wireChild guards stdin and stdout pipe streams with error listeners, but the
stderr stream only had data/end/close handlers, so an error emitted on it
reached the daemon uncaughtException handler, which process.exit(1)s the
worker and every hosted session with it. Add the matching stderr error
listener that records a kernel diagnostic, and extend the pipe-errors
regression test to emit on stderr and assert the kernel keeps running.

* test(coding-agent): trim the duplicated pipe-error comment to fit the test-line budget

The added comment block restated the guard the test name and the new stderr
assertions already carry; one line keeps the read-side rationale. Net test
lines drop to +2 against 3 added source lines.

(cherry picked from commit b6b94c7)
… host (PrimeIntellect-ai#2423)

* [RSI, bug] fix(kernel): bound REPL protocol frame sizes in kernel and host

The host buffers whole protocol lines before its per-execution truncation,
so a print()/repr() of huge data from one kernel cell buffered the full
payload twice in the host worker, and a newline-less flood from a corrupt
runtime ballooned host memory forever because the protocol repair only ever
saw complete lines.

The kernel now chunks Python-level stdout/stderr writes into 64 Ki-char
stream frames under a per-writer lock, caps result repr text at 1 Mi chars
with a trailing truncation marker, and refuses display payloads whose JSON
encoding exceeds 16 Mi characters (emit() raises ValueError in the cell).
The host caps protocol line accumulation at 32 Mi chars, failing an
oversized line through the existing protocol repair instead of buffering
it, and stops draining a poisoned child's residue.

Each regression test fails pre-fix: the chunked-write test saw one
300,000-char frame, the repr/display test saw the untruncated
3,000,000-char result, and the oversized unterminated protocol line hung
the manager until vitest's test timeout.

* docs(runtime): result text cap excludes the appended truncation marker

* fix(kernel): mark output truncated when frames arrive after the buffer is exactly full

A 64 KiB stream frame fills the 65536-char buffer exactly; later frames were dropped without setting stdoutTruncated/stderrTruncated, so the model saw no truncation marker.

---------

Co-authored-by: prime-agent <snimu@users.noreply.github.com>
(cherry picked from commit c91e6e9)
…rimeIntellect-ai#2425)

* [RSI, security] fix(kernel): keep the kernel stderr log owner-only

The kernel stderr log (session-artifacts/<id>/kernel-stderr.log) was created
world-readable (0644) while every sibling artifact in the same private
directory is owner-only (0600 files, 0700 dirs). Kernel stderr can carry
exception payloads and library warnings, so the log and its directory must
match the other private session artifacts.

Create both with owner-only bits: mode 0o700 for the directory and 0o600 for
the file, with fchmodSync on the opened descriptor to enforce the exact bits
despite the umask (and tighten a pre-existing loose log on next kernel start).

* fix(kernel): tighten the rotated kernel stderr log to 0600

Rotating an oversized kernel stderr log renamed it to `.old` before any
chmod, so a pre-existing world-readable (0644) log kept those bits and its
historical exception payloads stayed readable by other users. Only the new
current file was tightened.

chmod the log to KERNEL_STDERR_LOG_MODE immediately before the rename. The
path is known to exist (statSync just succeeded), a chmod failure lands in
the existing rotation catch and keeps appending instead, and on Windows the
chmod clears the read-only bit so the rename still proceeds.

Cover it with a regression test that pre-creates an oversized 0644 log and
asserts the rotated file is 0600. The failing-start tests now share
fakeRuntime/withManager helpers to keep the added coverage line-neutral.

(cherry picked from commit b6d955a)
…put (PrimeIntellect-ai#2424)

* fix(coding-agent): keep tool identity in compaction summarizer serialization

Compaction serialized all tool results as anonymous "[Tool result]" blobs, so parallel tool calls could not be paired with their results and failures were indistinguishable from successes in the summarizer input.

Label each result with its tool name and mark failed calls with an error tag so summaries keep failure attribution. Truncation behavior is unchanged.

* fix(coding-agent): pair serialized tool calls and results by index

serializeConversation labeled tool results with the tool name only, so
when the same tool was called twice in one turn the summarizer could not
reliably associate a result (or its error status) with the correct call,
and skipped empty results shifted the positional pairing.

Assign each serialized tool call a 1-based sequential index, prefix the
call with it (`#1 ipython(...); #2 bash(...)`), and repeat the index in
its result label (`[Tool result (ipython) #1]`, `[Tool result (ipython,
error) #2]`). The short index stands in for the raw provider toolCallId,
which can exceed 450 characters and would bloat the token-budgeted
summarizer input. Results whose call was not serialized (branch-summary
budget slices drop older entries) fall back to the name-only label.

Update the compaction docs examples and the changelog, and add
regression tests for same-tool pairing and the orphaned-result fallback.

* docs(coding-agent): correct branch-summary note in compaction serialization docs

Branch summarization drops toolResult entries in getMessageFromEntry
before serializeConversation is ever called, so branch summaries show
the `#N` call prefixes with no result lines at all. The fallback sentence
added in 6e3aa4c wrongly attributed orphaned results to branch-summary
budget slices. Re-attribute the name-only fallback to serialized inputs
that lack the matching call, which extension callers can pass since
serializeConversation is exported.

Docs and in-code comment only; no behavior change and no test changes.

(cherry picked from commit 5fb3d9a)
…e-summarizing file lists (PrimeIntellect-ai#2385)

* fix(coding-agent): anchor compaction summaries to kept-tail state and stop re-summarizing file lists

P1 (stale-summary drift): the summarizer only saw messages before the cut
point, so post-compaction summaries described pre-tail state. prepareCompaction
now extracts the newest kept-tail assistant text as a recency anchor, the
summarizer prompt carries it as <recent-state-anchor> (retained messages below
are authoritative), and the in-context [compaction-summary] prefix line states
that the retained messages below are authoritative over the summary.

P2 (compounding file lists): stored summaries end with mechanically appended
<read-files>/<modified-files> blocks that rode the previous summary back into
the update prompt, so the model re-summarized lists that compound across
repeated compactions. prepareCompaction strips the blocks before the update
path (entry details plus the fresh append stay the single source of truth) and
computeFileLists caps the combined lists at 6000 characters, dropping
read-only entries first.

* test(compaction): compress recency-anchor coverage to cross-layer essentials

* test(compaction): execute owner-authorized cut menu to fit the test-line budget

(cherry picked from commit 27f32dd)
…n the digest (PrimeIntellect-ai#2463)

* fix(harness): validate refinement writes and skip malformed entries in the digest

A harness entry with non-string content (the incident: content persisted as a
list by the kernel CRUD path) crashed the daemon digest builder on every
session build with "text.replace is not a function", bricking all child
spawns. Validate entry shape at the single write choke points (kernel
HarnessState._upsert for rlm.harness CRUD, validateEdit for refine/rollback
edits) so invalid entries are rejected with an error naming the entry and the
field instead of being saved, and make every harness renderer (digest, refine
overview, notice body) skip malformed entries and refinement events with a
"harness: skipped malformed entry <id> (...)" diagnostic so one corrupt store
entry can never break session creation.

* fix(harness): guard non-object refinement events and event ids

Review round 1 (Bugbot persona) found two residual gaps in the same crash
class: a non-object element in the persisted refinements array still crashed
the digest and its fingerprint (event.trigger / event.id dereferenced null),
and record_refinement persisted a non-string event id that the kernel loader
then silently dropped. Skip non-object refinement events with an
"event not an object" diagnostic, filter them out of the digest fingerprint,
and reject non-string/empty refinement event ids at write time.

* fix(harness): skip refinement events with non-string ids and change elements

Review round 2 (Macroscope) found the digest still rendered refinement
events whose id or change elements were not strings: a corrupt store
event like {"id":null,"trigger":"t","changes":[{"a":1}]} passed
harnessRefinementMalformation and rendered junk ("- [null] t: [object
Object]") into every session's prompt digest, and
malformedRefinementEventLabel interpolated the unvalidated id value.
Persisted stores are JSON, so these shapes cannot throw (only non-JSON
values like Symbols can, which JSON.parse never produces), but the
skip-malformed contract requires string-validated rendering: extend
harnessRefinementMalformation with "id not a string" and "changes
contain a non-string", and label invalid ids by bounded type instead of
interpolating the value. The digest and fingerprint share the predicate,
so both skip paths stay render-equal.

(cherry picked from commit 41e4e0e)
…ntellect-ai#2374)

* [RSI] fix(ai): recover stale Codex chains after metadata

* test(ai): consolidate the paired open-loop SSE tests to fit the test-line budget

Fold the two copy-paste "even when the SSE body stays open" tests into one
table-driven case (both stop reasons keep their own row), drop their 1s
wall-clock race in favour of the suite timeout, and pin the responseId reset on
the chain-reset path by seeding the stale-metadata attempt in the retry-failure
test. Net test lines drop to +2 against 13 added source lines.

(cherry picked from commit e683fcb)
…rimeIntellect-ai#2414)

* perf(session-manager): cache the live leaf branch on the per-turn read path

The per-turn compaction check and the context-usage state build call
getBranch() on every assistant message end, and each call walked
leaf-to-root and allocated a fresh array (thousands of entries on a long
session).

SessionManager now caches the live leaf's path. An append is always a
child of the current leaf, so appends extend the cached array in place.
Every other leafId write (branch, resetLeaf, branchWithSummary, append
rollback, session load/rebuild) drops the cache, and getBranch(fromId)
reads for a non-leaf entry are never cached.

getBranch() returns the cached array itself, so the one caller that hands
the branch to an awaited extension handler (session_before_compact) now
passes a slice. That keeps the previous snapshot contract: an entry
appended while the handler runs no longer grows the array the extension
holds. docs/extensions.md states that contract next to session_before_compact.

Compaction decisions are unchanged. On a synthetic 20k-entry branch the
cached read costs 0.02us versus 297us for the leaf-to-root walk it replaces.

Tests: test/session-manager/leaf-branch-cache.test.ts covers cache
identity across reads, in-place extension on append, invalidation on
branch/resetLeaf/branchWithSummary and on append rollback, non-leaf suffix
reads, and agreement with a reopened session.
test/suite/agent-session-compaction.test.ts pins the session_before_compact
snapshot: it appends an entry from inside the awaited handler and asserts
the handler's branchEntries stays unchanged while the live branch grows
(that test fails without the slice).

* chore(changelog): add fragment for the leaf branch cache

* fix(session-manager): keep a rolled-back append out of the live leaf branch cache

The cache was extended before _persist, so a failed persist or a failed
post-append flush left the rolled-back entry in an array getBranch() had
already handed out.

- extend the cache only after _persist returns
- pop the rolled-back entry from the live cached array before invalidating
- trim the cache comments to current behavior and document the read-only
  live-array contract on getBranch() (a ReadonlySessionManager surface)
- pin the rollback property and the in-place append identity in the cache test

* test(session-manager): rely on the configured test timeout in the branch snapshot test

* test(session-manager): compress leaf-branch-cache tests and prune tree-traversal duplicates

Consolidate the new cache-contract tests around shared fixtures (merged
leaf-move test, trimmed rollback phases, deleted the reopen-equivalence
duplicate) and compact the session_before_compact snapshot test. Prune
eleven tree-traversal vectors whose observables are already pinned by
the new cache tests and kept siblings (probed: each twin fails under
the matching mutation), fold the five append-operation tests into one
table preserving every vector, and fold the in-memory fork result
assertion into the kept branched-tree test. Net test additions fall
from 283 to -7 against a 26-line budget.

* test(session-manager): pin the third-sibling tree shape the compression dropped

The pruned getTree multi-branch duplicate was the only test with 3+ siblings
at one branch point; the surviving twin pinned only 2. Re-buy the shape by
branching the same point twice in the kept test (scoped review hardening).

* changelog: note getBranch() returns a live shared array

---------

Co-authored-by: prime-agent <snimu@users.noreply.github.com>
(cherry picked from commit 0498deb)
…lized header (PrimeIntellect-ai#2416)

* perf(coding-agent): count tool-result message entries from their serialized header

A cold saved-session catalog scan parses every transcript line, and a
transcript-heavy catalog spends most of that parse on tool results: in the roster
fixture (201 sessions, 106 MB) tool results are 54% of the bytes and 42% of the
JSON.parse time, yet the fold reads nothing from them but the message count.

Read the entry header instead of the payload for message entries whose role can
only contribute that count. The header check is bounded to the first 512
characters, and it falls back to the full parse for every layout the file writer
does not produce: spaced JSON, another key order, a role value that runs past the
prefix, and any container before the role marker. That container guard covers
both a payload nested before the type key and a payload that quotes the header
before the entry's own message key, so a shadowed role marker can never hide a
searchable entry's text. A double quote is escaped inside a JSON string, so a
header that matches can only be structural.

Measured on the same fixture, 5 cold scans per side, medians:

  scan wall  218.4 ms -> 182.6 ms  (-16%)
  scan CPU   286.9 ms -> 257.8 ms  (-10%)

The id-first catalog layout gives the same win, the hardenings cost nothing
measurable, and the returned SessionInfo is byte-identical before and after on
every catalog measured.

* test(coding-agent): fold the header-counting vectors into the session-manager suite

Net test additions drop 299 -> 22 (source additions 22), so the test-policy gate
passes at branch level with no category violations.

The standalone message-count-scan.test.ts file is gone: its ten vectors now live
in test/session-manager/file-operations.test.ts and reuse that file's existing
tempDir/header/msg/line harness. The three header-count vectors and the five
fallback-layout vectors are table-driven, and four pre-existing edge-case groups
in the same file are consolidated into tables with every case kept. Per-cut
lost-coverage ledger is in the PR body.

* test(coding-agent): restore the prefix-boundary pin by trading four duplicate rows

The header fast path's prefix-boundary early-out was unpinned after the budget
compression. Restore its vector and pay for it with four rows in the same file
that are true same-observable duplicates:

- findMostRecentSession: drop "returns null for an empty directory" and
  "returns null for a non-existent directory" (the null observable survives in
  the non-jsonl and headerless-jsonl rows).
- loadEntriesFromFile: drop "an empty file" and "malformed JSON" (the empty-array
  observable survives in the missing-file and headerless rows).

Reverting the early-out makes the restored row fail and nothing else. Net test
additions stay at 22 against 22 meaningful source lines, so the test-line budget
gate still passes at the main-tip view.

(cherry picked from commit 0597614)
…rses (PrimeIntellect-ai#2433)

* perf(coding-agent): append catalog metadata without full transcript parses

Catalog rename, archive, and mark_interrupted each opened a SessionManager to
append one metadata line, which parses and indexes the whole transcript. Add
appendEntryToExistingFile and the three append*ToExistingFile wrappers: they
repair crash damage like a full open, validate the session header from a
bounded leading window, resolve the current leaf from a bounded tail window,
and append a single line. Files whose header sits beyond the window, formats
this build does not write, oversized tails, and any other case only a full open
can place fall back to SessionManager.open, which now shares the entry builders
with the fast path.

Measured on an 8.2 MB, 20k-entry fixture: rename drops from 18.3 ms to 0.5 ms
median.

A missing or header-invalid session file now fails these updates with a clear
error instead of being recreated or silently rewritten as a fresh session. In
worker recovery the interruption notice stays advisory: a notice that cannot be
written is logged, and recovery still reaps orphaned processes and resolves its
journal.

* test(session-manager): compress append-to-existing-file coverage

Compact the catalog-metadata append suite: user-message fixtures (role
shape is irrelevant to the fast path), merged header invalid-refusal into
the catalog wiring test, and tightened helper blocks. All vectors kept.

* test(session-manager): cut catalog append coverage to the PR's test budget

Owner-authorized coverage cuts per the fleet ratio rule, executed in
cost order with a lost-coverage ledger in the PR body. Consolidations
first: PR helper blocks one-lined; suppression phase kept inside the
fallback test. Cuts: 18k reopen round-trip, v4 fallback row, huge-tail
rename row, empty-file refusal, torn-tail repair phase, state fast-write,
leading-blank row, header-only row, catalog subprocess integration test,
notice fast-write phase, supervisor notice-failure tolerance test.

(cherry picked from commit c37f5eb)
…ads (PrimeIntellect-ai#2389)

* chore(deps): drop stale peer markers from package-lock.json

Normalize the lockfile entries npm install left behind in the dev
worktree (seven dev-dependency "peer": true markers removed).

* perf(daemon): mtime-guard the cron jobs catalog reads

Every poll and mutation of the scheduled-jobs catalog re-read and
re-parsed the whole jobs file: readJobsState was readFileSync +
JSON.parse with no stat guard, the agents view polls the catalog
every 15s, and a single mutation read the file two to three times.

Each AgentCronJobStore now keeps an mtime-guarded in-memory snapshot
per jobs file, following the sessionScanStates precedent in
session-manager.ts: reads stat the file (dev/ino/size/mtimeMs) and
serve the snapshot while the identity is unchanged; a parse is only
cached when bracketed by one file identity, so a concurrent writer
cannot poison the snapshot. Mutations publish the newly written state
as the disk round-trip, so follow-up reads (heartbeat catalog
signatures) do not re-read what they just wrote, and a failed write
drops the snapshot instead of serving an unpersisted state.

External writers (other processes) still take effect on the next
poll: a replaced inode or changed mtime/size invalidates the snapshot
and falls back to a full read.

* fix(daemon): freeze cron snapshot views and verify write identity

Bot review on the mtime-guarded cron catalog snapshot surfaced three
ways the in-memory state could diverge from what reads and writes
promise:

- writeState sampled the file identity only after writeJobsState, so an
  external writer replacing the file between our rename and the stat
  had its identity cached together with our stale state: later reads
  served outdated jobs and a later mutation could overwrite the
  external update. writeJobsState now reports the identity the atomic
  rename produces (temp-file stat in the beforeRename hook; dev/ino/
  size/mtimeMs survive the rename), and writeState publishes the
  snapshot only when the post-write stat matches that identity,
  otherwise it invalidates and the next read re-parses the disk.
- readState handed out the cached CronJobsState graph itself, so an
  in-place edit of a listed job (store.list()[0].status = "cancelled")
  changed later store behavior and an unrelated mutation could persist
  the edit. Reads now serve deeply frozen views (publish and parse
  paths, plus a shared frozen empty state while the file is missing);
  unchanged polls keep the single-stat fast path and return the frozen
  object without cloning.
- mutateStates edited that cached state in place, so a mutator throwing
  mid-edit left unpersisted jobs and dispatches in the snapshot.
  Mutators (and recoverSessionArtifact) now operate on a private
  round-trip clone; the published frozen snapshot is never edited.

The snapshot suite gains three regressions mirroring the reports:
read-only views (in-place edits throw and never reach disk), mutator
mid-edit crash isolation, and an external replacement landing the
instant our write does (the store re-reads the external state and a
later cancel operates on it). All three fail on the pre-fix code.

(cherry picked from commit d2cdb45)
…PrimeIntellect-ai#2393)

Roster heartbeat frames re-composed every active session summary on every
flush: latestMessageActivityAt walked the whole message array per session,
each summary was rebuilt and re-serialized, and listHeartbeats built a full
summary per registered job on every poll. Daemon CPU therefore grew linearly
with messages x sessions x flushes, dominated by long-lived 18k-message
sessions that had not changed at all.

- summaryForActiveSession now memoizes the last composed summary per session
  and returns the same object when a cheap fingerprint of every summary input
  (message count, latest activity, busy bits, usage, verdict, registrations,
  action snapshot, identity fields) is unchanged. The fingerprint compares the
  full input set rather than leaf id + message count alone because streaming,
  bash, compaction, tool, attach, and verdict edges all change the summary
  without a single append; the memoized summary stays byte-identical to a fresh
  compose, so the wire delta is unchanged.
- The latest-activity timestamp scan is incremental: the memo folds only
  messages appended since the last scan and falls back to a full walk when the
  array is replaced, shrinks, or shifts (mid-array insert detected by the
  boundary tail reference).
- flushRoster reuses the previous roster entry and its serialization when the
  session composed to the same memoized summary, so unchanged sessions skip
  both the compose and the JSON.stringify per flush; freshly composed entries
  still stringify and compare exactly as before.
- listHeartbeats takes its sessionName/firstMessage from a new
  sessionDisplayLabels helper instead of composing a full summary per
  heartbeat job per poll.

Measured on an 18k-message idle session: 59us -> 9us per compose cycle when
unchanged, 44us -> 9us after an append.

(cherry picked from commit e311d64)
…st the ready event (PrimeIntellect-ai#2379)

* perf(kernel): defer the event-loop import stack past the ready event

The Python kernel imported asyncio (plus the bash tool's secrets, shutil,
datetime, selectors, struct, fcntl/termios, atexit, and tempfile) before
sending the ready event. python -X importtime shows the asyncio subtree is
the heaviest part of the boot chain (ssl, concurrent.futures, and logging
ride along).

Defer them: rlm.bash binds asyncio on first BashHandle construction (its
only entry point), repl imports asyncio in main() after the ready event and
in the handlers that reference it, and the remaining stdlib modules import
at their first real use. No protocol, API, or behavior changes; the kernel
serves requests and interrupts exactly as before.

Local A/B (macOS, 16 interleaved trials, same harness phases as
scripts/benchmarks):
- kernel_start: 31.4 -> 24.4 ms (-22.3%)
- kernel_rss: 28.6 -> 26.8 MB (-6.2%)
- kernel_exec, bash, git_status, output, mixed, interrupt, snapshot,
  loaded_rss: unchanged within noise
- restore: 90.1 -> 97.3 ms (+8%): the one-time loop import now lands in
  the first request of a fresh kernel instead of its boot; after one
  warm-up cell restore returns to parity (90.0 vs 89.4 ms baseline)

Regression tests: import rlm must keep asyncio/secrets off the boot path;
a serving kernel loads asyncio by the first cell; BashHandle construction
binds asyncio with no event loop or prior bash() call.

* fix(kernel): keep default SIGINT handler through deferred event-loop boot

_sigint_handler has no task to target before serving starts, so the PR-head
ordering (install before the ready send) silently swallowed a Ctrl-C during
the deferred asyncio import and event-loop startup. Keep the default handler
until the loop and _serve_task exist, then install the custom handler.

Adds a regression test that parks the kernel inside the post-ready deferred
import (fake asyncio on PYTHONPATH) and asserts a SIGINT there terminates
the kernel; it fails on the pre-fix ordering and passes with the fix.

* test(kernel): compress the deferral vectors to fit the test-line budget

Net test additions drop 116 -> 22 against the 23 meaningful source lines the
deferred event-loop import adds, so the test-policy gate passes at branch level.

Cuts: merge the import-deferral and serving vectors; park the fake asyncio on a
fifo read instead of polling for a marker with time.sleep (this also removes the
wall-clock-sleep violation); trim the fresh-interpreter handle test; fold the
no-string-key list_names vector into test_list_names; fold the int-reject vector
into the stdout.buffer test; drop the redundant clean-shutdown, unknown-id
host_reply and zero-size-cap tests. Per-cut ledger is in the PR body.

(cherry picked from commit 8218d6b)
…ernel venv (PrimeIntellect-ai#2387)

* perf(kernel): skip pip seeding when creating the kernel venv

`uv venv --seed` installs pip, setuptools, wheel, and packaging into every
fresh kernel venv, but nothing invokes the venv's own pip: every
kernel-venv package is managed through `uv pip install --python`. Drop
`--seed` so first-run bootstrap stops paying four unnecessary PyPI
downloads plus their install time, and shrink each seeded venv by the
same packages.

Local install benchmark (darwin-arm64 native build, loopback artifacts,
fresh home with empty npm/uv caches, 5 interleaved trials per side):
- total install: median 13.13s -> 11.87s (-1.26s, ~10%)
- python bootstrap phase: median 11.44s -> 10.18s
- uv venv step, cold uv cache: median 0.83s seeded vs 0.06s plain
- CLI cold start (`--version`): 0.062s median on both sides (no change)

* docs(kernel): state the no-seed venv invariant in the comment

The Bugbot review flagged that the old comment justified omitting --seed by recounting the removed seeding path and its download cost. Replace it with the current-behavior invariant: nothing invokes the venv's own pip, and every kernel-venv package is installed through uv pip install --python.
…ns do not re-pay it (PrimeIntellect-ai#2405)

* perf(coding-agent): land the kernel skill-sync marker incrementally

The kernel Python bootstrap wrote its whole-version marker only once, after the last Python skill install. A session killed during the skill sync (short-lived benchmark sessions hit this every run) never recorded the marker, so the next startup wiped the venv and re-paid the runtime install plus every editable skill install.

Write the marker atomically (temp file + rename, direct write fallback) and persist it incrementally: base fields right after the runtime install in bootstrapVenv, then the accumulated skill list after every successful install group in syncPythonSkills. A killed session now leaves a valid base marker so the next one takes the skills-only path and installs only the remainder, while the final unconditional write keeps the install-failure semantics (failed skills retry next startup) unchanged.

* fix(coding-agent): merge recorded skills into incremental bootstrap marker writes

The incremental marker write in syncPythonSkills persisted only the skills visited so far this session, so a completed install earlier in install order dropped skills already recorded in the marker but later in install order. A kill mid-sync then lost those skills from the marker, and the next startup re-ran their unchanged editable installs.

Merge fresh entries into the on-disk marker for the incremental write; the final unconditional write stays an authoritative replace, and a missing or corrupt marker contributes no base entries.

Adds a regression test that kills a child mid-install and asserts the marker keeps the already-recorded skills, and that resume skips their installs.

* fix(coding-agent): keep the bootstrap marker intact when the atomic swap fails

The rename fallback overwrote the marker in place, so a failure or kill mid-write left a partial marker that reads as absent and forces a full venv rebuild. Retry the atomic swap a bounded number of times, then leave the previous marker alone and drop the temp file.

* test(coding-agent): record the real-process bounds on the kill tests

The SIGKILL/resume tests poll an external process (fake-uv log appends
and OS pid liveness) with bounded deadlines and a generous per-test
timeout, which the objective test policy flags. Mark each expression
with a test-policy allow comment stating the specific external-process
reason, following the precedent in mcp-connection-store.test.ts.

* test(coding-agent): compress kernel-bootstrap sync-marker coverage to the test-line budget

* test(kernel-bootstrap): place the no-skill preservation tests outside the compressed fail-probe test
…rimeIntellect-ai#2426)

Port upstream PR PrimeIntellect-ai#2426 (ENG-5991: send queued messages after interrupt / abort_and_send_queued).
- Renumber schema revision to 34 (digest: protocol-7-schema-34-24a1b6f17411).
- Gate command behind server capability abort_and_send_queued_v1.
- DaemonAgentConnection falls back to abort() when capability is absent, on unknown command error, or when daemon is preparing an update restart.
- In AgentSession, abortAndSendQueued() filters only non-correlated user turns, arms forced batch for next turn boundary, and cleans up on clearQueue and abortForUpdateRestart.
- Interactive mode and test suites updated and verified.
- Added changefile packages/coding-agent/.changes/eng-5991-send-queued-after-interrupt.md.
…o-resume (PrimeIntellect-ai#2375, PrimeIntellect-ai#2502)

Adopts upstream PR PrimeIntellect-ai#2375 (Quota park and auto-resume) and PR PrimeIntellect-ai#2280 (Provider backup model
and bounded ping waits) into the Prime fork, folding in PR PrimeIntellect-ai#2502 for past-due wake restoration
and branch navigation preservation.

Key features:
- Automatically parks quota-exhausted sessions until provider reset timestamp
- Durable background cron wake jobs via AgentCronJobStore
- Auto-resume via synthetic resume marker prompt when quota returns
- Provider backup model routing for unavailable/quota-blocked primary models
- Event typing for auto_retry_start and auto_retry_end (reason, backupModel, restoredModel)
- Retains fork invariants: schema 34 backwards compatibility, prompt correlation ID preservation,
  no unadopted image-routing or priority assumptions.
@rynfar
rynfar force-pushed the feat/prime-quota-park-auto-resume branch from 41c8bf4 to a802b64 Compare September 25, 2026 19:22

This branch has not been deployed

No deployments
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.

3 participants