{"id":51244585,"url":"https://github.com/agentruntimecontrolprotocol/swift-sdk","last_synced_at":"2026-06-29T03:03:01.748Z","repository":{"id":356978819,"uuid":"1234794766","full_name":"agentruntimecontrolprotocol/swift-sdk","owner":"agentruntimecontrolprotocol","description":"Swift reference SDK for ARCP (Agent Runtime Control Protocol).","archived":false,"fork":false,"pushed_at":"2026-06-12T13:50:25.000Z","size":586,"stargazers_count":1,"open_issues_count":30,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T15:23:37.369Z","etag":null,"topics":["agent-protocol","agent-runtime-control-protocol","agents","ai-agents","arcp","durable-execution","llm","mcp","sdk","streaming","swift"],"latest_commit_sha":null,"homepage":"https://github.com/agentruntimecontrolprotocol/spec","language":"Swift","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:39.000Z","updated_at":"2026-06-12T13:50:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/agentruntimecontrolprotocol/swift-sdk","commit_stats":null,"previous_names":["agentruntimecontrolprotocol/swift-sdk"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/agentruntimecontrolprotocol/swift-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fswift-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fswift-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fswift-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fswift-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agentruntimecontrolprotocol","download_url":"https://codeload.github.com/agentruntimecontrolprotocol/swift-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Fswift-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34911135,"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","sdk","streaming","swift"],"created_at":"2026-06-29T03:03:01.042Z","updated_at":"2026-06-29T03:03:01.739Z","avatar_url":"https://github.com/agentruntimecontrolprotocol.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch3 align=\"center\"\u003eARCP Swift SDK\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eSwift SDK for the Agent Runtime Control Protocol (ARCP) — submit, observe, and control long-running agent jobs from Swift.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://swiftpackageindex.com/agentruntimecontrolprotocol/swift-sdk\"\u003e\u003cimg alt=\"Swift Package Index\" src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fagentruntimecontrolprotocol%2Fswift-sdk%2Fbadge%3Ftype%3Dswift-versions\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://swiftpackageindex.com/agentruntimecontrolprotocol/swift-sdk\"\u003e\u003cimg alt=\"Platforms\" src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fagentruntimecontrolprotocol%2Fswift-sdk%2Fbadge%3Ftype%3Dplatforms\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/agentruntimecontrolprotocol/swift-sdk/actions/workflows/test.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/agentruntimecontrolprotocol/swift-sdk/actions/workflows/test.yml/badge.svg\"\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\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/badge/license-Apache--2.0-lightgrey\"\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=\"https://swiftpackageindex.com/agentruntimecontrolprotocol/swift-sdk/main/documentation/arcp\"\u003eAPI reference\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nThe `ARCP` Swift package is the Swift 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 — `ARCPClient` for submitting and observing jobs, `ARCPRuntime` for hosting agents through a `ToolHandler` / `JobContext` pair — 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 Swift 6.1 or later and macOS 14+. The SDK ships as a single Swift Package Manager package named `ARCP` with one executable product, `arcp`, for the bundled CLI. Add the dependency to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/agentruntimecontrolprotocol/swift-sdk.git\", from: \"1.1.0\"),\n],\ntargets: [\n    .target(name: \"MyApp\", dependencies: [\n        .product(name: \"ARCP\", package: \"swift-sdk\"),\n    ]),\n]\n```\n\n## Quick start\n\nConnect to a runtime, submit a job, stream its events to completion:\n\n```swift\nimport ARCP\nimport NIOPosix\n\nlet group = MultiThreadedEventLoopGroup(numberOfThreads: 1)\nlet transport = try await WebSocketClient.connect(\n    url: \"wss://runtime.example.com/arcp\",\n    eventLoopGroup: group\n)\n\nlet client = try await ARCPClient.open(\n    transport: transport,\n    auth: AuthBlock(scheme: .bearer, token: ProcessInfo.processInfo.environment[\"ARCP_TOKEN\"]),\n    client: IdentityBlock(kind: \"quickstart\", version: \"1.0.0\"),\n    capabilities: Capabilities(streaming: true, durableJobs: true, subscriptions: true)\n)\n\nlet result = try await client.invoke(\n    tool: \"data-analyzer\",\n    arguments: .object([\"dataset\": .string(\"s3://example/sales.csv\")])\n)\n\nswitch result.outcome {\ncase .completed(let payload):\n    print(\"final:\", payload.result ?? payload.summary ?? .null)\ncase .failed(let error):\n    print(\"failed [\\(error.code.rawValue)]:\", error.message)\ncase .cancelled(let payload):\n    print(\"cancelled:\", payload.reason)\n}\n\nawait client.close()\ntry await group.shutdownGracefully()\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 `session.open`/`session.accepted` handshake, and is *resumable*: if the transport drops, you reconnect and the runtime replays buffered events from your last seen message id (`resume` with `after_message_id`). Resume-token rotation is not yet implemented; resume past the retained window returns `RESUME_WINDOW_EXPIRED`. 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`). Lease enforcement is **agent-cooperative**: the runtime validates `lease_constraints.expires_at` at submission (UTC, future) and exposes `checkLeaseExpiration`, `checkModelUse`, and `charge` on `JobContext`, which a handler MUST invoke before authority-bearing operations. The runtime does not yet mediate `fs.*`/`net.fetch`/`tool.call`/`agent.delegate` itself, so it cannot unilaterally stop a handler that skips the checks. Leases may carry a budget and an expiry, and the §9.4 subset rules are validated by `LeaseSubsetting`. 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```swift\nimport ARCP\n\nlet client = try await ARCPClient.open(\n    transport: transport,\n    auth: AuthBlock(scheme: .bearer, token: token),\n    client: IdentityBlock(kind: \"resumable\", version: \"1.0.0\"),\n    capabilities: Capabilities(streaming: true, durableJobs: true)\n)\n\n// Track the last envelope we saw so we can resume gap-free.\nvar lastSeen: MessageId?\nlet drainer = Task {\n    for await envelope in client.unhandled {\n        lastSeen = envelope.id\n    }\n}\n\n// ... transport drops; reconnect over a fresh transport ...\n\nlet resumed = try await ARCPClient.open(\n    transport: try await WebSocketClient.connect(url: url, eventLoopGroup: group),\n    auth: AuthBlock(scheme: .bearer, token: token),\n    client: IdentityBlock(kind: \"resumable\", version: \"1.0.0\"),\n    capabilities: Capabilities(streaming: true, durableJobs: true)\n)\ntry await resumed.send(\n    Envelope(\n        sessionId: resumed.info.sessionId, jobId: jobId,\n        payload: .resume(ResumePayload(afterMessageId: lastSeen, includeOpenStreams: true))\n    )\n)\n// The runtime replays every envelope with id \u003e lastSeen, then resumes live streaming.\ndrainer.cancel()\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```swift\nlet invocation = try await client.invoke(\n    tool: \"weekly-report@2.1.0\",\n    arguments: .object([\"week\": .string(\"2026-W19\")]),\n    costBudget: .from([\"USD\": 1.00]),\n    leaseConstraints: LeaseConstraints(expiresAt: Date().addingTimeInterval(60)),\n    idempotencyKey: IdempotencyKey(\"weekly-report-2026-W19\")\n)\n\nprint(\"job_id =\", invocation.jobId?.rawValue ?? \"(pending)\")\nprint(\"outcome =\", invocation.outcome)\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```swift\nlet invocation = try await client.invoke(\n    tool: \"summarizer\",\n    arguments: .object([\"text\": .string(corpus)])\n)\n\n// Stream structured progress while the job runs.\nlet progressTask = Task {\n    for await progress in invocation.progress {\n        let percent = progress.percent.map { String(format: \"%.0f%%\", $0) } ?? \"?\"\n        print(\"progress \\(percent) — \\(progress.message ?? \"\")\")\n    }\n}\n\n// Every event the client didn't consume internally surfaces here in order.\nfor await envelope in await client.unhandled {\n    switch envelope.payload {\n    case .log(let log):\n        print(\"[\\(log.level)] \\(log.message)\")\n    case .metric(let m):\n        print(\"metric \\(m.name)=\\(m.value)\\(m.unit.map { \" \\($0)\" } ?? \"\")\")\n    case .jobCompleted, .jobFailed, .jobCancelled:\n        break\n    default:\n        continue\n    }\n}\nprogressTask.cancel()\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```swift\nlet invocation = try await client.invoke(\n    tool: \"web-research\",\n    arguments: .object([\"iterations\": .int(8), \"perCallUSD\": .double(0.3)]),\n    costBudget: .from([\"USD\": 1.00]),\n    leaseConstraints: LeaseConstraints(expiresAt: Date().addingTimeInterval(600))\n)\n\n// Surface cost.budget.remaining metrics as they arrive.\nlet watcher = Task {\n    for await env in await client.unhandled {\n        if case .metric(let m) = env.payload, m.name == \"cost.budget.remaining\" {\n            let unit = m.unit ?? \"\"\n            print(String(format: \"budget remaining: %.2f %@\", m.value, unit))\n        }\n    }\n}\n\nif case .failed(let error) = invocation.outcome {\n    // BUDGET_EXHAUSTED and LEASE_EXPIRED are never retryable.\n    print(\"job ended [\\(error.code.rawValue)]: \\(error.message)\")\n}\nwatcher.cancel()\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```swift\nlet observer = try await ARCPClient.open(\n    transport: try await WebSocketClient.connect(url: url, eventLoopGroup: group),\n    auth: AuthBlock(scheme: .bearer, token: token),\n    client: IdentityBlock(kind: \"dashboard\", version: \"1.0.0\"),\n    capabilities: Capabilities(subscriptions: true)\n)\n\nlet filter = SubscriptionFilter(jobIds: [jobId])\ntry await observer.send(\n    Envelope(\n        sessionId: observer.info.sessionId,\n        payload: .subscribe(\n            SubscribePayload(filter: filter, since: SubscriptionSince(afterMessageId: nil))\n        )\n    )\n)\n\nfor await envelope in await observer.unhandled {\n    if case .subscribeEvent(let payload) = envelope.payload {\n        print(\"event:\", payload.event)\n    }\n}\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```swift\ndo {\n    let invocation = try await client.invoke(tool: \"flaky\", arguments: .null)\n    if case .failed(let error) = invocation.outcome {\n        switch error.code {\n        case .leaseExpired, .budgetExhausted:\n            throw ARCPError.failedPrecondition(detail: \"renew lease/budget and resubmit\")\n        default:\n            if error.retryable == true {\n                // safe to retry with backoff (e.g. internal, unavailable, deadlineExceeded)\n            }\n        }\n    }\n} catch let error as ARCPError {\n    if error.isRetryable {\n        // backoff and retry\n    }\n    throw error\n}\n```\n\n## Feature support\n\nARCP features this SDK negotiates during the `session.open`/`session.accepted` handshake:\n\n| Feature flag | Status |\n|---|---|\n| `heartbeat` | Supported |\n| `ack` | Planned |\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 `WebSocketTransport` (client-side, built on `WebSocketKit` / SwiftNIO), a `StdioTransport` (NDJSON framing over stdin/stdout for in-process child runtimes), and a `MemoryTransport` (in-process pair for tests and samples). WebSocket is the default for networked runtimes; stdio is used for in-process child runtimes. Select one by constructing the corresponding `Transport` — `WebSocketClient.connect(url:eventLoopGroup:)`, `StdioTransport(inbound:outbound:)`, or `MemoryTransport.makePair()` — and passing it to `ARCPClient.open(transport:auth:client:capabilities:)`.\n\n## API reference\n\nFull API reference — every type, method, and event payload — is in [`docs/`](docs/) and at \u003chttps://swiftpackageindex.com/agentruntimecontrolprotocol/swift-sdk/main/documentation/arcp\u003e.\n\n## Versioning and compatibility\n\nThis SDK speaks **ARCP v1.1 (draft)**. The SDK follows semantic versioning independently of the protocol; the negotiated runtime version is available on `client.info.runtimeIdentity.version`. 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%2Fswift-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagentruntimecontrolprotocol%2Fswift-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentruntimecontrolprotocol%2Fswift-sdk/lists"}