F# reference implementation of the Agent Runtime Control Protocol (ARCP). A typed, single-binary control plane for AI-agent runtimes that owns the session, job, event-stream, subscription, and lease machinery so applications stay out of message-routing.
| Package | Role |
|---|---|
Arcp.Core |
Wire types: Envelope, Message DU, ARCPError DU, LeaseGrant, codec. No I/O. |
Arcp.Client |
ArcpClient; in-memory / stdio / WebSocket transports; chunk assembler; auto-ack. |
Arcp.Runtime |
ArcpServer; job manager; lease validator; subscription fan-out; expiry watchdog; budget counters. |
Arcp.AspNetCore |
IEndpointRouteBuilder.MapArcp(...). |
Arcp.Giraffe |
useArcp HttpHandler. |
Arcp.Otel |
OpenTelemetry ActivitySource + canonical attribute helpers. |
Arcp |
Umbrella that re-exports the curated public surface. |
Arcp.Cli |
arcp global tool for serving over stdio and submitting jobs. |
open System.Threading
open ARCP.Core
open ARCP.Client
open ARCP.Client.Transport
open ARCP.Runtime
let server =
ArcpServer({ ArcpServerOptions.defaults with Features = Features.All })
server.RegisterAgent("hello", fun ctx ->
task {
do! ctx.EmitLogAsync(LogLevel.Info, "saying hello", ctx.CancellationToken)
return Json.serializeToElement<string> "Hello, ARCP!"
})
let clientT, serverT = MemoryTransport.CreatePair()
let _ = server.HandleSessionAsync(serverT, CancellationToken.None)
let client =
new ArcpClient(
clientT,
{ ArcpClientOptions.defaults with
Auth = AuthScheme.Bearer "demo"
Features = Features.All })
let session = (client.ConnectAsync CancellationToken.None).Result
let request : JobSubmitRequest = {
Agent = "hello"
Input = Json.serializeToElement<int> 0
LeaseRequest = None
LeaseConstraints = None
IdempotencyKey = None
MaxRuntimeSec = None
}
let handle = (client.SubmitAsync(request, CancellationToken.None)).Result
let result = handle.Result.ResultAll nine flag-gated features ship by default. See CONFORMANCE.md:
heartbeat—session.ping/session.pongack—session.ack; auto-ack scheduler (32 events / 250 ms)list_jobs—session.list_jobs/session.jobssubscribe—job.subscribe/job.subscribed/job.unsubscribelease_expires_at—lease_constraints.expires_at; per-jobExpiryWatchdogcost.budget—cost.budgetcapability + per-currency countersprogress—progressevent bodyresult_chunk— streamedresult_chunkevents + reassemblyagent_versions—name@version; rich agent inventory insession.welcome
Twenty-two runnable F# samples under samples/ — one per
feature, plus host-integration samples for ASP.NET Core, Giraffe, and
OpenTelemetry. Each sample is a single Program.fs paired with a small
shared harness.
dotnet run --project samples/QuickStart
dotnet run --project samples/SubmitAndStream
dotnet run --project samples/CostBudget
dotnet run --project samples/AgentVersions
dotnet run --project samples/AspNetCore # listens on http://127.0.0.1:7878/arcpdotnet pack src/Arcp.Cli
dotnet tool install --global --add-source ./artifacts Arcp.Cli
arcp serve --stdio
arcp send --url ws://localhost:7878/arcp --agent hello --input '{"name":"world"}'dotnet test ARCP.slnxUnit tests (xUnit + FsCheck) cover envelope round-trip, codec dispatch, lease/glob/budget arithmetic, chunk assembly, and feature-set property laws. Integration tests boot a paired client + runtime over the in-memory transport and exercise handshake, job lifecycle, idempotency, subscribe, list-jobs, lease expiry, and budget exhaustion.
See planning/v1.1/04-architecture.md
for the full design. The high-level shape:
- Wire envelope: 8 fields;
arcp = "1";payload : JsonElementfor lazy decode; codec usesFSharp.SystemTextJsonwithJsonUnionEncoding.InternalTagkeyed ontypeso the discriminator sits at the same level as peer fields. ARCPError: exhaustive 15-case DU; everymatchis compile-checked.LeaseGrant: immutable recordMap<namespace, glob list>;validateLeaseOpis stateless and runs glob match → expiry → budget in that order.- Streams: public surface is
IAsyncEnumerable<_>for C# interop;taskSeq { }survives as an internal authoring tool only.