Official Python SDK for the AnyFrame control plane.
Project description
anyframe
The official Python SDK for the AnyFrame control plane — point an agent at a repo, get a sandbox running Claude Code inside.
┌──────────────────────────────┐
│ Agent (repo, system prompt)│
│ ├── Skills │
│ ├── MCPs │
│ └── Connector toggles │
┌──────────┐ anyframe SDK └─────────────┬────────────────┘
│ you │ ───────────────────▶ │ build
│ (python) │ ▼
└──────────┘ ┌──────────────────────────────────────────┐
│ Session (sandbox · chat · serve) │
└──────────────────────────────────────────┘
User-level Connectors plug MCP servers (Linear, Sentry, …) in once and toggle them per-agent. Skills + MCPs ride with the agent into every session it boots.
Install
uv add anyframe
Quickstart
import anyframe
af = anyframe.AnyFrame() # reads ANYFRAME_API_KEY + ANYFRAME_BASE_URL
agent = af.agents.create(name="demo", repo_url="tinyhq/box", install_cmd="bun install")
af.agents.build(agent.id)
af.agents.wait_for_build(agent.id)
session = af.sessions.create(agent_id=agent.id)
session = af.sessions.wait_until_running(session.id)
print(session.sandbox_url)
Authentication
.env in your project root, or shell environment:
ANYFRAME_API_KEY=afm_...
ANYFRAME_BASE_URL=https://api.anyfrm.com # optional
ANYFRAME_LOG_LEVEL=INFO # set DEBUG for request tracing
Mint a key in the dashboard, or from a logged-in session with af.tokens.create(name=...).
Async
Every method exists on AsyncAnyFrame with the same signature, just await-ed.
import asyncio, anyframe
async def main():
async with anyframe.AsyncAnyFrame() as af:
me = await af.me()
agents = await af.agents.list()
asyncio.run(main())
Agents
Agents are the unit of "what runs in the sandbox" — a repo, a system prompt, a permissions config.
af.agents.list()
af.agents.create(name="demo", repo_url="owner/name", install_cmd="bun install")
af.agents.get(agent_id) # AgentDetail: includes skills, mcps, connectors, image
af.agents.update(agent_id, name="renamed")
af.agents.delete(agent_id)
Skills
Skills are bundles of instructions the agent loads at boot (think: "deploy this app", "review this PR").
af.agents.skills.list(agent_id)
af.agents.skills.create(agent_id, name="deploy", source="builtin", content={...})
af.agents.skills.update(agent_id, skill_id, enabled=False)
af.agents.skills.delete(agent_id, skill_id)
MCPs
MCPs configured inline on the agent — for one-off MCP servers that aren't worth setting up as a reusable connector.
af.agents.mcps.list(agent_id)
af.agents.mcps.create(agent_id, name="git", transport="http", config={"url": "..."})
af.agents.mcps.update(agent_id, mcp_id, enabled=False)
af.agents.mcps.delete(agent_id, mcp_id)
Connectors
User-level MCP connectors — configure once, then opt in per-agent via the connector-toggle API below.
af.connectors.list()
discovery = af.connectors.discover("https://mcp.linear.app/sse")
authorize = af.connectors.create_oauth(mcp_url=discovery.mcp_url, display_name="Linear")
# open authorize.authorize_url in a browser; callback completes server-side
af.connectors.create_bearer(mcp_url="...", display_name="...", token="...")
af.connectors.reauthorize(connector_id)
af.connectors.delete(connector_id)
Per-agent toggle (controls which connectors apply to one agent):
af.agents.connectors.list(agent_id)
af.agents.connectors.set(agent_id, connector_id, enabled=True)
Builds
Builds bake an agent's repo + dependencies into a cached sandbox image — required before a session can boot it.
af.agents.build(agent_id, force=False) # queue a build
af.agents.build_status(agent_id) # current state + cached image id
af.agents.builds(agent_id, limit=20) # history
af.agents.build_log_url(agent_id, build_id) # signed R2 URL for the archived log
for event in af.agents.stream_build(agent_id, build_id):
print(event.event, event.json()) # live SSE log frames
af.agents.wait_for_build(agent_id) # blocks until succeeded / fails
Sessions
A session is one live sandbox. Lifecycle is booting → running → snapshotting → terminated; resume brings a terminated session back from its snapshot.
session = af.sessions.create(agent_id=agent.id, idle_timeout_s=300)
af.sessions.wait_until_running(session.id)
af.sessions.list()
af.sessions.get(session.id)
af.sessions.snapshots(session.id)
af.sessions.terminate(session.id)
af.sessions.resume(session.id)
af.sessions.delete(session.id) # hard delete; requires terminated
Chat
Talk to the running agent. message and respond proxy verbatim to the in-sandbox chat server; events is the live SSE stream; transcript reads persisted history.
af.sessions.message(session.id, {"text": "deploy main to staging"})
for event in af.sessions.events(session.id, last_event_id=None):
print(event.id, event.event, event.json())
af.sessions.transcript(session.id, since=0, limit=1000)
af.sessions.respond(session.id, {"decision": "approve", "tool_use_id": "..."})
Serve (preview server)
Launch a dev server inside the sandbox and tunnel its port out — useful for live previews of the thing the agent is building.
af.sessions.serve_start(session.id, cmd="bun dev", port=3000)
af.sessions.serve_status(session.id) # serve_url is set when up
af.sessions.serve_logs(session.id, tail=200)
af.sessions.serve_stop(session.id)
Credentials
The control plane needs your Claude OAuth token (always) and a GitHub PAT (for private repos). It only ever shows you redacted views.
af.credentials.get() # set flag + last4
af.credentials.set_claude("sk-...")
af.credentials.set_github("ghp_...")
af.credentials.clear_claude()
af.credentials.clear_github()
Tokens
Manage the API keys this SDK uses. create returns the raw token exactly once — store it now.
af.tokens.list()
created = af.tokens.create(name="ci-bot")
print(created.token) # afm_... one-time
af.tokens.revoke(created.id)
Errors
All errors derive from anyframe.AnyFrameError, so one except catches everything.
anyframe.AnyFrameError # base
├── anyframe.APIError # any non-2xx (status_code, message)
├── anyframe.AuthError # 401 — bad / missing API key
├── anyframe.NotFoundError # 404
├── anyframe.ConflictError # 409 — e.g. delete on a running session
├── anyframe.ValidationError # 400/422 (carries field-level details)
├── anyframe.RateLimitError # 429 (exposes retry_after)
└── anyframe.ServerError # 5xx
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file anyframe-1.0.0.tar.gz.
File metadata
- Download URL: anyframe-1.0.0.tar.gz
- Upload date:
- Size: 24.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb564ec4443ee70391ca47da9c1d9280fb590724685648fbdedac6ecb4106ca9
|
|
| MD5 |
530f437f1b22498f61529c0626bb4f75
|
|
| BLAKE2b-256 |
d55645a3c3697a1f32d292a7c774bd9bb270617f51191a28f9a53ccda4a0e6b1
|
File details
Details for the file anyframe-1.0.0-py3-none-any.whl.
File metadata
- Download URL: anyframe-1.0.0-py3-none-any.whl
- Upload date:
- Size: 29.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6f918530030b56e0708ea9dd9e19c3ded42eedaa0d41e753fcc6b883c807477
|
|
| MD5 |
f7979e9cc4fb8aeec0eddf6b57ca1f4d
|
|
| BLAKE2b-256 |
13aa3f46f26c5f94ebf37efda7b6eae4add3dd4f0e967cc1a306b179425e8273
|