Repository navigation
feat(server): admit new turns only while the host has headroom - #20
Merged
Merged
Conversation
…use the most Agents starved of memory freeze silently: in the incident the kernel throttled the agents slice for hours and nothing told the user why every turn hung. The server now reads memory pressure (Linux PSI for the host and the agents slice, macOS's pressure level) and derives none/elevated/severe with hysteresis. Clients that opt in get it on the config stream. While pressure is severe, web shows a sidebar warning and mobile a banner on the thread list, both naming the heaviest agents with links to their threads and Force stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server-side admission service decides when a queued or new run may start: an optional hard cap on running turns and an optional pacing mode driven by the host's memory pressure and CPU. Waiting runs stay queued with an `admissionWait` reason and start oldest first through a cross-thread pump. Running turns are never paused; steer and interrupt are never gated, and "Start now" bypasses admission. A turn blocked in `t3_thread_wait` frees its slot. Clients show the reason, live counts, a Queued status, and the two settings; MCP results carry the wait reason. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The sidebar list resolves status with resolveSidebarThreadStatus, which mapped a queued run to Working, so only the command palette showed the static Queued label. Rows now read a per-thread wait flag, so they re-render only when their own wait starts or ends. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ait on cancel Runs left waiting by the previous process had no trigger to start them, so they stayed queued with free capacity. The first pump now runs after recovery, because pumping earlier raced the reconcile that cancels unstarted runs. Cancelling a waiting run also clears admissionWait. Fixes two typecheck errors in the host admission tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The replay harness exported its own settings service for the host capacity tests, so harnesses that provide their own settings (thread launch, MCP) read the hidden copy and missed per-project provider changes. It now exports one only when host capacity is requested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
jappyjan
marked this pull request as ready for review
November 8, 2026 12:22
…ttings follow the host Review found web and mobile deciding "Queued" differently (web needed the opt-in live list, mobile used the thread's own runtime), copy duplicated across clients, settings shown for servers that cannot store them, and a mobile Start now the server would refuse. - One shared check (`threadRuntimeAwaitsTurn`) from the thread's own runtime, used by web rows, the command palette, mobile rows and pipeline status helpers, so pipeline parents no longer show Working for a queued step. - Wait reasons, summaries and the default limit live in client-runtime and contracts. - Host capacity settings appear only for one environment whose server reports the capability; the live counts show only while a control is on; search entries added. - `canStartNow` per queued run, shared by web and mobile. - Mobile labels a not-yet-started delegated task Queued; the sidebar pill truncates, names the environment and states the actual reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…serves the oldest turn first Review found the admission pump re-requesting itself forever while any turn waited (about 3,000 passes a second in a test), the pump starting turns that startup or shutdown recovery then cancelled, and several ways for a newer turn to overtake or stall older ones. - A denial no longer requests another pump; run creation now triggers one. - The pump stays off until startup recovery finishes and from the start of shutdown; reconcile events never trigger it. - `decide` counts older waiting threads against the free slots, judging a thread by its oldest waiting turn. A thread held back for another reason (usage limit, mode ceiling, held queue) loses its wait marker so it cannot block newer turns. - A message on a thread with a waiting turn queues behind it; a run queued only by admission no longer inherits a held queue's hold; fork transfers skip admission like merge-back; a requeued launch keeps its place ahead of follow-ups. - `delegate_task` / `task_status` waits free the caller's slot like `t3_thread_wait`. - The snapshot queries use the recovery index; the slot reservation is registered under the gate; CPU and pressure are read before it; the state dedupe resets when the last listener leaves. - The replay harness always builds host admission, so existing orchestration tests run the unlimited path the server uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The replay harness's settings export follows the option's rename to hostAdmission, so harnesses that provide their own settings keep reading them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jappyjan
force-pushed
the
feat/multi-repo-projects
branch
from
November 8, 2026 12:44
60f70df to
df10fa6
Compare
jappyjan
force-pushed
the
feat/host-capacity
branch
from
November 8, 2026 12:44
ac3cdc1 to
b1d9dde
Compare
Thread transfer impact
This comment will update automatically after the next completed run. |
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.
Many parallel chats, scheduled runs, delegated tasks and pipeline steps can overload one machine. This adds server-side turn admission per environment, with two opt-in controls (both off by default): Pace new turns by host load (hold turns at severe memory pressure, one per 30 s while elevated or CPU is busy) and Limit running turns (1–64). Waiting turns stay
queuedwith anadmissionWaitreason and start oldest first through a cross-thread pump. Running turns are never paused. Steer, interrupt and stop are never gated. "Start now" bypasses admission; MCP has no bypass. A turn blocked int3_thread_wait, or indelegate_task/task_statuswithmode: "wait", frees its slot.Stacked on
feat/multi-repo-projects. The first commit is the port of pingdotgg#27253 (memory pressure), kept unchanged so it can be dropped once upstream merges it. Docs:docs/user/host-capacity.md,docs/internals/host-admission.md.Known:
ServerEnvironment.test.ts(macOS path), Playwright browser tests (no Chromium), and anOrchestratorMcpServicemodel-manifest test ("gpt-5.5 not advertised") fail locally and are outside this change.QA findings fixed in this PR
admissionWait; it is now cleared.Review findings fixed in this PR
Seven parallel reviews (correctness, requirements, architecture, security, performance, tests, UX) plus a re-review of the fixes.
decidenow counts older waiting threads, judging each by its oldest waiting turn. A thread held for another reason drops its wait marker so it cannot block newer turns.delegate_taskwithmode: "wait"kept the parent's slot, so at the cap the parent and its child blocked each other until the wait timed out.Open
task_statusdescribesworkStateas working, waiting_for_children or result_available; it does not mention the newqueuedvalue. Changing that text needs themcp-tool-useevals, and this machine has no API keys.queued). This predates host capacity.hostAdmissionout of the server config map so live counts don't recompute the sidebar; check the cap before loading a thread in the pump; trigger the pump on unarchive.QA (before the review fixes): verified on web with the Replay provider (cap, Start now, Remove, oldest first, scheduled task, restart, light/dark, 375 px) and on an iOS simulator. Severe memory pressure and the memory warning were covered by unit tests only.
🤖 Generated with Claude Code