{"id":51244405,"url":"https://github.com/agentruntimecontrolprotocol/fsharp-sdk","last_synced_at":"2026-06-29T03:00:38.586Z","repository":{"id":356978806,"uuid":"1234794453","full_name":"agentruntimecontrolprotocol/fsharp-sdk","owner":"agentruntimecontrolprotocol","description":"F# / .NET reference SDK for ARCP (Agent Runtime Control Protocol).","archived":false,"fork":false,"pushed_at":"2026-06-22T17:44:47.000Z","size":714,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-22T19:24:34.841Z","etag":null,"topics":["agent-protocol","agent-runtime-control-protocol","agents","ai-agents","arcp","dotnet","durable-execution","fsharp","llm","mcp","sdk","streaming"],"latest_commit_sha":null,"homepage":"https://github.com/agentruntimecontrolprotocol/spec","language":"F#","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:15.000Z","updated_at":"2026-06-21T13:55:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/agentruntimecontrolprotocol/fsharp-sdk","commit_stats":null,"previous_names":["agentruntimecontrolprotocol/fsharp-sdk"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/agentruntimecontrolprotocol/fsharp-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Ffsharp-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Ffsharp-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Ffsharp-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Ffsharp-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agentruntimecontrolprotocol","download_url":"https://codeload.github.com/agentruntimecontrolprotocol/fsharp-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentruntimecontrolprotocol%2Ffsharp-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34911134,"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","dotnet","durable-execution","fsharp","llm","mcp","sdk","streaming"],"created_at":"2026-06-29T03:00:33.115Z","updated_at":"2026-06-29T03:00:38.579Z","avatar_url":"https://github.com/agentruntimecontrolprotocol.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch3 align=\"center\"\u003eARCP F# SDK\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eF# SDK for the Agent Runtime Control Protocol (ARCP) — submit, observe, and control long-running agent jobs from F#.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.nuget.org/packages/Arcp\"\u003e\u003cimg alt=\"NuGet\" src=\"https://img.shields.io/nuget/v/Arcp.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/agentruntimecontrolprotocol/fsharp-sdk/actions/workflows/test.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/agentruntimecontrolprotocol/fsharp-sdk/actions/workflows/test.yml/badge.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/agentruntimecontrolprotocol/fsharp-sdk\"\u003e\u003cimg alt=\"codecov\" src=\"https://codecov.io/gh/agentruntimecontrolprotocol/fsharp-sdk/graph/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=\"docs/\"\u003eAPI reference\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n`Arcp` is the F# 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` for submitting and observing jobs, `Arcp.Runtime` for hosting agents, with `Arcp.AspNetCore` and `Arcp.Giraffe` middleware for in-process hosting — 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 the .NET 10 SDK (`net10.0`); the exact pinned SDK version lives in `global.json`. The umbrella `Arcp` package pulls in `Arcp.Core`, `Arcp.Client`, and `Arcp.Runtime`; pick à la carte if you only need one side of the wire, or add `Arcp.AspNetCore` / `Arcp.Giraffe` / `Arcp.Otel` for host integrations and the `Arcp.Cli` global tool for a ready-made `arcp` binary.\n\n```sh\ndotnet add package Arcp\n# à la carte:\ndotnet add package Arcp.Client   # client side\ndotnet add package Arcp.Runtime  # runtime side\n# host integrations:\ndotnet add package Arcp.AspNetCore\ndotnet add package Arcp.Giraffe\ndotnet add package Arcp.Otel\n# CLI:\ndotnet tool install --global Arcp.Cli\n```\n\n## Quick start\n\nConnect to a runtime, submit a job, stream its events to completion:\n\n```fsharp\nopen System.Threading\nopen ARCP.Core\nopen ARCP.Client\nopen ARCP.Client.Transport\n\ntask {\n    let! transport =\n        WebSocketClientTransport.connectAsync\n            (System.Uri \"wss://runtime.example.com/arcp\")\n            (Some (System.Environment.GetEnvironmentVariable \"ARCP_TOKEN\"))\n            CancellationToken.None\n\n    use client =\n        new ArcpClient(\n            transport,\n            { ArcpClientOptions.defaults with\n                Auth = AuthScheme.Bearer (System.Environment.GetEnvironmentVariable \"ARCP_TOKEN\") })\n\n    let! _session = client.ConnectAsync CancellationToken.None\n\n    let! handle =\n        client.SubmitAsync(\n            { Agent = \"data-analyzer\"\n              Input = Json.serializeToElement {| dataset = \"s3://example/sales.csv\" |}\n              LeaseRequest = Some (Lease.empty |\u003e Lease.withCapability Capabilities.NetFetch [ \"s3://example/**\" ])\n              LeaseConstraints = None\n              IdempotencyKey = None\n              MaxRuntimeSec = None },\n            CancellationToken.None)\n\n    let! result = handle.Result\n    match result with\n    | Ok r -\u003e printfn \"final: %s\" (r.Result |\u003e Option.map (fun v -\u003e v.GetRawText()) |\u003e Option.defaultValue \"null\")\n    | Error e -\u003e eprintfn \"job failed: %s\" (ARCPError.code e)\n\n    do! client.CloseAsync(None, CancellationToken.None)\n} |\u003e fun t -\u003e t.GetAwaiter().GetResult()\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```fsharp\nopen System\nopen System.Threading\nopen ARCP.Core\nopen ARCP.Client\nopen ARCP.Client.Transport\n\ntask {\n    let! transport =\n        WebSocketClientTransport.connectAsync\n            (Uri \"wss://runtime.example.com/arcp\")\n            (Some \"demo-token\")\n            CancellationToken.None\n\n    let client =\n        new ArcpClient(\n            transport,\n            { ArcpClientOptions.defaults with\n                Auth = AuthScheme.Bearer \"demo-token\" })\n\n    let! session = client.ConnectAsync CancellationToken.None\n    let sessionId = session.SessionId\n    let resumeToken = session.ResumeToken\n    // Track the highest event_seq you've durably processed; in this SDK\n    // the auto-ack scheduler captures it on your behalf when `ack` is\n    // negotiated, but you can also persist `session.ack`'s argument.\n\n    // ... transport drops ...\n\n    let! transport2 =\n        WebSocketClientTransport.connectAsync\n            (Uri \"wss://runtime.example.com/arcp\")\n            (Some \"demo-token\")\n            CancellationToken.None\n\n    // The session.hello carries a ResumeRequest; the runtime replays\n    // every event with event_seq \u003e LastEventSeq, then resumes streaming.\n    // See ResumeRequest in ARCP.Core.Messages for the wire shape.\n    return sessionId, resumeToken\n} |\u003e ignore\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```fsharp\nlet! handle =\n    client.SubmitAsync(\n        { Agent = \"weekly-report@2.1.0\"\n          Input = Json.serializeToElement {| week = \"2026-W19\" |}\n          LeaseRequest =\n              Some (Lease.empty\n                    |\u003e Lease.withCapability Capabilities.NetFetch [ \"s3://reports/**\" ])\n          LeaseConstraints =\n              Some { ExpiresAt = DateTimeOffset.UtcNow.AddMinutes 1.0 }\n          IdempotencyKey = Some \"weekly-report-2026-W19\"\n          MaxRuntimeSec = Some 300 },\n        CancellationToken.None)\n\nprintfn \"job_id = %s\" handle.JobId.Value\nprintfn \"credentials = %d provisioned\" (List.length handle.Credentials)\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. Auto-ack runs in the background once `ack` is negotiated (32 events / 250 ms windows by default).\n\n```fsharp\nlet enumerator = handle.Events.GetAsyncEnumerator CancellationToken.None\ntry\n    let mutable more = true\n    while more do\n        let! has = enumerator.MoveNextAsync().AsTask()\n        if not has then\n            more \u003c- false\n        else\n            match enumerator.Current with\n            | JobEventBody.Log (level, message) -\u003e\n                printfn \"[%A] %s\" level message\n            | JobEventBody.ToolCall (tool, args, _callId) -\u003e\n                printfn \"-\u003e tool %s %s\" tool (args.GetRawText())\n            | JobEventBody.Metric (name, value, unit, _) -\u003e\n                printfn \"metric %s = %O %s\" name value (Option.defaultValue \"\" unit)\n            | JobEventBody.Progress (current, total, _, _) -\u003e\n                printfn \"progress %O / %O\" current (Option.defaultValue 0m total)\n            | other -\u003e\n                printfn \"event %s\" (JobEventBody.kind other)\nfinally\n    ignore (enumerator.DisposeAsync().AsTask())\n\n// Manual ack is rarely needed:\n// do! client.AckAsync(lastSeq, CancellationToken.None)\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```fsharp\nlet lease =\n    Lease.empty\n    |\u003e Lease.withCapability Capabilities.ToolCall [ \"search.*\"; \"fetch.*\" ]\n    |\u003e Lease.withCapability Capabilities.CostBudget [ \"USD:1.00\" ]\n\nlet! handle =\n    client.SubmitAsync(\n        { Agent = \"web-research\"\n          Input = Json.serializeToElement {| iterations = 8; perCallUSD = 0.3 |}\n          LeaseRequest = Some lease\n          LeaseConstraints =\n              Some { ExpiresAt = DateTimeOffset.UtcNow.AddMinutes 10.0 }\n          IdempotencyKey = None\n          MaxRuntimeSec = None },\n        CancellationToken.None)\n\nlet watchBudget () =\n    task {\n        for body in handle.Events do\n            match body with\n            | JobEventBody.Metric (\"cost.budget.remaining\", value, unit, _) -\u003e\n                printfn \"budget remaining: %O %s\" value (Option.defaultValue \"\" unit)\n            | _ -\u003e ()\n    } |\u003e ignore\n\nlet! result = handle.Result\nmatch result with\n// BUDGET_EXHAUSTED and LEASE_EXPIRED are never retryable.\n| Error (ARCPError.BudgetExhausted currency) -\u003e\n    eprintfn \"out of %s — resubmit with a fresh budget\" currency\n| Error e -\u003e eprintfn \"job ended: %s\" (ARCPError.code e)\n| Ok _ -\u003e ()\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```fsharp\nlet observer =\n    new ArcpClient(\n        transport,\n        { ArcpClientOptions.defaults with\n            Auth = AuthScheme.Bearer \"dashboard-token\" })\n\nlet! _ = observer.ConnectAsync CancellationToken.None\nlet! listing =\n    observer.ListJobsAsync(\n        Some { Status = Some [ JobStatus.Running ]; Agent = None; IdempotencyKey = None },\n        Some 10,\n        None,\n        CancellationToken.None)\n\nlet firstRunning = listing.Jobs |\u003e List.head\nlet! sub =\n    observer.SubscribeAsync(\n        JobId.ofString firstRunning.JobId,\n        { SubscribeOptions.defaults with History = true },\n        CancellationToken.None)\n\nfor body in sub.Events do\n    printfn \"[%s] %s\" (JobEventBody.kind body) (sprintf \"%A\" body)\n\n// ... later ...\ndo! observer.UnsubscribeAsync(sub.JobId, CancellationToken.None)\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```fsharp\nlet! result = handle.Result\nmatch result with\n| Ok r -\u003e printfn \"ok: %s\" (r.Result |\u003e Option.map (fun v -\u003e v.GetRawText()) |\u003e Option.defaultValue \"null\")\n| Error err -\u003e\n    match err with\n    | ARCPError.LeaseExpired _\n    | ARCPError.BudgetExhausted _ -\u003e\n        // Never retryable — resubmit with a fresh lease / budget.\n        raise (ArcpException err)\n    | _ when ARCPError.retryable err -\u003e\n        // Safe to retry with backoff (TIMEOUT, HEARTBEAT_LOST, INTERNAL_ERROR).\n        eprintfn \"transient: %s\" (ARCPError.code err)\n    | _ -\u003e\n        eprintfn \"fatal: %s — %s\" (ARCPError.code err) (ARCPError.message err)\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), a newline-delimited JSON stdio transport for in-process child runtimes, and an in-memory loopback transport for tests and same-process samples. WebSocket is the default for networked runtimes; stdio is used for in-process child runtimes. Select one by constructing the corresponding `ITransport` (`WebSocketClientTransport.connectAsync uri token ct`, `new StdioTransport(stdin, stdout, ownsStreams=false)`, `MemoryTransport.CreatePair()`) and passing it to the `ArcpClient` constructor; `Arcp.AspNetCore` exposes `IEndpointRouteBuilder.MapArcp(...)` to attach the runtime-side WebSocket upgrade to Kestrel, and `Arcp.Giraffe` exposes `useArcp` for Giraffe pipelines.\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%2Ffsharp-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagentruntimecontrolprotocol%2Ffsharp-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentruntimecontrolprotocol%2Ffsharp-sdk/lists"}