Repository navigation
probe: u2d-r1 faulthandler capture over 8ab244c4 (fork-only, do not merge) - #5
Closed
bandzalkin wants to merge 13 commits into
Closed
bandzalkin wants to merge 13 commits into
bandzalkin wants to merge 13 commits into
Conversation
* fix: Use Strands Responses provider for OpenAI tool calls * fix: End Strands native loop after a successful decline * fix: Preserve Bedrock follow-up history after a decline * fix: Limit Strands decline stops to native platform tools * fix: Keep Strands examples runnable against the published SDK Standalone uv run installs the PEP 723 PyPI floor and ignores the local checkout. These examples imported band.core.defaultmodels, which band-sdk 4.0.0 does not ship, so the documented command failed at import. Model ids stay literals, matching the rest of the examples. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: Require the Strands-fixed SDK release in Strands examples band-sdk 5.0.0 on PyPI ships band.core.defaultmodels but not this branch's Strands adapter fixes, so standalone examples resolved a release without Responses history normalization or decline stops. Raise their PEP 723 floor to the next release, drop the now-false defaultmodels surface guard, and restore the ALL_TOOL_NAMES import lost in the main merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPGqG2jLxWPYmPXyivYXxW --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: bound native Google ADK generation retries * fix: enforce Google ADK HTTP attempt bounds * test: centralize Google HTTP peer modes * fix: cap Google ADK generation retry budget in config Reject max_retries above GOOGLE_ADK_MAX_GENERATION_RETRIES and document the upgrade default (one HTTP attempt to three) in the Google ADK examples README. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: band-release-bot <release-bot@band.ai> Co-authored-by: AlexanderZ-Band <alexander.zaikman@band.ai>
…op (band-ai#789) * fix: keep a stopped room's synthetic queue from freezing the event loop A STOP during a contact-hub turn's setup was lost, and the synthetic drain read a message it had put back as progress, spinning without yielding. - TurnOutcome (with PAUSED) replaces the bool and BacklogProcessResult; _aborted_outcome is the one mapping for aborted turns. - Synthetic messages stay at the head of their queue until ADVANCED; _drain_synthetic matches on every outcome and nothing is put back. - _begin_turn is the single admission point for WebSocket, backlog and control-probe turns, so a control is never lost after a turn begins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKLXPCh7iYDz9c8x3TxDpa * test: rename turn-admission platform fixture off the reserved `peer` name The E2E baseline's collection guard rejects any test requesting `peer` without @per_adapter(peer=...), which failed CI's whole-tree collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKLXPCh7iYDz9c8x3TxDpa * test: wait on the durable processed mark after PLAY replay The handler records completion before the processed mark's HTTP call lands, so slower Windows runners asserted the mark too early. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKLXPCh7iYDz9c8x3TxDpa * refactor: tighten turn-outcome dispatch and turn-admission tests - _drain_synthetic blocks on any non-advancing, non-paused outcome instead of re-typing the deferred set, so a new outcome can't spin it. - _aborted_outcome and the resync result dispatch use match/case; the resync match is exhaustive. - _begin_turn logs when a control defers a turn before it begins. - Tests: shared synthetic_message helper, started/stop_room kit helpers, observed-stop INTERRUPT test in its own class, an assertion that could not fail removed, watchdog why-comments moved above their asserts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKLXPCh7iYDz9c8x3TxDpa * test: finish synthetic_message reuse and clarify review-cycle wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKLXPCh7iYDz9c8x3TxDpa --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: preserve diagnostics for stalled Pydantic room turns * fix: retain durable evidence after Pydantic decline loops * refactor: simplify turn diagnostic correlation * fix: restore lint after Pydantic rebase * fix: keep stall diagnostics off the default log level Per-message boundary traces were INFO for every SDK user, and a swallowed output-retry warning dropped the only copy of pydantic-ai's retry sentence. Message lookup now stops after a fixed page ceiling so a missing id cannot walk a long-lived room. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
…room contract (band-ai#709) * feat: own accepted room work and release causes in retention core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: report unfinished execution settlement from stop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: keep failed room cleanup owned and fence its readmission Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: surface incomplete host cleanup and keep it retryable Agent, PlatformRuntime and OneShotInvoker keep failed or cancelled cleanup, and the single-instance guard, until a retry succeeds, while preserving the primary error during unwind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: retain shared MCP backend until its final stop succeeds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * docs: describe shared retention ownership rules Also updates the Agent lifecycle tests to the honest-stop contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: own room startup with retirement and never block presence Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: join one owned OneShot shutdown across callers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: let owned work that requests cleanup unwind uncancelled Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: keep cumulative graceful result across agent stop retries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: hold the platform runtime until its whole stop succeeds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: coordinate OneShot startup and shutdown ownership Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: drop stop intent raised during failed-start unwind Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: drain accepted OneShot events before shutdown disposes Shutdown now refuses new events synchronously and waits, without a new timeout, for every accepted handle_event (turns, acks, room cleanup, cancellation unwinding) to settle before cleanup_all and disconnect. A shutdown requested from inside an accepted event reports CleanupIncompleteError instead of joining itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: resync /next after every routed room admission Events that arrive before a first execution is routed are dropped unclaimed, so every still-desired admission resyncs to recover them; an obsolete admission neither routes nor resyncs. Startup stays unbounded: leave and stop wait for it, then retire it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: split room retention into single-concern modules Config, residency vocabulary, clock, owned-cleanup reporting, one room's lifecycle and the adapter-wide owner each get their own module; the package re-exports the same public surface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: run agent cleanup phases through one loop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: split AgentRuntime room lifecycle and control routing RoomExecutions owns each room's admission, routing and retirement; band.runtime.control owns control dedup, targeting and dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: drop unused control dedup capacity parameter Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * test: split long lifecycle test files into one-concern packages Each former module becomes a package with a conftest for fixtures, a kit for shared helpers and one file per test class or concern. The room cleanup double used by both runtime suites now lives in one helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: replace runtime asserts with non-optional values by construction Task-starting helpers return their task, initialize() returns its link, and the runtime and link are passed to the helpers that need them, so no production path relies on assert for narrowing. The MCP backend's final close names the backend it claims instead of re-checking it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * test: one test class per file, one concern per class Every split lifecycle test file now holds a single Test class; loose module-level tests became classes and helper classes moved to each package's kit. AGENTS.md states the rule for new and touched tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: drive room lifecycle as a signal-driven state machine Each room runs at most one step (start or retire); a signal or that step's completion advances it once through a match on state, intent and ownership. Waiters await a per-convergence future instead of re-running a reconcile loop, and a per-signal step budget fails loudly instead of spinning. Table-row tests cover each transition and the breaker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * refactor: drive retention waits and idle scheduling by signals The idle scheduler is one timer armed for the earliest deadline and re-armed on every room change instead of a polling loop. Disposal waits on a drained signal, and settling and becoming resident run bounded step sequences that wait on a settleable signal and fail loudly at their budget instead of looping forever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * test: add multistage scenarios over a real link, presence and rooms Seven end-to-end flows run the real BandLink, RoomPresence, RoomExecutions, control path and ExecutionContext against a loopback Phoenix server and a stateful REST peer, asserting each room's ordered story: rejoin during cleanup, reconnect during admission, the stop/play control cycle, agent-wide interrupt fan-out, shutdown retry, one-shot drain, and a two-room retention lifecycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: retire a start that fails after withdrawal and keep early room events A start that raises after its room was withdrawn or the runtime began stopping now retires what it built instead of failing the stop. Events for a wanted room that is not routed yet are held, bounded, and delivered in order before live routing, so participant and room events from the startup window are no longer lost; withdrawal discards them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * test: one class per file for the remaining touched suites Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: re-check room intent after held-event delivery before routing Delivering held events awaits the execution, so a leave (and rejoin) could land between the startup intent check and routing, leaving a withdrawn start routed and, after a rejoin, serving instead of replaced. Intent is now re-read with no await between the check and routing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: bind held-event delivery to the admission it was held for Delivery now re-reads the originating admission after every on_event await and stops when a leave, stop or rejoin invalidates it, so events held for a rejoined membership stay for the fresh execution instead of being consumed by the stale one. The stale start is left unrouted for the owner to retire before a replacement starts. Barrier regressions cover leave, stop and leave-then-rejoin during held delivery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiQGxJR2FrTVuRP85Y1dq * fix: keep recovery, startup events, and working-state reports owned An idle release that finished while recovery was recorded cleared that cause and strict-resumed the crashed residency. Startup delivery could skip or drop events that arrived while a full hold queue drained, and a still-legal working:false report could fail execution settlement. Co-authored-by: Cursor <cursoragent@cursor.com> * test: expect a startup flood past the delivery cap to be dropped A full queue's accepted events are delivered, including ones that arrive while it drains. The two events past the delivery cap are dropped for /next, not left held. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: keep a stronger release pending until it actually runs Disposal during an in-flight idle release was committing the idle close and erasing the room without release(FINAL). A stronger cause recorded during a release now stays pending, and a withdrawn admission logs the delivery batch it drops. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: say stop joins a deferred working:false report An inactive reporter is the deferred-POST case. The await returns immediately only when no false send is in flight. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: one owned-task helper logs every orphaned lifecycle failure OneShot lifecycle, retention shutdown, room residency transitions and the final MCP stop each created a task and attached a near-identical failure logger. spawn_owned in band.core.retention.cleanup is now the one place that does it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0127u9A1eQy9JiCBt8yHNotB * fix: give retirements during stop the caller's grace AgentRuntime.stop destroys rooms one at a time. A room whose start finished before stop reached it was retired by the state machine with the leave default (cancel immediately), so a turn it began got no grace and stop reported graceful=False. RoomExecutions.close records the shutdown timeout with the accepting flag, and every retirement after close uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0127u9A1eQy9JiCBt8yHNotB * fix: record a control signal as seen only once it reached a room A signal for a room that was wanted but not yet routed was dropped and its correlation id recorded, so a later copy after routing was ignored too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0127u9A1eQy9JiCBt8yHNotB * docs: state OneShot shutdown's unbounded wait and the guard kept on failed disconnect Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0127u9A1eQy9JiCBt8yHNotB * feat: compose Codex rooms with retained room residency Each Codex room is a RetainedRoom: acquire starts and owns the app-server before its first await, a lost process is recovered by generation and its thread restored strictly (exact threadId, no history, no new-thread fallback, settings and tools verified), and leaving is final erasure. Native events and requests are routed to the turn that started them; autonomous native work is refused rather than charged to a Band turn. Work is serialized per room. Idle release stays refused (BUSY). Shared retention behavioral host and peer tests cover Codex and Claude. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * test: one class per file for the touched Codex and lifecycle suites Pure move: test_codex_adapter.py, test_workspace_lifecycle.py and the Codex adapter flow tests become packages with a kit, fixtures and one concern class per file. The flow tests reuse the shared Codex doubles instead of their own copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * fix: keep early native events ahead of later ones Owned events that arrived before turn/start answered are queued before refusing unowned ones, since a refusal awaits and the reader could otherwise queue the turn's later events, even its completion, first. The lifecycle host records each child's workspace itself so the overlap guard holds for Claude children too, and one helper reads thread and turn ids from Codex results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * fix: pin resumed threads to the room workspace and bound turn leftovers A thread resumed from Band history now sends the room's workspace and sandbox like a strict restore. Without a cwd, Codex reports the thread's recorded one, and a later restore after a process loss refused it as a changed setting, leaving the room stuck until it was left. A finished turn answers its leftover native requests within turn_settle_timeout_s, so a process that stopped reading cannot hold the turn, and with it the room, open. The shared lifecycle host takes each adapter's retained residency as a hook instead of reading Codex internals, and the Claude double honours a held start like the Codex one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * fix: keep archive reachable and stop asking rooms for a lost process Local commands lease the room's Codex process only when they ask it (/model, /models, /reasoning), so /thread archive still works after Codex refuses to restore the thread, as the docs promise. A turn whose process was lost no longer shows that process's queued requests to the room, and an answer that fails because the process exited no longer masks the lost-process failure. The routing doc names the turnId it actually routes by. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * feat: own ACP room residency through one RoomRetention for OMP, Cursor and Copilot Each ACP room is an ACPRoom composed with a RetainedRoom. Detached work is owned before it runs, prompts bind tools under the room lease, and rooms progress independently. A deliberate wake strictly reloads the exact session and reapplies MCP and the model selection; refusals keep the identity. Closure keeps the process handle until __aexit__ succeeds, and final cleanup shares one transition budget with the shared Band MCP server. Cursor sessionless extension requests bind to the originating room process and generation. All three providers stay ProviderActivity.UNKNOWN, so idle release remains off pending native qualification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * fix: address ACP room ownership review findings - Spawn a room's process under its turn's lease, not inside the bounded acquisition, so a slow agent start no longer loops through recovery. - Report and recover session-setup failures like prompt failures. - Reloading a room's own session after a crash keeps the session and the selection on a configuration refusal; only bootstrap candidates close. - Confirm closure at stdout EOF, so a descendant holding the pipes keeps the room closing even when the agent exited first or the retry path runs. - Bound session/cancel so a wedged agent cannot hold disposal forever. - Cursor: register each turn before it can start, hand the message queue back when a turn must wait for its room, cancel every accepted turn on interrupt, and cap pending decisions per room. - Copilot switch maps a stopping retention to the mid-switch config error. - Tests: fix assertions that could not fail, run the pipe holder under the base interpreter on Windows, and cover each fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * fix: hand the queue back for a Cursor turn behind a waking one A turn that started after the room's holder released the reservation, but before the queued turn ahead of it took it, saw a free lock and stayed on the room's message queue. If the turn ahead then parked on a decision, the answer could not arrive until the decision timed out. A turn now also waits off the queue whenever an earlier accepted turn is still ahead of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * feat: own Claude SDK rooms through shared room retention Compose each room's Claude CLI session with one RetainedRoom under a single adapter RoomRetention, replacing the adapter-wide serial command queue. Turns are owned before their first await and lease the room through query, stream, approvals and outcome; tools and MCP routing bind before a CLI starts. Client and transport are retained from before connect until a confirmed close; startup failure, crashes, interrupted turns and Band MCP endpoint changes recover through the owner. Strict resume never falls back to a new session. One continuous receive_messages reader spans turns and the timeout drain. cleanup_all closes rooms, then the shared MCP server, within one transition_timeout_s budget. Idle retention stays disabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: retire Claude CLIs whose output ends outside a turn The single reader now hands a CLI whose output ended while no turn was listening to the room's owner, so the next message never waits on a dead stream. A timed-out turn whose drain reaches the stream's end without a result retires its CLI, and the bootstrap fallback no longer starts a new CLI while a failed one it could not close is still held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * refactor: state the reader's retirement reason once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: retire Claude CLIs whose output ends after a turn's result A turn stops listening once its result arrives, so an end of output the turn will never read is retired by the reader with the crash policy. The fresh-session fallback keeps the conversation until a new one runs, a permission decision is re-checked against a retiring residency, and Claude's retention budget defaults to the SDK's own CLI startup bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: follow the room lifecycle contract in ACP startup and failures - Start a room's ACP process inside its acquisition, so a start that outlasts transition_timeout_s is abandoned and recovered by the owner instead of a second startup path; the room's generation now reads from its RetainedRoom. - Report the failure of a detached Cursor turn that ended before its prompt, so a queued message is never lost silently. - Revert a refused remembered selection on a crash reload, as bootstrap does; only a deliberate wake keeps it. The room keeps its session identity. - Keep one capped decision registry per Cursor room. - Log a failed agent exit, a forced kill, and a descendant still holding the agent's stdout; own the closure and session-close tasks with spawn_owned. - Bound the strict session/load like the fallback load. - Remove the unused lazy respawn in ensure_connection and the test-only bind_session. - Tests: queued-turn start failure, crash reload revert, sessionless Cursor updates, real-process failed-exit retry, shared cleanup budget, restart refusal while rooms close, and a live (not yet run) ACP rejoin smoke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * fix: readmit late-disposed rooms and own sub-agent requests per turn - Keep a leaving room registered until its disposal is confirmed, so a leave reported unfinished can be retried and lift the room's fence. - A Band turn owns requests from sub-agent threads its collab items name; requests from threads not yet named wait for that item, and are refused when the turn ends. - Give Codex its own 60s acquisition budget and report a start that runs over as "Codex process did not become ready in time". - Own the event reader with spawn_owned and log each process loss once, with the transport's reason. - /status reads room state without registering a room. - One builder each for tool and approval responses, event params, and a room's thread binding. - Tests: routing while a turn runs, sub-agent attribution, late disposal, slow start, idle refusal, Codex workspace after rejoin, deterministic early-event fakes, exact restore error types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * fix: keep the Claude session on read failures and share the startup budget The reader forgets a session only when the CLI process is gone, matching the in-turn crash policy; a read failure from a running CLI keeps it. The CLI startup budget is one session-manager constant that both the manager and the adapter config default to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: attribute multi_agent_v2 sub-agents and log each process loss once - A turn also learns its sub-agent threads from multi_agent_v2's subAgentActivity item, which replaces the public collab tool call when that feature is on; the item is routing-only and never rendered. - A lost process's leftover requests are no longer answered into it. - The reader logs each process loss once, with its reason or traceback; a process-not-ready timeout logs the bound that actually expired. - /status reads the active room without registering it. - Tests: v2 attribution in both orders, a held request that another sub-agent's naming leaves held, a lost process's leftovers, /status's room model, and a slow start's close and fresh restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * fix: log a process-not-ready timeout with its traceback A close timeout re-raised through the residency carries no message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * fix: tie Cursor failure reports to the room lease and bound ACP startup A Cursor turn now holds its room through a holder slot set and cleared inside its lease, and reports its failure before the lease passes on, so a queued turn never takes the room while the failed one still answers for it. Failures are posted as generic runtime turn failures, once. ACP agent startup is bounded by a new start_timeout_s setting (10s), because the room owner never cancels an acquisition its caller stopped waiting for; a start that never answers now fails and recovers. Also: one exit-failure traceback naming the pid, a held-stdout warning that covers the SDK's own exit, read-only decision lookups, removal of the dead respawn path, and tests for each (strict-load timeout, held stdout warning, exit/kill logs, leak-free failing-exit wrapper). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * refactor: simplify Claude turn binding and retired-session denial logs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: report Claude turn failures as already reported A failure _converse has already posted to the room is raised as TurnResultAlreadyReported, as the timeout path and the Codex adapter do, so the runtime neither logs it again at ERROR nor treats it as unreported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: refuse unqualified Claude idle releases before teardown The release callback returns BUSY for IDLE with the CLI, transport, reader and session id intact, matching the other unqualified providers; recovery and final releases still close, or keep the CLI for a retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: seal Codex shutdown admission and keep the stopped owner until restart cleanup_all refuses new rooms, turns and native commands before its first await, then reports one owned shutdown (turn settlement plus room disposal) within a single transition deadline. Callers join that shutdown, a failed one is retried, and an unfinished or failed shutdown keeps every room fenced with its process and workspace. A confirmed shutdown stays sealed until on_started opens a new residency owner. The common lifecycle suite now discovers each provider's bridge from its own lifecycleprofile.py, so provider PRs register without editing shared files; the failed-cleanup loss scenario asserts the retained CLEANUP_PENDING room, no replacement, and a successful retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZfyciFpDDVFBfkWkktU6E * test: join the Claude adapter to the common room lifecycle suite The profile reads residency from the room's RetainedRoom and the native id from its ClaudeRoomSession; a lost transport fails the turn as TurnResultAlreadyReported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Veg5G5xrvCPKGZw66SX1pv * fix: serialize ACP settings with prompts and own direct-stop MCP close A model switch now takes the room's reservation before its lease, so it queues behind the room's running and queued prompts instead of changing the model under one; other rooms never wait on it, and a room's leave cancels a waiting switch. A detached Cursor turn that took the room reports its failure only inside its lease; a report lost there is not retried after the lease, so the next queued turn never starts while the failed one still reports. Turns that never took the room still report once. A direct stop closes the shared Band MCP server with the same owned, deadline-bounded close as final cleanup, and reopens it and the residency owner only after the close is confirmed, so a cancelled, failed or overdue close is retried rather than forgotten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * test: bridge OMP, Cursor and Copilot ACP into the common lifecycle suite tests/integrations/acp/lifecycleprofile.py registers the three ACP profiles with the shared Room lifecycle suite from #709, which discovers it; that kit is not on main yet, so this bridge needs #709. Each room process is a real stdio ACP agent (workspacepeer.py) speaking the suite's roompeer vocabulary, started through the adapter's production runtime. Only the spawn is wrapped, to hold its start and hold or refuse its exit. A lost transport surfaces as ConnectionError after the turn reported it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEoSJo9ZG9p4i3Vkp6CwqC * fix: refuse work for a closing room as InputNotAccepted RetainedRoom admission and RoomRetention.register now raise RoomClosingError, an InputNotAccepted that is still a RuntimeError, so the host keeps a message refused before any provider execution instead of reporting a failed turn. Adapters no longer need to tell that refusal apart from a failed cleanup by message text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * fix: retry an unfinished ACP stop on restart and end a cancelled Cursor delivery's turn on_started finishes the previous owner's stop before renewing it: a room whose close still fails keeps its process and raises again, and the owner and shared MCP server reopen only once every room closed. Cancelling a Cursor delivery before its turn detaches now cancels and joins that turn, as an inline OMP or Copilot turn ends with its handler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: run the common lifecycle suite's failed-close cases on all five profiles A refused close now fails one attempt: the suite allows the close before the room retries it, since ACP retries a failed exit by killing the process it still holds. Each case still proves the failed close keeps ownership, starts no replacement while the old process runs, and confirms its exit before one. A held close joined by a second leave covers cleanup that stays pending across callers for every profile. A stopped Claude adapter refuses a message as InputNotAccepted, and Codex relies on core's admission refusal instead of re-wrapping it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * fix(core): recover a residency whose abandoned acquisition never finishes An operation that stopped waiting for its own acquisition, because it timed out or was cancelled (a /stop), only requested recovery, and recovery waited for the acquisition to finish. A start that never returns, such as an ACP agent that never answers initialize, therefore held the room until dispose: every later turn timed out joining it. The initiator now cancels the acquisition it abandons while recovery is the pending cause. The acquire's existing failure path records it and the recovery release closes the half-started handle. A start that resists cancellation stays owned and is never closed while it runs; a pending FINAL still leaves the acquisition to disposal. Another operation that was joining the cancelled acquisition re-evaluates the room instead of failing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * refactor: simplify lifecycle ownership code and test kits Share the room-thread teardown between disposal and /thread archive, drop Claude SDK's write-only per-room session context, give retention snapshots one "disposing" rule for ACP and Claude rooms, pass the retained ACP session into its restore instead of re-checking it, and reuse one agent_input/ROOM_ID/Hold.wait in the test kits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * refactor: one owned-task rejoin rule, one cleanup deadline, one authority rule - rejoin_owned in band.core.retention.cleanup joins a running or settled owned task and restarts it only after a failure; RoomRetention shutdown, Codex cleanup_all and ACPRuntime closure use it. - cleanup_deadline and finish_by give Claude and ACP cleanup_all one adapter-wide deadline that counts only its own overrun as unfinished; a TimeoutError the MCP close raises itself now propagates in ACP too. - RetainedRoom.has_authority is public, and the Claude session's speaks_for delegates to it instead of restating the rule. - docs/idle-room-release.md lists the Claude SDK and ACP integrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * fix: refuse a Cursor message while its room's turn runs; reload once per ACP process Cursor detached a message queued behind the room's turn before it owned anything, which settled its delivery: the runtime acknowledged it processed, so a STOP, leave or shutdown dropped it for good, and a later session_busy refusal was posted as a provider failure. A Cursor message that arrives while the room's turn still runs is now not accepted (InputNotAccepted), so the host keeps it unacknowledged and retries it; decision replies reach the parked turn through try_handle_control. The turn is registered before its task exists and waits for its enrollment, so an eager task factory cannot run it unowned. Turn activation (_accept/_take_room) and decision cancellation each have one path. After a recovery, a remembered selection refused on the reloaded session left it loaded but non-live, so the next turn sent session/load again on the same process. Such a refusal now also recovers the process, and the next turn reloads the session on a new one. Also: restore_session_response is the one session/load path and load_session_response its lenient wrapper; ACPRoom.live_record owns the live-session test; ACP agent errors log their room and generation; ACP tests pin the refusal type, exact errors, the switch refusal branches and the idle BUSY refusal, share stop_budget/SHORT_TRANSITION_S/TEST_GUARD_S, and use one send_to helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * fix: name Codex close overruns and derive the room's residency generation - A Codex process close past client_close_timeout_s raises a TimeoutError naming the room and bound, since it becomes the leave's error. - RoomCodexClient.generation reads the residency snapshot instead of a hand-kept copy. - Tests: a ConnectionError from a live process fails the turn; a stale process loss leaves the replacement serving (asserted by process starts/closes); a process that stops reading still admits the next turn; the STOP retention test is split into two behaviors over a kit client and fixture; autonomous native requests and the fake's thread/model ids are named once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: drive Codex bootstrap turns through the kit and name shared ids - The repeated nine-line bootstrap deliver_message block becomes send_bootstrap (which now takes the message content), and "room-1" becomes ROOM_ID across the Codex adapter test files. - The /status model and transport-closed notices use LISTED_MODEL and TRANSPORT_CLOSED instead of retyped literals. - The token-usage cleanup test drops the stale shared-client setup. - A fileChange session-key docstring states the rule, not its history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: split the Codex adapter grab-bag test classes by behavior TestCodexAdapter and TestReviewFixes covered unrelated concerns under one class each. Their tests now live in one class per behavior: config, turn delivery, approval notification failures, room process close, raw task events, /status, /model and /reasoning, self-config tools, sandbox settings, process-loss recovery, turn timeout, custom tools and execution reporting. The review-fix tests joined the concern files they belong to (slash commands, types, session keying, structured errors, turn lifecycle events). The shutdown suite's TwoRooms double and refused() helper moved to the kit, and its fixture to conftest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: move the fake-client Codex flow tests into the adapter suite The tests under tests/integrations/codex/ for approvals, item forwarding and thread resumption drove the same FakeCodexClient as the adapter unit tests and never shut their adapters down. The approval-id flow now runs on codex_room in the sequential-room approval suite, with no wall-clock poll and CodexApprovalMethod instead of a retyped method. The item-forwarding flow joined the item forwarding suite, asserting through events_of_type with a comment that says what the final agentMessage becomes. Thread resumption moved to tests/adapters/codex/, starting each adapter through a started_codex fixture that shuts it down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: split the ACP room-ownership suite into one concern per module tests/integrations/acp/roomownership/ holds one Test class per file; the shared agents, sessions, bounds and helpers move to its kit.py, and the two real-subprocess closure tests become TestProcessClosure. Assertions are unchanged (46 tests before and after). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: split the ACPClientAdapter suite and share its room-process fixtures tests/integrations/acp/clientadapter/ holds one Test class per file, with connection, adapter and event helpers in kit.py. The identical per-class mock_spawn/adapter_with_mocks fixtures become one conftest pair built on adapter_on, and one room_runtime helper replaces each class's _runtime. The module-level config test joins TestACPClientAdapterConfig. Assertions are unchanged (93 tests before and after). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * refactor: one Claude turn-failure path, one retention default, no dead closing fence - _fail_turn recovers the CLI, reports the one generic turn failure and marks it reported; both failing arms of _converse and the startup failure share _TURN_FAILURE. - _compose_prompt logs and appends bootstrap history in one branch. - default_claude_retention is the single Claude retention default, used by the adapter config and the session manager. - on_message no longer checks session.closing: _admit_turn's closing-room fence and RetainedRoom admission already refuse it first, so it never ran. A held leave now pins that refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: split Claude room-ownership tests by concern; virtual shutdown budget One class per file for parked-turn ownership, retired-CLI authority, conversation continuity and the shutdown budget. The shared-budget test runs on looptime and asserts the budget ran out during the MCP close, which a retry then finishes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: split ClaudeSessionManager tests into a package by concern conftest.py holds the fake-CLI fixture and kit.py the shared doubles (TaskRecordingPeer, Signal) and builders; each concern has one test class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: share the Hold barrier from a provider-neutral module The common lifecycle suite, the Codex profile and the ACP lifecycle bridge imported Hold from the Claude CLI fake, tying them to a provider's private test module and its extra. Hold now lives in tests/adapters/hold.py, which fakecli imports too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: name the lifecycle peers' protocol once The start marker, the refused-start message and the refused-close message were typed separately in every peer and test. roompeer.py now defines STARTING_MARKER, STARTUP_FAILURE and CLEANUP_FAILURE, and the ACP workspace peer reuses its start steps instead of reimplementing them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: pin each provider's failed-start error and bound the start wait LifecycleProfile now declares the exact error a refused start reports, and the one reported when that start's close is refused too, so the suite asserts the type and message per provider instead of accepting either failure from any profile. RetentionHost.start_reached replaces two unbounded polls on the peer's start marker with a bounded wait that names the room. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: run room independence on every profile; move Codex-only lifecycle tests The shared lifecycle directory claims to run against every registered provider, but room independence and several ownership tests were pinned to Codex. Room independence now runs on all five profiles. The Codex-only native-continuity and leave-ownership tests move to tests/adapters/codex/lifecycle/, whose conftest starts the common host on the Codex profile through the shared `hosted` context manager. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: cover Codex and Claude SDK room rejoin in the live baseline Codex and Claude SDK leave and rejoin now go through the same RetainedRoom disposal path as ACP, so the remove-and-re-add smoke runs on them too and is renamed to a provider-neutral test. Expected cells are updated for the new node id and the two added adapters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * feat: final disposal settles accepted work within a grace, admission closed RoomRetention takes settle_grace_s (default 0, the existing cancel-at-once behaviour): a room's final disposal, from a leave or an owner stop, closes admission at once, lets accepted work finish on its own until that grace, timed by the owner's clock, expires, then cancels what remains once and joins it. A retry after a failed release gets no second grace. The caller still learns the outcome within transition_timeout_s, and an unfinished disposal stays owned for a retry to join. RetainedRoom.closing is the one "admits no more work" projection; the ACP room and the Claude session read it instead of restating it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * refactor: Codex leaves and stops through the residency owner's settle grace Codex no longer keeps admission state beside its RetainedRooms. The _shutdown task and _closing_rooms are gone: the owner's settle grace (turn_settle_timeout_s) gives a leaving or stopping room's turn time to wind down with admission already closed, and RoomRetention.stop is the one joinable shutdown. on_cleanup clears the room's approvals and disposes it; cleanup_all clears every room's approvals and stops the owner; on_started finishes a previous stop before renewing the owner. Approvals asked during the grace, and the judging of a turn wound down by cleanup, read the room's RetainedRoom.closing. ApprovalInterruptMixin._admit_turn refuses only a running turn; a closing room is refused by its owner. The owner's final cleanup no longer re-cancels work its own owner already cancelled, so a Codex turn's TurnTask cleanup runs uninterrupted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: pin that final cleanup leaves already-cancelled work's cleanup alone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * refactor: Claude leaves and stops through its residency owner; one retirement shape Claude no longer keeps _closing_all, _closing_rooms or _room_cleanup beside its RetainedRooms. cleanup_all stops the session manager first, which closes admission for every room at once, then the shared MCP server, under one cleanup deadline; on_cleanup clears the room's approvals, disposes its session and drops its state; on_started finishes the previous stop before starting. ApprovalInterruptMixin keeps only the running-turn refusal and interrupts: leave, stop and readmission belong to the residency owner, as for ACP, so Codex, Claude and ACP leave, stop and restart the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * fix: report a failed Claude start before its refused close ClaudeSDKClient.connect closes the CLI before re-raising a failed start, so a refused close escaped in the start's place. The session now closes the failed start itself; when that close is refused too, it keeps the handles for recovery and raises the start's own failure, as the Codex and ACP adapters do. The common lifecycle suite pins the same failed-start error for all five profiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * fix: refuse closing-room work without failing it; one renewal, deadline and enrollment rule Codex turns whose lease a leaving room refuses re-raise InputNotAccepted instead of posting a failure, and a disposed Codex room forgets its record inside its disposal, so a later message can no longer re-claim a released workspace. Cursor extension requests from a residency recovery retired are refused. A failed Claude start reports its own failure whichever close of it fails. The residency owner now owns the shared rules: RoomRetention.renewed() replaces a stopped owner only once its stop closed every room, RetainedRoom.start_owned() runs detached work only once enrolled (Codex turns, Cursor turns, the ACP model switch), RetainedRoom.generation and closing are read directly, and SharedBandMCPBackend.close_after() stops rooms then MCP under one deadline for ACP and Claude. Unjoined transition failures keep their traceback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: pin the settle-grace rows and keep touched test files one class each The disposal-grace tests now observe what their names claim (owned disposal outliving its cancelled or timed-out caller, timer cancelled on drain, cancelled once). Touched module-level test files are wrapped in one class, branching parametrized tests are split, the Cursor delivery helpers moved into the runtime kit, the room-reader retirement test waits on residency state instead of log text, and the Claude shutdown-budget test runs on loop time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * test: bind the OMP form-policy client to a room residency The probe test from main builds its client through _runtime_client_factory, which takes the room and residency generation the client is bound to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw Signed-off-by: Alexander Zaikman <alexander.zaikman@band.ai> * refactor: simplify lifecycle ownership code Inline single-use wrappers (RoomRetention._owned_shutdown, ClaudeSessionManager.stopping, an ACP switch local), drop a restating comment, correct two stale Codex docstrings, and dedupe test kit wait loops and the Cursor question filter. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: share one time-bounded until() wait across provider suites Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: give the ACP session_busy refusal one source of truth Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: assert retention states on observed progress, not loop turns The stop-authority check now asks for the stop from inside a turn, where stop() returns before its shutdown runs, instead of counting one loop turn. The joining-operation check waits with until(), and the cancel-resisting start is checked while it is provably parked at its opening barrier. Review items 16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * fix: log an owned task's failure only when no caller reports it Every caller that joins an owned retention task re-raises its failure, and the caller then logs it, so the done-callback log duplicated it: one failed acquire gave two tracebacks, one failed release during stop gave five. Joiners now wait through await_owned, which marks them as the task's reporters; the done callback logs only a failure nobody waits on, so unjoined idle and recovery releases still log once. An abandoned start cancelled for recovery now logs a warning, and its initiator's timeout says the start was abandoned rather than still running. Review items 64, 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: drop the unused room-scoped DecisionRegistry API Adapters keep one registry per room, so cancel_room, unclaimed_in_room and register_minted's room_id had no src caller. The teardown test now drives cancel_all on a room's own registry. Review items 4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: drive Codex workspace isolation through the shared kit patch_codex_client serves an ordered sequence of processes, so a process-replacement test no longer hand-patches _build_client. The per-room thread workspace test opens two rooms through the kit instead of an inline client and the private _ensure_thread, and the two synchronous workspace tests are plain functions. Review items 49, 50, 51. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * fix: keep a Codex room's model across process loss The room's selected model is the room's choice, but _release cleared it as if it belonged to the process. A recovered process then re-picked a model from its own model/list (or the default when that failed), and the strictly restored thread's next turn/start silently switched models. _release now leaves it, _acquire selects only when the room has none, and _dispose already erases it with the room. Review items 55. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: one workspace-claim rollback for room registration RoomWorkspaces.claiming claims a room's workspace and gives it back if the setup inside raises. Codex _room_client and ACP _room both register through it, replacing two hand-written rollbacks that had drifted (one released by workspace, one without). Codex's RetainedRoom field is now named retained, as in ACP and Claude. Review items 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: leave idle-release refusal to the residency owner Codex, ACP and Claude each refused an IDLE release inside their release callback, but none of those guards could run: the owner refuses an idle release unless the provider reported QUIESCENT, and all three configs reject idle_timeout_s. The owner's rule plus the config validators are the one policy. The three tests that called the callbacks directly to pin the dead guard go with it. Review items 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * docs: pin the Codex start budget and its failure text The Codex page restated CODEX_TRANSITION_TIMEOUT_S and PROCESS_NOT_READY_ERROR in prose with nothing checking them; a runnable block now asserts both, like the Claude SDK page. Review items 61. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: bound Codex test waits by time, not loop turns answer_to, disposed and FakeCodexClient.read_by_adapter wait through the shared until() instead of counting 200 sleep(0) polls, so an extra await on the adapter's reader or close path cannot exhaust the budget. MAX_LOOP_POLLS goes away. Review items 25. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: cover Codex's refusal of unknown server requests An unknown server request is answered with method-not-found inside a turn and outside any turn, and a refusal the process cannot take is logged while the room keeps serving. Nothing else stops such a request from waiting forever. Review items 24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: a Codex turn timeout whose interrupt fails still reports once FakeCodexClient takes an interrupt_error, and a timeout row with a failing turn/interrupt asserts the timeout is the turn's one failure and the room's next message still reaches Codex. Review items 54. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: give the Codex close-timeout test its own bounded host The test built its bound by reassigning the running adapter's frozen config twice. A close_bound_host fixture now installs the adapter with a named PEER_CLOSE_BOUND_S, long enough for a real peer to close but always overrun by a held close; the host fixtures share one codex_host builder. Review items 27. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: prove Codex recovery keeps usage through /thread info The recovery test planted a private _token_usage entry and asserted it back. It now scripts a thread/tokenUsage/updated event before the process exits and reads the usage and thread id the room sees from /thread info, with the thread id taken from the thread/resume request. Review items 28. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: give the Codex peer's rollout refusal and home one source roompeer.py now defines NO_ROLLOUT and CODEX_HOME beside its other protocol strings; the peer's refusal and --home default use them, and lifecycleprofile and the continuity and ownership tests import them instead of retyping the literals. --home keeps its default: the integration cleanup test and the Claude peer launch roompeer without it. Review items 30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: name the Codex tests' approval, hang and close bounds PATIENT_APPROVAL_S, HANG_GUARD_S and CLOSE_BOUND_S in the Codex kit replace ten bare approval_wait_timeout_s=3600, four timeout=5 guards and a 0.5s close bound that was retyped inside its own match string, now built with re.escape from the constant. Review items 29. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: one DECLINED/ACCEPTED answer for the Codex tests The kit defines the approval answers once and builds REFUSED_AUTONOMOUS_TURN from DECLINED; the leave-grace, ownership, sequential-room and session-validation tests use them. The leave-grace helper that calls cleanup_all is named shut_down, since it is adapter shutdown, not a room STOP. Review items 31. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: pin the Codex shutdown failure inside its exception group The failed-close shutdown test accepted any ExceptionGroup. It now expects a group holding exactly the held room's RuntimeError, matched on the kit's CLOSE_FAILURE, which CountingCloseClient raises. Review items 32. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: one residency_of lookup for the Codex tests The kit's residency_of(adapter, room_id) is the single reach into the adapter's private room map: lifecycleprofile's bridge and the retirement, ownership and status tests use it instead of each indexing _room_clients on its own. Review items 33. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: one FailingNoticeTools double for the Codex tests The kit's FailingNoticeTools rejects every notice with NOTICE_REJECTED; the approval-notification and slash-command tests use it in place of four identical inline classes, and match the rejection on the constant. Review items 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * fix: let a closing room's refusal of a model switch reach its caller Enroll the switch before the mid-switch handler, so a room that is already closing raises RoomClosingError instead of 'ACP session ended mid-switch'. Review items 56. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * fix: refuse a Cursor permission from a retired residency _active_turn_for now judges the room holder through _turn_of, so a permission request from a process recovery retired is refused like a question, instead of reaching the room or auto-allowing a Band tool. The direct permission tests now activate their turn on a resident room. Review items 66. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: state the Cursor detached-turn reason once in on_message Review items 60. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * fix: make an ACP stop final until on_started serves again cleanup_all() and stop() only tear down. on_started finishes a pending stop, renews the residency owner and reopens the shared Band MCP server, as Codex and Claude do, so a stop() racing Agent.stop() can no longer reopen the server or admit turns after a final teardown. The final flag and its stale docstring are gone. Review items 5, 59. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: bound an ACP start only by the residency owner Drop start_timeout_s and its own asyncio.timeout around runtime.start(). ACPClientAdapterConfig's retention now defaults transition_timeout_s to ACP_TRANSITION_TIMEOUT_S (30s), and a lease whose start or stop outlasts it fails the turn with PROCESS_NOT_READY_ERROR, as Codex does. docs/acp.md pins the default with a runnable block. Review items 61, 62. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: probe Copilot's persisted selection on a registered room Build the probe runtime from spawner._room(room_id) and its generation instead of a hand-made ACPRoom whose retained owner is never set. Review items 52. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * fix: forget a Claude room from its owned disposal A leave whose caller was cancelled or whose report timed out skipped forgetting the room's adapter state, although the owned disposal still finished later; a rejoined room then reused the old dedup cache and suppressed a repeated reply. The session manager now reports each confirmed disposal, and the adapter forgets the room there, as Codex and ACP do in their dispose callbacks. Review items 63, 67. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * refactor: start Claude's detached turn through start_owned on_message hand-wrote create_task plus own(), the enroll-before-run contract RetainedRoom.start_owned already owns for Codex and Cursor. owner_ready now only hands the turn its TurnTask. Review items 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: define the Claude OpenRoom alias once Review items 42. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: read Claude room internals once through ClaudeRoom ClaudeRoom exposes the session manager and Band MCP backend and restarts its adapter with one set of agent metadata; the shutdown budget test asserts the backend still runs instead of its identity, and the shutdown race drops a loop yield that create_task ordering already guarantees. Review items 37, 39, 40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: share the scripted Claude CLI and the real-child launcher scripted_claude() patches the session manager's transport factory by attribute for both suites, and launch_children() replaces four copies of the build-append-monkeypatch peer launcher. Review items 35, 36. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ifWtjnzfd5YPdEgrPTdcw * test: name the fake Claude CLI's failures once The fake raises STARTUP_EXITED and CLEANUP_FAILED, which the tests match with re.escape; a real child's refused close matches roompeer's CL…
…-ai#804) * feat: configure the stopped-room reconciliation interval A room paused by STOP probes /next at the base interval forever, because those probes are the only way to discover a missed PLAY. SessionConfig.stopped_resync_seconds lets operators trade PLAY-discovery latency for request volume on paused rooms. Unset keeps the base interval. * fix: validate session settings and exercise stopped polling through real wiring * refactor: split runtime types and import directly from their owners * fix: complete direct runtime owner imports on current main * refactor: clarify session timing invariants --------- Co-authored-by: Alexander Zaikman <alexander.zaikman@band.ai>
…h 27 updates (band-ai#796) * chore(deps): bump the uv-minor-and-patch group across 1 directory with 27 updates --- updated-dependencies: - dependency-name: band-client-rest dependency-version: 0.0.46 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: python-dotenv dependency-version: 1.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: anthropic dependency-version: 1.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: langchain dependency-version: 1.4.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: langchain-core dependency-version: 1.6.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: langgraph dependency-version: 1.2.14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: langchain-openai dependency-version: 1.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: langchain-anthropic dependency-version: 1.7.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: langchain-text-splitters dependency-version: 1.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: claude-agent-sdk dependency-version: 0.2.165 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: github-copilot-sdk dependency-version: 1.0.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: werkzeug dependency-version: 3.1.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: crewai dependency-version: 1.15.27 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: a2a-sdk dependency-version: 1.2.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: slack-sdk dependency-version: 3.45.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: aiohttp dependency-version: 3.14.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: boto3 dependency-version: 1.43.111 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: fastapi dependency-version: 0.143.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: agno dependency-version: 3.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch - dependency-name: pytest-mock dependency-version: 3.16.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: packaging dependency-version: '26.3' dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: pydantic-ai-slim dependency-version: 2.55.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: opentelemetry-sdk dependency-version: 1.45.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: opentelemetry-instrumentation-logging dependency-version: 0.66b1 dependency-type: direct:production dependency-group: uv-minor-and-patch - dependency-name: ruff dependency-version: 0.17.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: commitizen dependency-version: 4.19.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: uv-minor-and-patch - dependency-name: typer dependency-version: 0.27.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: uv-minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> * fix: adapt Pydantic AI adapter for pydantic-ai 2.55 / httpx2 Resolve OpenAI models with legacy httpx clients, end terminal declines without an extra model step under tight usage limits, and align test tool-call fixtures with JSON wire shapes. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: use public pydantic-ai types for the OpenAI httpx workaround The provider-prefix list was duplicated, the factory had no return type, and nothing checked that openai model strings still get a legacy httpx client. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexander Zaikman <alexander.zaikman@band.ai> Co-authored-by: Cursor <cursoragent@cursor.com>
* feat!: upgrade OpenAI with isolated legacy adapters Require OpenAI 3 for Pydantic AI and modern test environments; preserve OpenAI 2 for CrewAI and shared Strands/NOOA tooling. Keep separate execution lanes, compatible LangGraph peers, and complete coverage. BREAKING CHANGE: modern Pydantic AI extras cannot co-install with current CrewAI, Strands, or NOOA dependencies on their supported platforms. Refs INT-1791 * fix: normalize legacy coverage paths on Windows Compare pytest node paths using portable forward slashes and sort the renamed shared-probe import. * refactor: clarify wheel checks and Strands NOOA environment names * refactor: clarify extracted test helper documentation * refactor: derive wheel CI cases from consumer catalog * fix: end native LangGraph loops after successful declines * fix: isolate bespoke Copilot smoke runtime homes * test: capture Parlant tool metadata in diagnostic runs * chore: expose bounded diagnostics for unsettled native runs * test: preserve upstream diagnostics coverage after rebase * fix: enforce the NOOA wheel OpenAI 2 contract
Port the reviewed cancellation correction from dd8f3af..942759f4. Delegate acquisition cancellation explicitly to the matching startup owner, preserve first abandonment/cleanup diagnostics, and cover public caller/cleanup orderings after uncancel. Keep Claude-owned retention changes in the stacked Claude PR.
Owner
Author
|
Fork-only single capture run complete; closing. |
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.
Fork-only diagnostic for band-agent task 13. Parent exact 8ab244c; observer install + artifact retention steps only. One run, no reruns. Will be closed and deleted.