Skip to content

chore: validation-only CI for composed 4fc8f84d (do not merge) - #8

Closed
bandzalkin wants to merge 17 commits into
mainfrom
u2d-composed-ci-4fc8f84d
Closed

bandzalkin wants to merge 17 commits into
mainfrom
u2d-composed-ci-4fc8f84d

Conversation

@bandzalkin

Copy link
Copy Markdown
Owner

Validation-only temporary PR for task17 (r3-u2d-composed-ci). DO NOT MERGE.

Purpose: one initial stock ci.yml run for the composed SDK target
4fc8f84d256d9d9dbc4f68a292f54f4005fbe1e9 (tree 483429a544e67d988104df3b9d0928dd875a621e)
against unchanged fork main 5d3c72504290958fae3de263b3264e477cafde78.

No source, test, workflow or matrix changes. No rerun. This PR is closed without merge and its
temporary branch deleted after the run reaches a terminal state.

AlexanderZ-Band and others added 17 commits October 13, 2026 14:04
* 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
* fix: give each baseline provision its own agent name

A session-scoped name made a leftover registration fail later cells with name-taken.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor: name the provision token for what it is

Mentionable labels take a fixed short token. Only longer labels take a uuid slice.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep a recorded agent out of the orphan sweep

Per-provision names do not contain the session id, so an aged staging agent was swept and the follow-up delete blocked replacement. The name budget shares one separator.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: describe the session id as the sweep key

Provisions draw their own name token. The session id is only what the orphan sweep skips.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep provisioned names inside the API-key cap

Registration creates the agent and an API key in one transaction, and the
key name is the agent name plus a suffix that is also capped at 100.
Budgeting only the agent row lets a long label roll the register back.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Always replayed: the fork's only commit that is not an upstream pull request.
@bandzalkin

Copy link
Copy Markdown
Owner Author

task17 validation complete (run 38850442991). Closed without merge.

@bandzalkin bandzalkin closed this Oct 16, 2026
@bandzalkin
bandzalkin deleted the u2d-composed-ci-4fc8f84d branch October 16, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants