{"id":51244694,"url":"https://github.com/agentruntimecontrolprotocol/python-sdk","last_synced_at":"2026-06-29T03:03:32.973Z","repository":{"id":356978809,"uuid":"1234794635","full_name":"agentruntimecontrolprotocol/python-sdk","owner":"agentruntimecontrolprotocol","description":"Python reference SDK for ARCP (Agent Runtime Control Protocol).","archived":false,"fork":false,"pushed_at":"2026-06-11T21:56:27.000Z","size":1606,"stargazers_count":1,"open_issues_count":29,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T23:19:07.933Z","etag":null,"topics":["agent-protocol","agent-runtime-control-protocol","agents","ai-agents","arcp","durable-execution","llm","mcp","python","sdk","streaming"],"latest_commit_sha":null,"homepage":"https://github.com/agentruntimecontrolprotocol/spec","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agentruntimecontrolprotocol.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-10T16:45:29.000Z","updated_at":"2026-06-11T21:54:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/agentruntimecontrolprotocol/python-sdk","commit_stats":null,"previous_names":["agentruntimecontrolprotocol/python-sdk"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/agentruntimecontrolprotocol/python-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fpython-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fpython-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fpython-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fpython-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agentruntimecontrolprotocol","download_url":"https://codeload.github.com/agentruntimecontrolprotocol/python-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fpython-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34911136,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-protocol","agent-runtime-control-protocol","agents","ai-agents","arcp","durable-execution","llm","mcp","python","sdk","streaming"],"created_at":"2026-06-29T03:03:32.487Z","updated_at":"2026-06-29T03:03:32.960Z","avatar_url":"https://github.com/agentruntimecontrolprotocol.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch3 align=\"center\"\u003eARCP Python SDK\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003ePython SDK for the Agent Runtime Control Protocol (ARCP) — submit, observe, and control long-running agent jobs from Python.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pypi.org/project/agentruntimecontrolprotocol/\"\u003e\u003cimg alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/agentruntimecontrolprotocol.svg?cacheSeconds=60\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://pypi.org/project/agentruntimecontrolprotocol/\"\u003e\u003cimg alt=\"Python versions\" src=\"https://img.shields.io/pypi/pyversions/agentruntimecontrolprotocol.svg?cacheSeconds=60\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/agentruntimecontrolprotocol/python-sdk/actions/workflows/test.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/agentruntimecontrolprotocol/python-sdk/actions/workflows/test.yml/badge.svg?branch=main\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/agentruntimecontrolprotocol/python-sdk\"\u003e\u003cimg alt=\"codecov\" src=\"https://codecov.io/gh/agentruntimecontrolprotocol/python-sdk/graph/badge.svg?token=RHW2K4B7C8\u0026cacheSeconds=60\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md\"\u003e\u003cimg alt=\"ARCP\" src=\"https://img.shields.io/badge/ARCP-v1.1%20draft-blue?cacheSeconds=60\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/badge/license-Apache--2.0-lightgrey?cacheSeconds=60\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md\"\u003eSpecification\u003c/a\u003e ·\n  \u003ca href=\"#concepts\"\u003eConcepts\u003c/a\u003e ·\n  \u003ca href=\"#installation\"\u003eInstall\u003c/a\u003e ·\n  \u003ca href=\"#quick-start\"\u003eQuick start\u003c/a\u003e ·\n  \u003ca href=\"docs/\"\u003eGuides\u003c/a\u003e ·\n  \u003ca href=\"docs/\"\u003eAPI reference\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n`arcp` is the Python reference implementation of [ARCP](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md), the Agent Runtime Control Protocol. It covers both sides of the wire — `arcp.client.ARCPClient` for submitting and observing jobs, `arcp.runtime.ARCPRuntime` for hosting agents — so either side can talk to any conformant peer in any language without hand-rolling the envelope, sequencing, or lease enforcement.\n\nARCP itself is a transport-agnostic wire protocol for long-running AI agent jobs. It owns the parts of agent infrastructure that don't change between products — sessions, durable event streams, capability leases, budgets, resume — and stays out of the parts that do. ARCP wraps the agent function; it does not define how agents are built, how tools are exposed (that's MCP), or how telemetry is exported (that's OpenTelemetry).\n\n## Installation\n\nRequires Python 3.11 or later. The package is published on PyPI as `agentruntimecontrolprotocol`; the import name stays `arcp`. The default install is the kitchen sink — client, runtime, transports, ASGI/aiohttp middleware, OpenTelemetry middleware, JWKS over HTTPS, and the `arcp` CLI — so everything works without thinking about extras.\n\n```sh\n# everything (default)\npip install agentruntimecontrolprotocol\n\n# semantic intent markers (no-op extras — same deps as default,\n# but useful for documenting which side of the wire a service uses\n# in its lockfile or dependency manifest)\npip install \"agentruntimecontrolprotocol[client]\"\npip install \"agentruntimecontrolprotocol[runtime]\"\npip install \"agentruntimecontrolprotocol[jwks]\"\npip install \"agentruntimecontrolprotocol[otel]\"\n\n# pytest stack for running the test suite\npip install \"agentruntimecontrolprotocol[test]\"\n```\n\n**Available extras** (as declared in the wheel's `Provides-Extra` metadata):\n\n| Extra | What it adds | When to use |\n|-------|-------------|-------------|\n| `client` | _(none — included by default)_ | Document that a service is a job submitter / observer. |\n| `runtime` | _(none — included by default)_ | Document that a service hosts agents and accepts jobs. |\n| `jwks` | _(none — included by default)_ | Document that a service verifies bearer tokens against a remote JWKS. |\n| `otel` | _(none — included by default)_ | Document that a service emits OpenTelemetry spans through `arcp.middleware.otel`. |\n| `test` | `pytest`, `pytest-cov`, `pytest-asyncio`, `pytest-randomly`, `pytest-timeout`, `hypothesis`, `dirty-equals`, `aiohttp`, `starlette`, `opentelemetry-sdk` | Run the SDK's own pytest suite against an installed wheel. |\n\n\u003e Python extras can only **add** dependencies, not subtract — so the `client` / `runtime` / `jwks` / `otel` extras are no-ops at install time. They exist as semantic markers in lockfiles and dependency manifests, so a consumer can declare *which side of the wire they're using* without affecting what `pip` resolves.\n\n## Quick start\n\nConnect to a runtime, submit a job, stream its events to completion:\n\n```python\nimport asyncio\nimport contextlib\nimport os\n\nfrom arcp import ClientInfo, WebSocketTransport\nfrom arcp.client import ARCPClient\n\n\nasync def main() -\u003e None:\n    client = ARCPClient(\n        client=ClientInfo(name=\"quickstart\", version=\"1.0.0\"),\n        token=os.environ[\"ARCP_TOKEN\"],\n    )\n    async with contextlib.aclosing(client):\n        transport = await WebSocketTransport.connect(\"wss://runtime.example.com/arcp\")\n        await client.connect(transport)\n\n        handle = await client.submit(\n            agent=\"data-analyzer\",\n            input={\"dataset\": \"s3://example/sales.csv\"},\n            lease_request={\"net.fetch\": [\"s3://example/**\"]},\n        )\n        async for event in handle.events():\n            print(f\"[{event['kind']}]\", event[\"body\"])\n\n        result = await handle.done\n        print(\"final:\", result.final_status, result.result)\n\n\nasyncio.run(main())\n```\n\nThis is the whole shape of the SDK: open a session, submit work, consume an ordered event stream, get a terminal result or error. Everything below is detail on those four moves.\n\n## Concepts\n\nARCP organizes everything around four concerns — **identity**, **durability**, **authority**, and **observability** — expressed through five core objects:\n\n- **Session** — a connection between a client and a runtime. A session carries identity (a bearer token), negotiates a feature set in a `hello`/`welcome` handshake, and is *resumable*: if the transport drops, you reconnect with a resume token and the runtime replays buffered events. Jobs outlive the session that started them. See [§6](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md).\n- **Job** — one unit of agent work submitted into a session. A job has an identity, an optional idempotency key, a resolved agent version, and a lifecycle that ends in exactly one terminal state: `success`, `error`, `cancelled`, or `timed_out`. See [§7](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md).\n- **Event** — the ordered, session-scoped stream a job emits: logs, thoughts, tool calls and results, status, metrics, artifact references, progress, and streamed result chunks. Events carry strictly monotonic sequence numbers so the stream survives reconnects gap-free. See [§8](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md).\n- **Lease** — the authority a job runs under, expressed as capability grants (`fs.read`, `fs.write`, `net.fetch`, `tool.call`, `agent.delegate`, `cost.budget`, `model.use`). The runtime enforces the lease at every operation boundary; a job can never act outside it. Leases may carry a budget and an expiry, and may be subset and handed to sub-agents via delegation. See [§9](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md).\n- **Subscription** — read-only attachment to a job started elsewhere (e.g. a dashboard watching a job a CLI submitted). A subscriber observes the live event stream but cannot cancel or mutate the job. Distinct from *resume*, which continues the original session and carries cancel authority. See [§7.6](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md).\n\nThe SDK models each of these as first-class objects; the rest of this README shows how.\n\n## Guides\n\n### Sessions and resume\n\nOpen a session, negotiate features, and reconnect transparently after a transport drop using the resume token — jobs keep running server-side while you're gone.\n\n```python\nimport asyncio\nimport contextlib\nimport os\n\nfrom arcp import ClientInfo, SessionResume, WebSocketTransport\nfrom arcp.client import ARCPClient\n\nURL = \"wss://runtime.example.com/arcp\"\nTOKEN = os.environ[\"ARCP_TOKEN\"]\n\n\ndef new_client() -\u003e ARCPClient:\n    return ARCPClient(\n        client=ClientInfo(name=\"resumable\", version=\"1.0.0\"),\n        token=TOKEN,\n    )\n\n\nasync def main() -\u003e None:\n    first = new_client()\n    transport1 = await WebSocketTransport.connect(URL)\n    welcome = await first.connect(transport1)\n    session_id = welcome.session_id\n    resume_token = welcome.resume_token\n\n    handle = await first.submit(agent=\"long-running\", input={})\n    async for _ in handle.events():\n        if first.latest_event_seq \u003e= 2:\n            break\n    last_seq = first.latest_event_seq\n\n    # Drop the transport without sending session.bye; the job keeps running.\n    await transport1.close()\n\n    second = new_client()\n    async with contextlib.aclosing(second):\n        transport2 = await WebSocketTransport.connect(URL)\n        await second.resume(\n            transport2,\n            resume=SessionResume(\n                session_id=session_id,\n                resume_token=resume_token,\n                last_event_seq=last_seq,\n            ),\n        )\n        # The runtime replays every event with seq \u003e last_seq, then resumes live streaming.\n\n\nasyncio.run(main())\n```\n\n### Submitting jobs\n\nSubmit a job with an agent (optionally version-pinned as `name@version`), an input, and an optional lease request, idempotency key, and runtime limit.\n\n```python\nfrom datetime import UTC, datetime, timedelta\n\nfrom arcp import LeaseConstraints\n\nexpires_at = (datetime.now(UTC) + timedelta(minutes=1)).isoformat().replace(\"+00:00\", \"Z\")\n\nhandle = await client.submit(\n    agent=\"weekly-report@2.1.0\",\n    input={\"week\": \"2026-W19\"},\n    lease_request={\"net.fetch\": [\"s3://reports/**\"]},\n    lease_constraints=LeaseConstraints(expires_at=expires_at),\n    idempotency_key=\"weekly-report-2026-W19\",\n    max_runtime_sec=300,\n)\n\nprint(\"job_id =\", handle.job_id)\nprint(\"effective lease =\", handle.lease)\nprint(\"resolved agent =\", handle.agent_ref)\n```\n\n### Consuming events\n\nIterate the ordered event stream — `log`, `thought`, `tool_call`, `tool_result`, `status`, `metric`, `artifact_ref`, `progress`, `result_chunk` — and optionally acknowledge progress so the runtime can release buffered events early.\n\n```python\nfrom arcp import ClientInfo\nfrom arcp.client import ARCPClient, AutoAckOptions\n\nclient = ARCPClient(\n    client=ClientInfo(name=\"ack-demo\", version=\"1.0.0\"),\n    token=TOKEN,\n    # Coalesced session.ack: send when 32+ events have accrued, at most every 250 ms.\n    auto_ack=AutoAckOptions(every_n=32, interval_sec=0.25),\n)\n\nhandle = await client.submit(agent=\"chatty\", input={})\n\nasync for event in handle.events():\n    kind = event[\"kind\"]\n    body = event[\"body\"]\n    if kind == \"log\":\n        print(body.get(\"message\"))\n    elif kind == \"tool_call\":\n        print(\"-\u003e\", body.get(\"name\"), body.get(\"arguments\"))\n    elif kind == \"metric\":\n        print(\"metric\", body)\n    elif kind == \"progress\":\n        print(\"progress\", body)\n    # Or ack manually: await client.ack(client.latest_event_seq)\n```\n\n### Leases and budgets\n\nRequest capabilities, a budget, and an expiry; read budget-remaining metrics as they arrive; handle the runtime's enforcement decisions.\n\n```python\nfrom datetime import UTC, datetime, timedelta\n\nfrom arcp import BudgetExhaustedError, LeaseConstraints, LeaseExpiredError\n\nexpires_at = (datetime.now(UTC) + timedelta(minutes=10)).isoformat().replace(\"+00:00\", \"Z\")\n\nhandle = await client.submit(\n    agent=\"web-research\",\n    input={\"iterations\": 8, \"per_call_usd\": 0.3},\n    lease_request={\n        \"tool.call\": [\"search.*\", \"fetch.*\"],\n        \"cost.budget\": [\"USD:1.00\"],\n    },\n    lease_constraints=LeaseConstraints(expires_at=expires_at),\n)\n\nprint(\"initial budget =\", handle.budget)\n\ntry:\n    async for event in handle.events():\n        if event[\"kind\"] == \"metric\" and event[\"body\"].get(\"name\") == \"cost.budget.remaining\":\n            body = event[\"body\"]\n            print(f\"budget remaining: {body['value']:.2f} {body.get('unit', '')}\")\n    await handle.done\nexcept (BudgetExhaustedError, LeaseExpiredError) as err:\n    # Never retryable: resubmit with a fresh lease or budget instead.\n    print(\"job ended:\", err.code, err.message)\n```\n\n### Subscribing to jobs\n\nAttach read-only to a job submitted elsewhere and observe its live stream (with optional history replay) without cancel authority.\n\n```python\nfrom arcp import ClientInfo, ListJobsFilter, WebSocketTransport\nfrom arcp.client import ARCPClient\n\nobserver = ARCPClient(\n    client=ClientInfo(name=\"dashboard\", version=\"1.0.0\"),\n    token=TOKEN,\n    features=(\"list_jobs\", \"subscribe\"),\n)\nawait observer.connect(await WebSocketTransport.connect(URL))\n\nlisting = await observer.list_jobs(filter=ListJobsFilter(status=(\"running\",)))\nsub = await observer.subscribe(listing.jobs[0].job_id, history=True)\nprint(f\"subscribed from seq={sub.subscribed_from} replayed={sub.replayed}\")\n\nasync for event in sub.handle.events():\n    print(f\"[seq\u003e{sub.subscribed_from}] {event['kind']}\")\n\n# ... later ...\nawait observer.unsubscribe(sub.job_id)\n```\n\n### Error handling\n\nCatch the typed error taxonomy and respect the `retryable` flag — `LEASE_EXPIRED` and `BUDGET_EXHAUSTED` are never retryable; a naive retry fails identically.\n\n```python\nfrom arcp import ARCPError, BudgetExhaustedError, LeaseExpiredError\n\ntry:\n    handle = await client.submit(agent=\"flaky\", input={})\n    await handle.done\nexcept (LeaseExpiredError, BudgetExhaustedError):\n    # Resubmit with a fresh lease / budget instead of retrying.\n    raise\nexcept ARCPError as err:\n    if err.retryable:\n        # Safe to retry with backoff (e.g. INTERNAL_ERROR, TIMEOUT).\n        ...\n    else:\n        raise\n```\n\n## Feature support\n\nARCP features this SDK negotiates during the `hello`/`welcome` handshake:\n\n| Feature flag | Status |\n|---|---|\n| `heartbeat` | Supported |\n| `ack` | Supported |\n| `list_jobs` | Supported |\n| `subscribe` | Supported |\n| `lease_expires_at` | Supported |\n| `cost.budget` | Supported |\n| `model.use` | Supported |\n| `provisioned_credentials` | Supported |\n| `progress` | Supported |\n| `result_chunk` | Supported |\n| `agent_versions` | Supported |\n\n## Transport\n\nARCP is transport-agnostic. This SDK ships a WebSocket transport (default), an stdio transport for in-process child runtimes, and an in-memory transport for tests. WebSocket is the default for networked runtimes; stdio is used for in-process child runtimes. Select one by constructing the corresponding object (`WebSocketTransport.connect(url)`, `StdioTransport(...)`, `pair_memory_transports()`) and passing it to `client.connect(transport)`; the `arcp.middleware.asgi` and `arcp.middleware.aiohttp` packages attach the WebSocket upgrade to Starlette, FastAPI, Litestar, Quart, or `aiohttp.web` servers.\n\n## API reference\n\nFull API reference — every type, method, and event payload — is in [`docs/`](docs/).\n\n## Versioning and compatibility\n\nThis SDK speaks **ARCP v1.1 (draft)**. The SDK follows semantic versioning independently of the protocol; the protocol version it negotiates is shown above and in `session.hello`. A runtime advertising a different ARCP MAJOR is not guaranteed compatible. Feature mismatches degrade gracefully: the effective feature set is the intersection of what the client and runtime advertise, and the SDK will not use a feature outside it.\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md). Protocol questions and proposed changes belong in the [spec repository](https://github.com/agentruntimecontrolprotocol/spec); SDK bugs and feature requests belong here.\n\n## License\n\nApache-2.0 — see [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentruntimecontrolprotocol%2Fpython-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagentruntimecontrolprotocol%2Fpython-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentruntimecontrolprotocol%2Fpython-sdk/lists"}