AnyFrame – Sandboxes for Your AI Agents
AnyFrame is the official Python SDK for the AnyFrame control plane, enabling developers to point an agent at a repo and get a sandbox running Claude Code. It provides comprehensive APIs for managing agents, sessions, skills, MCPs, connectors, builds, previews, and more, with both sync and async support.
Notifications You must be signed in to change notification settings
Fork 0
Star 3
BranchesTags
Open more actions menu
Folders and files
NameName
Last commit message
Last commit date
Latest commit
History
66 Commits
66 Commits
.github/workflows
.github/workflows
docs
docs
src/anyframe
src/anyframe
tests
tests
.env.example
.env.example
.gitignore
.gitignore
.pre-commit-config.yaml
.pre-commit-config.yaml
.python-version
.python-version
CHANGELOG.md
CHANGELOG.md
LICENSE
LICENSE
README.md
README.md
pyproject.toml
pyproject.toml
uv.lock
uv.lock
Repository files navigation
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.
Demo
AnyFrame.mp4
Install
uv add anyframe
Get an API key
The SDK authenticates with a personal API token (prefix afm_).
Sign in at anyfrm.com.
Open Dashboard → Settings → API keys and click Create key.
Copy the afm_... token (shown once - store it now).
Drop it into a .env next to your script, or export it:
ANYFRAME_API_KEY=afm_...
Already authed in another script? af.tokens.create(name="ci-bot") mints a new one programmatically.
Working with private repos? Also set a GitHub PAT once - in the dashboard's Credentials page, or via af.credentials.set_github("ghp_..."). See Credentials.
Quickstart
Take over a web session
Already have a session running in the web UI? Attach to it and take a turn:
import anyframe
af = anyframe.AnyFrame() # reads ANYFRAME_API_KEY + ANYFRAME_BASE_URL
Grab the session id from the web URL, or list and pick one:
session = next(s for s in af.sessions.list() if s.status == "running")
af.sessions.message(session.id, {"text": "summarize what you've done so far"})
for event in af.sessions.events(session.id): # live SSE; Ctrl-C when done print(event.event, event.json())
message and events proxy verbatim to the in-sandbox chat - the web UI and the SDK are two clients on the same channel.
Build a fresh agent from scratch
import anyframe
af = anyframe.AnyFrame()
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
See Get an API key for where the afm_... token comes from.
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", runtime="claude", # or "codex" env_vars={"DATABASE_URL": "..."}, # injected into every session ) 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="inline", 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)
Catalog
The control plane ships with a curated catalog (Linear, Sentry, Google, …). Install by slug instead of pasting URLs.
catalog = af.connectors.list_catalog() # ConnectorCatalogItem[] af.connectors.install_catalog_oauth("linear") # → authorize URL (DCR or pre-registered) af.connectors.install_catalog_bearer("sentry", token="...")
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
Setup sessions + save-as-base
Setup sessions are user-driven sandboxes you use to seed an agent's filesystem (clone, install, warm caches), then promote to that agent's warmup image. Future normal sessions then hydrate from the promoted snapshot.
session = af.sessions.create(agent_id=agent.id, is_setup_session=True) af.sessions.wait_until_running(session.id)
... do interactive setup ...
result = af.sessions.save_as_base(session.id) # SaveAsBaseResult print(result.warmup_image_id)
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": "..."})
Previews (in-sandbox dev servers)
Launch dev servers inside the sandbox and tunnel their ports out. Multiple previews can run per session - name them or address them by port.
af.sessions.previews_start(session.id, cmd="bun dev", port=3000, name="web") af.sessions.previews_status(session.id, name="web") # PreviewActionResult af.sessions.previews_list(session.id) # Preview[] af.sessions.previews_logs(session.id, name="web", tail=200) af.sessions.previews_stop(session.id, name="web")
Atomic batch - restarts at most once when allocating new ports
af.sessions.previews_batch_start(session.id, [ anyframe.PreviewSpec(cmd="bun dev", port=3000, name="web"), anyframe.PreviewSpec(cmd="bun api", port=4000, name="api"), ])
Migrated from the old serve_* methods. The control plane replaced /sessions/{id}/serve/* with a single POST /sessions/{id}/previews action body. The SDK methods above target that endpoint; Session.previews (a list of Preview) replaces serve_status / serve_port / serve_url.
Attention rail
A curated, newest-first list of things the operator should act on - pending permission prompts, idle running sessions, and recently-paused sessions.
for item in af.attention.list(limit=20): print(item.kind, item.agent_name, item.preview)
Each row is one of AttentionPendingItem, AttentionIdleItem, or AttentionPausedItem. Discriminate on item.kind.
Credentials
The control plane needs a runtime credential - Claude OAuth (default Claude runtime) or an OpenAI Codex token (Codex runtime) - plus a GitHub PAT for private repos. It only ever shows you redacted views.
af.credentials.get() # set flag + last4 for claude / codex / github af.credentials.set_claude("sk-...") af.credentials.set_codex("sk-...") af.credentials.set_github("ghp_...") af.credentials.clear_claude() af.credentials.clear_codex() 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.
Docs: docs.anyfrm.com · Found a bug or have a question? Join us on Discord.
About
The official Python SDK for the AnyFrame
pypi.org/project/anyframe/
Resources
Readme
License
MIT license
Uh oh!
There was an error while loading. Please reload this page.
Activity
Custom properties
Stars
3 stars
Watchers
0 watching
Forks
0 forks
Report repository
Releases 2
v1.1.0
Latest
May 17, 2026
+ 1 release
Packages 0
Uh oh!
There was an error while loading. Please reload this page.
Contributors
Uh oh!
There was an error while loading. Please reload this page.
Languages
Python 100.0%