feat(retry): park quota-blocked sessions until provider reset and auto-resume (#2375, #2280, #2502) - #82
Open
rynfar wants to merge 29 commits into
Open
feat(retry): park quota-blocked sessions until provider reset and auto-resume (#2375, #2280, #2502)#82rynfar wants to merge 29 commits into
rynfar wants to merge 29 commits into
Conversation
…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)
…t queueing behind serialized work (PrimeIntellect-ai#2378)
…art instead of failing (PrimeIntellect-ai#2391)
… an async-bash notice is withdrawn (PrimeIntellect-ai#2386)
…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#2459) (cherry picked from commit 63d8831)
…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
force-pushed
the
feat/prime-quota-park-auto-resume
branch
from
September 25, 2026 19:22
41c8bf4 to
a802b64
Compare
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
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
isQuotaParked: true).AgentCronJobStore, surviving daemon restarts.<provider_quota_resumed>), continuing the interrupted task.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.auto_retry_start(reason,backupModel) andauto_retry_end(restoredModel) in public and daemon wire event interfaces while keeping fields strictly backward-compatible.success: trueon aborted retry loops.Invariant Checks
type: "custom"with custom payloads; no core session entry types or daemon schema IDs modified.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).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)Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.