Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This changelog is automatically generated by [Release Please](https://github.com/googleapis/release-please).

## [Unreleased]

### Bug Fixes

* **codex:** keep readiness cancellation from interrupting startup settlement or
closing its client early when the public message caller is cancelled or room
cleanup joins startup settlement, even when settlement consumes the task's
cancellation count.

## [6.0.1](https://github.com/band-ai/band-sdk-python/compare/band-sdk-v6.0.0...band-sdk-v6.0.1) (2026-10-20)


Expand Down
66 changes: 64 additions & 2 deletions docs/adapters/claude_sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,67 @@ reuse the failed client or take another room's claimed directory.
such as `settings`, `agents`, `bare` and `dangerously-skip-permissions` that
would bypass Band's wiring, permission gating or host-config isolation. `env`
reaches only the Claude CLI process; the host's `os.environ` is untouched.
- **`resolve_mcp_servers=` adds per-connection MCP servers.** This keyword-only
constructor argument is an async callback `(workspace) -> {name: McpServerConfig}`
that returns the complete set of external servers (native stdio, `http` or `sse`
configs) for a new Claude connection in that room's workspace.
- It runs once per new connection, under the room's retained acquisition; reusing a
room's connection never calls it, so a rotated grant reaches only a later
connection. An empty result means no external servers. A failure to
resolve, read or validate the servers, or to see them connect, fails the
turn with `ClaudeMcpStartupError` and never resumes without the room's
history; only the CLI itself failing to start (a session that can't be
resumed, for one) is retried as a fresh session, which resolves again.
- Band's server is added separately; the name `band`, in-process `sdk`
servers, names that aren't plain strings and a `type` that isn't a string
are refused by name. The servers reach the CLI only as the path of a
private file, never on its command line. On POSIX, its directory is `0700`
and its file is `0600`. On Windows, privacy relies on the user-profile and
temp-directory ACLs, not POSIX mode bits. The file stays until the CLI's exit
is confirmed and the file is removed.
- The session is used only after every server, Band's included, reports
`connected` through the CLI's `mcp_status`; anything else within 30 seconds
(each status request included) fails the turn. Errors and logs name the
stage and server, and the exception type only when it is exactly one of a
few builtin classes (such as `LookupError` or `OSError`); they never read
an exception's own name or module, and never carry callback values,
credentials or native error text, even for a `ClaudeMcpStartupError` the
callback raised, or when the check of a held client's servers is what
fails, with or without a resolver. Such a failure never starts the room's
session again without its history.
- External tools stay under `permission_mode` and `approvals`; they are never
added to the tool allowlist.
- **A starting connection is owned until it settles.** Accepted operations share
the room's retained acquisition, after any outdated CLI is closed. If the
initiating operation stops waiting, the acquisition is cancelled (even if the
callback swallows it), or the room is cleaned up, the startup is retired: it
publishes nothing, is cancelled at most once, and keeps its process, file and
workspace claim until it settles. A resolver may itself request cleanup:
adapter cleanup returns without waiting on that acquisition, while direct
manager cleanup reports `CleanupIncompleteError` to avoid waiting on itself.
The request fences admission immediately, and the retained room completes
disposal once the resolver and its accepted turn finish unwinding.
Admission is rechecked, without calling out, after every callback the startup
runs: a callback that closes admission or retires the room stops any later
reuse, client construction or publication. The reuse decision is settled to a
plain yes/no before that last check, so reading it again calls nothing.
The shared turn owner is bound before tool binding or asynchronous preparation,
so STOP and room retirement also end a turn held in resolution or readiness.
A still-settling turn keeps the room fenced; its provider timeout starts only
at the query, not during preparation.
- **Teardown keeps what it can't release.** `cleanup_all()` refuses new turns
and session startups as soon as it is called, and keeps refusing them until a
successful `on_started`. A CLI whose exit can't be confirmed within 5 seconds of
being killed, or a private file that can't be removed (including one only
partly written), is kept with its workspace claim and the Band server it may
dial, and the cleanup raises a safe error; calling it again (or `on_started`)
retries. A failed room cleanup keeps new input unaccepted until `on_cleanup()`
succeeds on an explicit retry. A file or client left by a failed startup is
retried before a later turn starts a replacement connection.
Concurrent `cleanup_all()` calls share one teardown that a cancelled caller
can't abandon.
The physical-exit owner accepts the native child before a transport can defer
its own exit check; a deferred check never replaces the owner's kill and reap.

```python
import pytest
Expand Down Expand Up @@ -156,8 +217,9 @@ so a slow CLI in one room never holds up another.
discards that start.
- **Shutdown shares one budget.** `cleanup_all` closes every room, then the
shared Band MCP server, within one `retention.transition_timeout_s`. An
unfinished or failed step raises, stays owned, and a retry joins it; the
server stays up while any room's CLI may still dial it.
unfinished or failed step raises and stays owned; a retry joins it. The whole
shutdown continues after a cancelled caller or an expired reporting budget,
and the server stays up while any room's CLI may still dial it.

```python
import pytest
Expand Down
78 changes: 78 additions & 0 deletions docs/adapters/codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ For existing integrations, follow the [SDK 4.x to 5.x migration](../acp.md#upgra
First-prompt context refreshes communication instructions, not saved tool schemas.
- **`reasoning_effort` is not validated.** The valid values depend on the model
and the Codex CLI version, and the backend rejects unknown ones.
- **POSIX cleanup settles the original owned process group.** Close allows the
leader one second to exit on stdin EOF, then sends TERM to its original group,
waits up to one second, and escalates to KILL with one more second to confirm
`ESRCH`. Unconfirmed cleanup retains the client and workspace for retry.
A reused leader PID is never signaled. POSIX groups lack generation-bound
handles: reuse between a probe and signal remains a residual race.
Descendants that leave the group are outside this guarantee.
- **`skill_roots` need Codex CLI 0.136.0 or newer.** Codex has no config key
for extra skill folders, so the adapter sends them to each room's app-server
with `skills/extraRoots/set` before the room's thread starts. An older CLI
Expand Down Expand Up @@ -111,3 +118,74 @@ Approval commands use the token displayed in the room prompt. Native Codex
request and approval IDs stay internal; a fresh request gets a new chat token
after completion or room cleanup, even when Codex reuses its native ID. A
redelivery of a still-pending request keeps its displayed token.

## Runtime MCP servers

Pass the keyword-only `resolve_mcp_servers` callback to `CodexAdapter`, not
`CodexAdapterConfig`. It receives the room's absolute workspace and returns a
`Mapping[str, CodexMcpServer]`. Import the descriptors and startup error from
`band.integrations.codex.mcp`.

- `CodexMcpStdioServer(command, args=(), env={}, cwd=None)` freezes arguments
and child-only environment values. Relative commands and arguments use the
server's `cwd`. On POSIX, nonempty `env` uses the SDK's interpreter to restore
generated aliases before replacing itself with the server process; native
protected environment names are refused. On Windows, nonempty stdio `env`
fails before client construction with stage `unsupported-platform`:
`secret-bearing stdio MCP servers require POSIX exec`. Env-free Windows stdio
and HTTP servers remain supported; no Windows supervisor is included.
- `CodexMcpHttpServer(url, bearer_token=None, headers={})` describes streamable
HTTP. `url` must be an absolute `http://` or `https://` endpoint with a host.
Its origin and path are public: they appear in argv, repr and supported
Pydantic serialization, so never put credentials there. URL authentication
is unsupported: userinfo, every query string (regardless of parameter names)
and fragments are refused, including empty `?` or `#` suffixes. Put credentials
in `bearer_token` or `headers` instead. Invalid URLs raise
`CodexMcpStartupError` with stage `invalid-descriptor` without echoing the URL;
descriptor construction inside a resolver is reported as stage `resolution`.
Other descriptor types, including SSE, are refused. Names must match
`[A-Za-z0-9_-]+`, be unique, and not be the reserved native name `codex_apps`.
- Resolution runs inside the retained room's acquisition, once per new process
after any predecessor is confirmed closed. Reusing a process does not resolve
again. To rotate a running grant, clean up the room first. An empty result
adds no servers or readiness requests; callback failures never fall back.
- The snapshot is frozen before launch without changing configuration or
`codex_env`. Validated TOML overrides are appended after existing arguments,
including a relay's `--` boundary. Supplied servers are enabled, required and
use connection readiness; other native servers are unchanged.
- Bearer tokens, headers and stdio environment values use generated child-env
names, never secret values in generated TOML or argv. Names reserve inherited
env names too. Secret fields are excluded from repr, equality and supported
Pydantic serialization; these objects are runtime-only, not persistence data.
- Before the process becomes resident or restores a thread, readiness pages
`mcpServerStatus/list` under one 30-second deadline. Every supplied server must
report information and capabilities, no discovery error, and null pre-thread
or `connected` runtime status. Tool-less servers are supported. Auth settings
or an empty catalog alone are not readiness proof. Malformed pages, duplicate
names, repeated cursors, negative statuses and consumed timeout cancellation
fail startup.
- Cancellation and retirement fence each stage and every discovery request.
A resolver may request its own room cleanup without self-waiting. Cleanup and
acquisition abandonment disarm readiness deadlines through the startup
cancellation owner, which records and delivers the first cancellation with
its abandonment or cleanup reason. Later cleanup joins that same cancellation
authority even when settlement consumes the task's cancellation count; it
never sends a fallback cancellation into owned settlement. Explicit cleanup
retries retain their separate cancellation semantics. Settlement beyond the
reporting bound retains acquisition, child, workspace and diagnostic scope
until cleanup succeeds.
Failed registration unwinds the gated worker before resolution can start.
Invocation-only cancellation timers end with their owner, never later work.
- Thread start/resume errors with adopted servers trigger bounded readiness
rather than error-text classification. Failed readiness refuses history
fallback and retires the process; the next acquisition resolves a fresh grant.
Positive readiness preserves genuine missing-history fallback and unrelated
errors. Recovery still strictly restores retained threads as described above.

`CodexMcpStartupError.stage` is a secret-free startup or validation category.
Transport diagnostics omit RPC methods, wire contents and process-stderr text.
Unknown frames report transport, type and character length; stderr reports byte
length; full queues report only the dropped event kind; cleanup reports only
exception type. Forwarded RPC errors remain scoped-redacted and are frozen
before teardown clears the secret scope.

Loading
Loading