Skip to main content

airframe

PyPI version Python versions License: MIT CI

One protocol, every agent SDK. Vendor-neutral runtime for Python AI agents — write once against a small AgentRuntime protocol and run on AWS Bedrock, Claude Code, GitHub Copilot, the OpenCode HTTP agent server, OpenCode Go, OpenCode Zen, or OpenRouter by changing a single config value.

Quickstart

pip install airframe-agents[claude]   # or [bedrock] / [copilot] / [openai-compat] / [all]
from airframe import runtime_for, ProviderModel
from pydantic import BaseModel

class Brief(BaseModel):
    summary: str
    risks: list[str]

# Provider ID comes from config — YAML, env, CLI flag, whatever.
provider_id = "claude"  # or bedrock / github-copilot / opencode / opencode-go / opencode-zen / openrouter

cls = runtime_for(provider_id)       # discovery lookup by ID
runtime = cls()                      # auth resolves from env / credential files
result = await runtime.execute(
    "Brief me on the project structure.",
    schema=Brief,
    model=ProviderModel(provider_id, "claude-haiku-4-5"),
)
print(result.structured)     # {"summary": "...", "risks": [...]}
print(result.cost.cost_usd)  # 0.0042
await runtime.close()

The same agent code now serves any installed adapter — swap provider_id (and model) in config, no import or instantiation changes. Add a new vendor to your project's YAML and ship.

Direct imports still work when you only ever need one adapter:

from airframe import ClaudeCodeRuntime
runtime = ClaudeCodeRuntime()

Use list_providers() to enumerate installed adapters at startup (handy for validating YAML config):

from airframe import list_providers
list_providers()  # ["claude", "github-copilot"]  — whichever extras are installed

The PyPI distribution name is airframe-agents. The import name is airframe.

Supported providers

Adapter PROVIDER_ID Vendor SDK Auth
BedrockRuntime bedrock aioboto3 boto3 chain (env / AWS_PROFILE / IAM role) + AWS_REGION
ClaudeCodeRuntime claude claude-agent-sdk Claude Max OAuth → ~/.claude/credentials.json → ANTHROPIC_API_KEY
CopilotRuntime github-copilot github-copilot-sdk GITHUB_TOKEN → gh auth
OpenCodeGoRuntime opencode-go OpenAI compatible OPENCODE_API_KEY → opencode auth.json::opencode-go.key
OpenCodeServerRuntime opencode opencode-ai HTTP Basic (loopback unauthenticated; OPENCODE_SERVER_PASSWORD for remote)
OpenCodeZenRuntime opencode-zen OpenAI compatible OPENCODE_API_KEY → opencode auth.json::opencode.key
OpenRouterRuntime openrouter OpenAI compatible OPENROUTER_API_KEY

The OpenAI-compatible family (OpenCodeZenRuntime per-token, OpenCodeGoRuntime subscription, OpenRouterRuntime multi-vendor gateway today; Together / Groq / Fireworks as future siblings) shares the OpenAICompatibleRuntime base — subclasses are ~30 lines. See docs/adapters/third-party.md.

BedrockRuntime is the enterprise / managed-cloud path — AWS-billed access to a multi-vendor catalog (Anthropic, Meta, Mistral, Cohere, Amazon Nova) behind IAM-rooted auth and region pinning. Distinct from the OpenAI-compatible family because Bedrock speaks Converse, not Chat Completions.

Each adapter has one canonical provider ID. "anthropic" is reserved for a future direct-API AnthropicRuntime; "openai" for a future OpenAIRuntime. Current adapters cover the subscription paths (Claude Max, Copilot, ChatGPT Plus, opencode-go), the per-token gateways (OpenCode Zen, OpenRouter), the AWS-billed managed path (Bedrock), and the self-hosted agent server path (OpenCode Server — wraps opencode serve and fronts whichever upstream providers opencode auth login has configured, including ChatGPT-OAuth subscriptions).

ClaudeCodeRuntime is the only adapter that accepts Claude bindings. CopilotRuntime declines them — Claude served via Copilot Chat Completions emits markdown-fenced JSON instead of honouring tool calls, so it can't satisfy the structured-output contract.

Capability matrix

Current snapshot (run uv run python examples/probe_supports.py for the live version):

Feature Bedrock Claude Copilot OpenAI-compat OpenCode
STRUCTURED_OUTPUT_JSON_SCHEMA ✓ ✓ ✓ ✓ ✗ (SDK gap)
STREAMING / CANCEL ✓ ✓ ✓ ✓ ✓
SESSION_RESUME ✗ ✓ ✓ ✗ ✓
REASONING_EFFORT ✓ (Anthropic) ✓ ✓ ✓ ✓ (per-upstream)
REASONING_BUDGET_TOKENS ✓ (Anthropic) ✓ ✗ ✗ ✓ (Anthropic upstream)
VISION_INPUT ✓ ✓ ✓ ✓ ✓
FILE_INPUT ✓ (Anthropic) ✓ ✓ ✗ ✓
TOOLS_FUNCTION ✓ ✓ ✓ ✓ ✗ (SDK gap)
TOOLS_MCP_STDIO / _HTTP ✗ ✓ ✓ ✗ ✗ (SDK gap)
TOOLS_MCP_SSE ✗ ✓ ✗ ✗ ✗ (SDK gap)
PERMISSION_CALLBACK ✓ ✓ ✓ ✗ ✗ (SDK gap)
LIFECYCLE_HOOKS ✓ (6 kinds) ✓ (8 kinds) ✓ (7 kinds) ✓ (6 kinds) ✓ (6 kinds)
BUDGET_USD_CAP ✓ ✓ ✓ ✓ ✓
BUDGET_TURN_CAP ✓ ✓ ✗ ✓ ✓
RATE_LIMIT_TELEMETRY ✗ ✓ ✗ ✓ ✗
REASONING_OUTPUT ✗ ✓ ✗ ✓ ✗
REQUEST_METADATA ✗ † ✓ ✗ † ✓ ✗ †
COUNT_TOKENS ✗ ✓ ✗ ✓ ✗
PROMPT_CACHE_CONTROL ✗ † ✗ † ✗ † ✓ ✗ †
SLASH_COMMANDS ✓ ✓ ✓ ✓ ✓

† Soft contract — adapters returning False accept the kwarg structurally and silently drop it rather than raising. The call's correctness doesn't depend on the tag/cache key reaching the vendor; consumers who care branch on supports() first.

Capability flags are statically declared per adapter. Check runtime.supports(Feature.X) before invoking a feature; declined capabilities raise UnsupportedFeatureError with a feature= attribute so the call fails fast.

Full per-feature semantics in docs/capabilities.md; per-adapter quirks under docs/adapters/.

Why?

Each vendor ships a Python SDK that does something subtly different: the Claude Agent SDK exposes a subprocess + JSON-RPC interface; GitHub's Copilot SDK exposes a session + tool registration model; AWS Bedrock's aioboto3 client fronts a multi-vendor model catalog behind the Converse envelope and IAM auth; sst/opencode's opencode-ai SDK fronts a model- agnostic HTTP agent server (opencode serve) routing through any upstream opencode auth login has configured; the OpenAI-compatible gateways (OpenCode Zen, OpenCode Go, OpenRouter) speak Chat Completions HTTP. Each has its own auth chain, error taxonomy, cost-reporting shape, structured-output mechanism, and models-endpoint shape. Airframe collapses those differences behind one execute / session / reset / close / validate_binding / list_models / supports / unwrap interface, classifies every vendor's failures into a single hierarchy, and produces a single CostRecord / ModelInfo shape regardless of the vendor.

The protocol is intentionally narrow. The eight methods are the contract; everything else (auth chains, session caching, tool-call forcing, JSON-schema mode, envelope unwrapping, per-model metadata joining) lives inside each adapter, where vendor-specific behaviour belongs.

Anything above the protocol — retry policy, fallback across vendors, conversation memory, multi-agent orchestration — is left to the consumer. Airframe is the adapter layer; the application composes its own behaviour on top.

The shape — one narrow protocol plus pluggable vendor adapters, discovered by ID — is borrowed from JDBC, with the same goal: let the application code stay vendor-agnostic while each adapter absorbs its vendor's quirks.

Install

pip install airframe-agents[bedrock]        # BedrockRuntime
pip install airframe-agents[claude]         # ClaudeCodeRuntime
pip install airframe-agents[copilot]        # CopilotRuntime
pip install airframe-agents[opencode]       # OpenCodeServerRuntime (local opencode serve)
pip install airframe-agents[openai-compat]  # OpenCodeGoRuntime + OpenCodeZenRuntime + OpenRouterRuntime (+ future siblings)
pip install airframe-agents[all]            # Everything
pip install airframe-agents[testing]        # Conformance contract suite (pytest)

list_providers() filters by which extras you installed: airframe-agents[copilot] makes list_providers() return ["github-copilot"]. Pass installed_only=False to see every built-in provider for documentation UIs.

Command line & CI

Installing any extra also puts an airframe CLI on your PATH — a thin shell over discovery and the AgentRuntime protocol, so one prompt runs against any installed adapter:

airframe run --provider claude --prompt "Summarise this changelog" --format text
airframe run --provider opencode-zen --prompt-file task.md --format json
echo "explain this diff" | airframe run --provider openrouter   # prompt on stdin
airframe providers                                              # list installed adapters

--format json emits the full envelope (text / structured / finish / cost); --output-file writes the reply to a file; --system, --model, and --timeout map to the matching execute() kwargs. Exit codes distinguish auth (4), runtime (5), and usage (2) failures for scripting.

The same CLI is wrapped by a reusable GitHub Action so a workflow can run an agent step against any provider — pick the provider per job:

- uses: get2knowio/airframe/action@v1
  id: review
  with:
    provider: claude
    api-key: ${{ secrets.ANTHROPIC_API_KEY }}
    prompt-file: review-prompt.md
# result is at ${{ steps.review.outputs.result }}

See action/README.md for the full input/output reference and the issue-triage + PR-review example.

Sessions, streaming, and the new kwargs

runtime.execute(...) is convenient single-turn sugar. The full surface lives on runtime.session(...):

from airframe import (
    ClaudeCodeRuntime, FunctionTool, McpServerRef,
    PermissionCallback, PermissionDecision, PermissionRequest,
    HookEvent, ClaudeOptions, TextDelta, TurnComplete,
)
from pydantic import BaseModel

class AddArgs(BaseModel):
    a: float
    b: float

async def add(args: AddArgs) -> float:
    return args.a + args.b

class ApproveAll(PermissionCallback):
    async def handle(self, req: PermissionRequest) -> PermissionDecision:
        return "allow"

def log_event(e: HookEvent) -> None:
    print(f"[{e.kind}] {e.payload}")

runtime = ClaudeCodeRuntime()
sess = runtime.session(
    system="You are a careful math assistant.",
    tools=[FunctionTool(name="add", description="Add two numbers.",
                        params=AddArgs, handler=add)],
    mcp_servers=[McpServerRef(name="docs", transport="http",
                              url="https://mcp.example.com",
                              auth_token="...")],
    on_permission=ApproveAll(),
    on_event=log_event,
    provider_options=ClaudeOptions(strict_mcp_config=True),
)
try:
    async for event in sess.stream("What is 17 + 25?",
                                    max_turns=10, max_budget_usd=0.05):
        if isinstance(event, TextDelta):
            print(event.text, end="", flush=True)
        elif isinstance(event, TurnComplete):
            print(f"\nfinal cost: ${event.result.cost.cost_usd}")
finally:
    await sess.close()

session.stream() yields a discriminated union of five event variants: TextDelta, ReasoningDelta, ToolCallStart, ToolCallResult, TurnComplete. The variant set is shape-locked.

Per-kwarg semantics live in docs/capabilities.md; per-adapter quirks in each docs/adapters/ page.

Cross-cutting portable surfaces

A handful of cross-vendor knobs sit alongside the streaming + tools + permission machinery:

from airframe import (
    ClaudeCodeRuntime,
    RequestMetadata, CacheConfig, SlashCommandsConfig,
    Feature,
)

runtime = ClaudeCodeRuntime()
sess = runtime.session(
    metadata=RequestMetadata(user_id="acct_42",
                              tags={"tenant": "acme"}),
    cache=CacheConfig(key="agent-foo:session-7", retention="long"),
    slash_commands=SlashCommandsConfig(),
)

# Pre-flight token count — answer "will this fit?" before paying.
count = await runtime.count_tokens("very long prompt...")
if count > 100_000:
    raise ValueError("over budget")

result = await sess.execute("Summarise this codebase.")

# Quota telemetry on the way back.
if result.rate_limit is not None:
    for w in result.rate_limit.windows:
        print(f"{w.name}: {w.remaining}/{w.limit}")

# Reasoning trace, when the model emitted one.
if result.reasoning is not None:
    print("model thought:", result.reasoning[:200], "...")

# Discover user-authored /commands from .claude/commands/ etc.
for cmd in await sess.list_slash_commands():
    print(f"/{cmd.name} — {cmd.description}")

Each surface is gated by its own Feature flag — check runtime.supports(Feature.X) if your code's correctness depends on the vendor honouring the kwarg. The metadata= and cache= kwargs follow a soft contract: non-supporting adapters silently drop them (call still succeeds, just without the attribution tag or cache speed-up). count_tokens(), reasoning, and rate_limit follow the standard hard-contract: declined adapters either raise UnsupportedFeatureError or leave the field None. Full per-feature semantics in docs/capabilities.md.

Errors

Adapters classify vendor failures into a small hierarchy:

Error Meaning
RuntimeAuthError Credentials bad / expired / missing
RuntimeModelNotFoundError Server doesn't serve that model on this binding
RuntimeTransientError 5xx, rate limit, brief outage — recoverable
RuntimeStructuredOutputError Transport OK; payload didn't match schema
RuntimeBudgetExceededError max_turns= / max_budget_usd= cap tripped
UnsupportedFeatureError Capability declined (carries feature= attr)

Full list and the rest of the hierarchy in docs/reference.md#errors.

Escape hatch: unwrap()

When the portable surface doesn't expose a vendor-specific knob, reach the native SDK object via unwrap():

from claude_agent_sdk import ClaudeSDKClient
sess = runtime.session()
await sess.execute("hi")
client: ClaudeSDKClient = sess.unwrap(ClaudeSDKClient)
await client.interrupt()

Each adapter declares the native types it accepts; unsupported types raise TypeError. Runtime-level types via runtime.unwrap(...); session-level vendor objects via session.unwrap(...).

Live probes

examples/probe_*.py exercise each adapter end-to-end against a real CLI / HTTP endpoint. They're runnable demos, not part of make test. Auth issues surface as classified Runtime*Error.

uv run python examples/probe_supports.py        # capability matrix
uv run python examples/probe_streaming.py       # stream() against any installed adapter
uv run python examples/probe_tools.py           # FunctionTool round-trip
uv run python examples/probe_mcp.py             # external MCP server
uv run python examples/probe_permission.py      # PermissionCallback
uv run python examples/probe_hooks.py           # HookEvent observation
uv run python examples/probe_budget.py          # max_turns / max_budget_usd

Full list with one-line descriptions in docs/cookbook.md.

Documentation

Development

uv sync --all-extras --group dev
make test          # full suite (incl. integration tests, which self-skip without creds)
make test-fast     # exclude `integration` marker
make lint          # ruff
make typecheck     # mypy
make ci            # lint + format + typecheck + test

Integration tests run automatically when credentials for an adapter are configured (see auth.md).

License

MIT — see LICENSE.

Metadata

Release files for airframe-agents 0.9.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for airframe-agents 0.9.2
File Size Uploaded Provenance Actions
airframe_agents-0.9.2.tar.gz 605.4 kB With provenance Details for airframe_agents-0.9.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for airframe-agents 0.9.2
File Interpreter ABI Platform Actions
airframe_agents-0.9.2-py3-none-any.whl Python 3 none any Details for airframe_agents-0.9.2-py3-none-any.whl

Total release size: 839.6 kB

Release files / airframe_agents-0.9.2.tar.gz

Download URL airframe_agents-0.9.2.tar.gz
Size 605.4 kB
Tags Source
SHA-256 checksum
How to use checksums
da312a73889cffe3ead567e725c637453d84eb23a1b473dd98bf2d093a71c97a
BLAKE2b-256 checksum
How to use checksums
0d90b66ad662a3baaf303bff5f4d36d3dab376332fbe4d7fd0006b952acde1b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / airframe_agents-0.9.2-py3-none-any.whl

Download URL airframe_agents-0.9.2-py3-none-any.whl
Size 234.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
00cde68bb94142fe5eac9af3d8c44d098a3c9b3730ed358df7d38485b814088f
BLAKE2b-256 checksum
How to use checksums
4e65bfee7037472225898b4e9eb8faf9e7686cedb7bb878d966d788af6dc5aa3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Security for airframe-agents 0.9.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for airframe_agents-0.9.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page