Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/scripts/setup-omp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Install Bun and pin OMP (@oh-my-pi/pi-coding-agent) for the `backends` e2e lane.
set -euo pipefail

OMP_PACKAGE="${OMP_PACKAGE:-@oh-my-pi/pi-coding-agent@18.2.8}"
OMP_MIN_BUN="${OMP_MIN_BUN:-1.3.14}"
OMP_PACKAGE="${OMP_PACKAGE:-$(uv run python -c 'from band.integrations.omp import OMP_PINNED_PACKAGE; print(OMP_PINNED_PACKAGE)')}"
OMP_MIN_BUN="${OMP_MIN_BUN:-$(uv run python -c 'from band.integrations.omp import OMP_MIN_BUN; print(OMP_MIN_BUN)')}"

if ! command -v bun >/dev/null 2>&1; then
curl -fsSL https://bun.sh/install | bash
Expand Down Expand Up @@ -31,6 +31,6 @@ fi
bun --version
bun install -g "${OMP_PACKAGE}"
omp --version
omp acp --help >/dev/null
omp --mode rpc --help >/dev/null

echo "OMP ready (package=${OMP_PACKAGE}, min Bun=${OMP_MIN_BUN})"
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ jobs:
path: ~/.cursor-agent-*
key: ${{ steps.cursor-cli-cache.outputs.cache-primary-key }}

- name: Install Bun and OMP (omp_acp)
- name: Install Bun and OMP (omp)
if: matrix.lane == 'backends'
run: bash .github/scripts/setup-omp.sh

Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/python-core-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ concurrency:

jobs:
coverage:
# Dependabot cannot read repository Actions secrets, so it cannot check
# out the private Core repository with this workflow's token.
if: github.actor != 'dependabot[bot]'
# Fork pull requests and Dependabot cannot read repository Actions secrets.
# Validate fork revisions through a reviewed upstream branch and manual dispatch.
if: >-
github.actor != 'dependabot[bot]' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
timeout-minutes: 30
env:
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@ 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]

### Breaking changes

* **omp:** replace `OmpACPAdapter` and `OmpACPAdapterConfig` with native
`OmpAdapter` and `OmpAdapterConfig` from `band.adapters.omp`. Use the `omp`
extra and OMP 18.8.9 or later; configure `model` and `reasoning_effort`
directly instead of ACP session configuration.

### Bug fixes

* **omp:** keep a Band turn active through native async yields and background
resumes until the matching prompt outcome and session settlement. Surface late
continuation errors and close the room process on timeout or cancellation,
preserving tool-only replies and the existing missing-reply verdict.

### Features

* **omp:** register canonical Band and custom tools through native RPC protocol
v2 with structured text/image results, typed external MCP server overlays, and
native select permission callbacks that reject unoffered choices.

* **omp:** install connection guidance in genuine system context, preserving user
prompt additions and restored sessions. Per-message room context carries
factual metadata instead of system-styled instructions; native lifecycle
state remains distinct from Band reply delivery.

* **runtime:** remove contradictory turn-ending guidance and move mention-routing
instructions out of participant roster updates.

## [5.1.0](https://github.com/band-ai/band-sdk-python/compare/band-sdk-v5.0.0...band-sdk-v5.1.0) (2026-10-13)


Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,8 @@ LangGraph supports the built-in Band platform tools, custom LangChain tools thro
| ------------ | ------------- | ------------------------------------ | --------------------------------------------- |
| A2A bridge | `a2a` | `A2AAdapter` | [examples](examples/a2a_bridge/) |
| A2A gateway | `a2a_gateway` | `A2AGatewayAdapter` | [examples](examples/a2a_gateway/) |
| ACP | `acp` | `ACPClientAdapter`, `CopilotACPAdapter`, `OmpACPAdapter`, `ACPServer`, `BandACPServerAdapter` | [examples](examples/acp/) |
| ACP | `acp` | `ACPClientAdapter`, `CopilotACPAdapter`, `ACPServer`, `BandACPServerAdapter` | [examples](examples/acp/) |
| OMP native RPC | `omp` | `OmpAdapter` | [example](examples/omp.py), [setup](docs/acp.md#omp-native-rpc) |
| Slack | `slack` | `SlackAdapter` | [examples](examples/slack/) |

> **Other languages:** The Band SDK is also available for [TypeScript](https://github.com/thenvoi/thenvoi-sdk-typescript).
Expand All @@ -497,7 +498,7 @@ Additional bridge extras exist for specialized deployments: `a2a_gateway_demo` s

Agents using the Band SDK can receive built-in tools for interacting with Band. **Chat tools are always enabled**, and cannot be disabled. Contact and memory tools are opt-in capabilities, configured via `capabilities=` on the adapters that support them, and are disabled unless you explicitly enable them.

The table below is the agent tool surface exposed to LLM adapters. Framework adapters in [Supported Adapters](#supported-adapters) support `Capability.CONTACTS` and `Capability.MEMORY`; the A2A adapters (`A2AAdapter`, `A2AGatewayAdapter`) and the ACP server adapter (`BandACPServerAdapter`) declare no capabilities, so `capabilities=` has nothing to enable there. `ACPClientAdapter` and the Copilot, Cursor and OMP backends built on it accept them.
The table below is the agent tool surface exposed to LLM adapters. Framework adapters in [Supported Adapters](#supported-adapters) support `Capability.CONTACTS` and `Capability.MEMORY`; the A2A adapters (`A2AAdapter`, `A2AGatewayAdapter`) and the ACP server adapter (`BandACPServerAdapter`) declare no capabilities, so `capabilities=` has nothing to enable there. `ACPClientAdapter`, its Copilot and Cursor backends, and the native `OmpAdapter` accept them.

| Category | Tool Names | What They Enable |
| ------------ | ---------- | ---------------- |
Expand Down
113 changes: 96 additions & 17 deletions docs/acp.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# ACP (Agent Client Protocol) Integration
# ACP and native OMP integrations

Facts about the ACP integration that span modules or that the code cannot say for
itself. The client side is `ACPClientAdapter` (Band room → room-owned ACP subprocess);
the server side is `ACPServer` + `BandACPServerAdapter`.
Facts about the ACP and native OMP integrations that span modules or that the
code cannot say for itself. The ACP client side is `ACPClientAdapter` (Band room →
room-owned ACP subprocess); the server side is `ACPServer` +
`BandACPServerAdapter`. OMP uses a separate [native RPC integration](#omp-native-rpc),
not ACP.

Client-side automatic idle release is unavailable pending provider-specific
safety qualification; see [idle room resource retention](idle-room-release.md).

## Configuration

`ACPClientAdapterConfig` holds the plain settings; `CursorACPAdapterConfig`,
`CopilotACPAdapterConfig` and `OmpACPAdapterConfig` extend it with backend defaults
and fields. Callables (`workspace_for_room`, `resolve_session_config`,
`resolve_permission`) are keyword-only constructor arguments.
`ACPClientAdapterConfig` holds the plain settings; `CursorACPAdapterConfig` and
`CopilotACPAdapterConfig` extend it with backend defaults and fields. Callables
(`workspace_for_room`, `resolve_session_config`, `resolve_permission`) are
keyword-only constructor arguments.

```python
from band.adapters import ACPClientAdapter, ACPClientAdapterConfig
Expand Down Expand Up @@ -169,10 +171,9 @@ assert slow.retention.transition_timeout_s == 120
overdue server close stays owned and a retry joins it. A stop is final: later
turns are refused until `on_started` (an `Agent.start()`) finishes it and serves
again.
- **Idle release stays off.** OMP, Cursor and Copilot report `ProviderActivity.UNKNOWN`,
so an idle release always answers BUSY. Native qualification is still owed: OMP
background jobs (including suppressed or acknowledged work), Cursor work beyond
subagent drain, and Copilot scheduled `/after` prompts. Band MCP calls and ACP
- **Idle release stays off.** Cursor and Copilot report `ProviderActivity.UNKNOWN`,
so an idle release always answers BUSY. Native qualification is still owed: Cursor
work beyond subagent drain, and Copilot scheduled `/after` prompts. Band MCP calls and ACP
permission or extension requests carry no prompt id, so a call arriving after its
turn is served with the room's current tools.

Expand All @@ -185,11 +186,6 @@ failure fails that room turn visibly instead of falling back.

## Backends

- **OMP:** `approval_mode="yolo"` bypasses OMP's native approval and gives the agent full
access to its host. It does not change Band tool registration or platform permissions.
`OmpACPAdapterConfig.model` is passed as OMP's `--model` flag; OMP does not read an
`OMP_MODEL` env variable. Set `api_key` with it and the adapter passes the key in the env
variable that model's provider needs.
- **Cursor:** question, plan and permission decisions default to `manual`, resolved by a
room participant with `/cursor <word> <token>`. Band's own tools, including
`additional_tools`, are approved once per call in every `approval_mode` during
Expand Down Expand Up @@ -217,6 +213,89 @@ failure fails that room turn visibly instead of falling back.
first prompt. The live `backends` lane pins the Cursor CLI and passes `E2E_CURSOR_API_KEY`
as `CURSOR_API_KEY` only to its baseline step; local runs may use a stored `agent login`.

## OMP native RPC

`OmpAdapter`, `OmpAdapterConfig` and `OmpPermissionRequest` live in
`band.adapters.omp` and are exported from `band.adapters`. Install `band-sdk[omp]`
and an OMP executable at version 18.8.9 or later; the executable fixture is pinned
to 18.8.9 and requires Bun 1.3.14 or later. The `omp` extra does not require the ACP
runtime. Native sessions negotiate RPC protocol v2 to preserve structured
text/image tool results.

- **Configuration.** Pass `OmpAdapterConfig` to the adapter. `model` selects
`provider/model`, and `reasoning_effort` selects the native thinking level,
including `"auto"`. Automatic effort stays automatic rather than being replaced
with the effective level chosen for one turn.
`api_key` requires `model` and reaches the child in that provider's environment
variable; OMP does not read an `OMP_MODEL` environment variable.
`workspace_for_room` and `resolve_permission` remain constructor callables.
OMP does not use the ACP `resolve_session_config` callback.
- **Tools and replies.** Canonical Band tools and `additional_tools` are
registered through native `set_host_tools`; results retain text/image content.
Room replies require `band_send_message` or an `ask` (below), and deliberate
declines use `band_no_reply`. Native assistant text is optional thought telemetry, not a
room reply. `SimpleAdapter.run_judged_turn` remains the sole turn-judging owner:
native-only output and failed tools followed by narration still reach the
missing-reply verdict.
- **Questions go to the room.** The adapter opts each session into OMP's
structured `ask` dialog (`set_ask_dialog`). An `ask` call posts its questions,
options and recommended choice to the room, mentioning the requester; the post
counts as the turn's reply. Once the post lands, the tool returns without
waiting for the answer and tells the model it arrives as a later message; that
reply resumes the same native session. Band processes a room's messages one at
a time, so a turn cannot wait for the answer, and OMP aborts the whole turn on
a cancelled `ask`. When the post fails, the model is told so instead and the
turn still owes a reply; a room the platform stopped ends the turn. Other UI
requests keep the `resolve_permission` behavior.
- **Instructions and attribution.** A private always-apply native rule places Band's
stable communication and completion guidance in OMP's genuine system prompt,
preserving native instructions, user prompt additions and append-file discovery.
A startup command verifies the rule is present before inference, even without
external MCP servers. Disabling rules or using a system-prompt template that
omits them fails startup rather than silently dropping Band's connection policy.
The rule survives restored sessions and prompt rebuilds during background work.
Per-message platform context contains
room, requester, roster and contact facts as JSON, not `[System]`-styled commands.
Replayed history is identified as previously handled data; it is not a new
participant handoff. Native lifecycle fields describe runtime progress, while the
Band turn ledger separately determines whether a reply was delivered or declined.
- **Background settlement.** Prompt acceptance, an initial `prompt_result`, and
`agent_end` with async work remaining are not completion. The reader stays
attached through automatic background continuations and waits for both the
matching prompt outcome and actual native session settlement
(`session_settled` or `get_state.isSettled`). An error in a resumed assistant
message fails the same Band turn. This is a native lifecycle barrier, not an
instruction-only mitigation, and it does not synthesize a reply or decline.
`turn_timeout_s` covers the whole lifecycle. Timeout, errors and cancellation
close that room's process and terminate its captured descendants before returning;
a subsequent message starts a fresh process rather than inheriting unfinished work.
- **Isolation and state.** Each room owns a native process, persistent session
and workspace. OMP receives the workspace via `--cwd`; the subprocess itself
starts without a supplied cwd. Session state carries `omp_session_id`,
`omp_session_file` and `omp_room_id`. A workspace is not an OS sandbox.
- **External MCP servers.** `mcp_servers` is a name-to-`OmpMcpServer` map; import
that typed model from `band.integrations.omp`. Stdio entries use `command`,
`args`, `env` and optional `cwd`; remote entries use `url`, `headers` and optional
`type` (`http` or `sse`). Relative stdio `cwd` resolves from the room workspace;
relative path-like commands resolve from that server cwd, or the room workspace
when cwd is omitted, before plugin projection. The adapter writes an owner-only temporary
`band-sdk/.mcp.json` plugin overlay and passes it with `--plugin-dir`. The overlay
stays alive until the child closes, then is removed. User profiles and native
credentials are not rewritten.
The SDK requires native connection and tool-mount receipts for every configured
server before inference. It uses the RPC UI event mode and a private startup
setting to enable those receipts without rewriting user settings. Missing or
failed receipts fail startup rather than allowing a model to answer without its
required tools. Native connection and tool failures remain errors, not successful
Band replies. The enclosing turn timeout still bounds startup.
- **Permissions.** `approval_mode="yolo"` bypasses native approval checks without
changing Band platform permissions. Otherwise `resolve_permission` receives
`OmpPermissionRequest(room_id, session_id, title, options)` and returns an
offered option or `None`. Unknown or unoffered choices cancel the request.
Without a resolver, only the exact native `("Approve", "Deny")` select is
approved. Unsupported interactive forms are cancelled; this is not approval
for arbitrary UI requests.

## Server prompt outcomes

`ACPServer.prompt` settles on the first terminal outcome for its room:
Expand Down
14 changes: 14 additions & 0 deletions docs/ci-cd-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ the workflow files cannot say for themselves.
(`ci.yml`, `pr-title.yml`, `release-gate.yml` and `python-core-coverage.yml` have one).
Without it the queue waits forever for a check that never starts.

## Private Core coverage

- `python-core-coverage.yml` checks out the private Core repository with
`CORE_SDK_READ_KEY`. Fork pull requests and Dependabot cannot receive that secret,
so this job skips those contexts rather than attempting an unauthenticated checkout.
- The ordinary CI test matrix still runs for fork pull requests. Core coverage runs
for trusted pull requests, merge groups, scheduled runs, and manual dispatches.
- To validate a fork revision before merge, a maintainer can publish its reviewed
commit to a temporary upstream branch and run
`gh workflow run python-core-coverage.yml --repo band-ai/band-sdk-python --ref <branch>`.
Check that the run's head SHA matches the reviewed commit, wait for coverage and
reporting to finish, then remove only that temporary branch. The manual workflow
also posts its normal coverage digest; no secrets should be exposed to fork code.

## PyPI publish

- **Trusted publishing binds to the top-level filename `band-publish.yml`** (with environment
Expand Down
Loading
Loading