|
1 | | -# `arcp` — Java reference implementation of ARCP v1.0 |
| 1 | +# ARCP Java SDK |
2 | 2 |
|
3 | | -A clean, idiomatic, tested Java reference implementation of the |
4 | | -**Agent Runtime Control Protocol (ARCP)** v1.0 as specified in |
5 | | -[RFC-0001-v2.md](./RFC-0001-v2.md). |
| 3 | +Java SDK for the **Agent Runtime Control Protocol (ARCP)**. Targets JDK 21 |
| 4 | +LTS; tested on 21. Depends on Jackson and SLF4J only at the `api` level — |
| 5 | +no logging binding shipped. |
6 | 6 |
|
7 | | -> **Status:** Phase 0 (skeleton + plan). The Gradle multi-project build |
8 | | -> compiles cleanly and the four gate commands pass; no protocol surface is |
9 | | -> implemented yet. Track progress in [CONFORMANCE.md](./CONFORMANCE.md). |
| 7 | +## Install |
10 | 8 |
|
11 | | -## Requirements |
| 9 | +### Gradle (Kotlin DSL) |
12 | 10 |
|
13 | | -- **JDK 25** (LTS). Available via Homebrew as `openjdk` or by direct download |
14 | | - from [adoptium.net](https://adoptium.net/) / [openjdk.org](https://openjdk.org/). |
15 | | -- **Gradle wrapper.** No system Gradle required; the wrapper bootstraps |
16 | | - Gradle 9.0.0 on first run. |
| 11 | +```kotlin |
| 12 | +dependencies { |
| 13 | + implementation("dev.arcp:arcp:1.0.0") // umbrella |
| 14 | + // or, granular: |
| 15 | + implementation("dev.arcp:arcp-client:1.0.0") // client only |
| 16 | + implementation("dev.arcp:arcp-runtime:1.0.0") // runtime only |
| 17 | + implementation("dev.arcp:arcp-runtime-jetty:1.0.0") // WebSocket server |
| 18 | + implementation("dev.arcp:arcp-otel:1.0.0") // OpenTelemetry tracing |
| 19 | +} |
| 20 | +``` |
17 | 21 |
|
18 | | -## Layout |
| 22 | +### Maven |
19 | 23 |
|
| 24 | +```xml |
| 25 | +<dependency> |
| 26 | + <groupId>dev.arcp</groupId> |
| 27 | + <artifactId>arcp</artifactId> |
| 28 | + <version>1.0.0</version> |
| 29 | +</dependency> |
20 | 30 | ``` |
21 | | -java-sdk/ |
22 | | -├── settings.gradle.kts # multi-project: :lib, :cli, :examples |
23 | | -├── build.gradle.kts # root config |
24 | | -├── gradle/libs.versions.toml # version catalog |
25 | | -├── lib/ # arcp library (Maven Central artifact) |
26 | | -├── cli/ # `arcp` CLI binary (picocli, application plugin) |
27 | | -├── examples/ # runnable example programs |
28 | | -├── PLAN.md # engineering plan |
29 | | -├── CONFORMANCE.md # per-RFC-section status |
30 | | -└── RFC-0001-v2.md # canonical protocol spec |
| 31 | + |
| 32 | +## Quickstart |
| 33 | + |
| 34 | +```java |
| 35 | +import dev.arcp.client.ArcpClient; |
| 36 | +import dev.arcp.client.JobHandle; |
| 37 | +import dev.arcp.core.transport.MemoryTransport; |
| 38 | +import dev.arcp.runtime.ArcpRuntime; |
| 39 | +import dev.arcp.runtime.agent.JobOutcome; |
| 40 | + |
| 41 | +import com.fasterxml.jackson.databind.node.JsonNodeFactory; |
| 42 | + |
| 43 | +class Quickstart { |
| 44 | + public static void main(String[] args) throws Exception { |
| 45 | + MemoryTransport[] pair = MemoryTransport.pair(); |
| 46 | + ArcpRuntime runtime = ArcpRuntime.builder() |
| 47 | + .agent("echo", "1.0.0", |
| 48 | + (input, ctx) -> JobOutcome.Success.inline(input.payload())) |
| 49 | + .build(); |
| 50 | + runtime.accept(pair[0]); |
| 51 | + |
| 52 | + try (ArcpClient client = ArcpClient.builder(pair[1]).build()) { |
| 53 | + client.connect(java.time.Duration.ofSeconds(5)); |
| 54 | + JobHandle handle = client.submit(ArcpClient.jobSubmit( |
| 55 | + "echo@1.0.0", JsonNodeFactory.instance.objectNode().put("hi", 1))); |
| 56 | + System.out.println(handle.result().get().result()); |
| 57 | + } |
| 58 | + runtime.close(); |
| 59 | + } |
| 60 | +} |
31 | 61 | ``` |
32 | 62 |
|
33 | | -## Build |
| 63 | +For a WebSocket-backed runtime, swap `MemoryTransport` for |
| 64 | +`dev.arcp.runtime.jetty.ArcpJettyServer` on the runtime side and |
| 65 | +`dev.arcp.client.WebSocketTransport.connect(uri)` on the client side. |
34 | 66 |
|
35 | | -```bash |
36 | | -export JAVA_HOME=/path/to/jdk-25 # e.g. /opt/homebrew/opt/openjdk |
37 | | -./gradlew check # compile + test + spotless + jacoco |
38 | | -./gradlew javadoc # publish-quality javadoc |
39 | | -./gradlew :lib:publishToMavenLocal # local Maven artifact (Phase 7) |
40 | | -``` |
| 67 | +## Packaging |
41 | 68 |
|
42 | | -## Architecture |
43 | | - |
44 | | -```mermaid |
45 | | -flowchart TB |
46 | | - app[Application code] |
47 | | - client[ARCPClient] |
48 | | - runtime[ARCPRuntime] |
49 | | - transport[Transport<br/>WebSocket | stdio | InMemory] |
50 | | - store[(SQLite event log)] |
51 | | - app --> client |
52 | | - client --> transport |
53 | | - transport --> runtime |
54 | | - runtime --> store |
55 | | -``` |
| 69 | +| Artifact | What's in it | Depends on | |
| 70 | +| ------------------------------ | -------------------------------------------------- | ------------------------------- | |
| 71 | +| `arcp-core` | Wire types, errors, capability, ids, lease, transport SPI | none | |
| 72 | +| `arcp-client` | `ArcpClient`, `JobHandle`, `ResultStream`, JDK WebSocket transport | `arcp-core` | |
| 73 | +| `arcp-runtime` | `ArcpRuntime`, session FSM, job FSM, lease enforcement, budget counters | `arcp-core` | |
| 74 | +| `arcp` | Umbrella; re-exports client + runtime | `arcp-client`, `arcp-runtime` | |
| 75 | +| `arcp-runtime-jetty` | Embedded Jetty 12 WebSocket server transport | `arcp-runtime` | |
| 76 | +| `arcp-middleware-spring-boot` | Spring Boot 3.x auto-config + WebSocket handler | `arcp-runtime` | |
| 77 | +| `arcp-otel` | OpenTelemetry adapter (transport-wrapping `Tracer`)| `arcp-core`, `opentelemetry-api`| |
| 78 | +| `arcp-tck` | Reusable JUnit 5 `@TestFactory` conformance suite | `arcp-client`, `arcp-runtime` | |
56 | 79 |
|
57 | | -- The **library** (`:lib`) is pure protocol: envelope serialisation, runtime, |
58 | | - client, transports, event log. |
59 | | -- The **CLI** (`:cli`) is a thin picocli wrapper: `arcp serve|tail|send|replay`. |
60 | | -- The **examples** (`:examples`) are runnable demo programs: minimal session, |
61 | | - tool invocation with progress, human input, permission challenge, observer |
62 | | - subscription, agent relay. |
| 80 | +## Layout |
63 | 81 |
|
64 | | -See [PLAN.md](./PLAN.md) for the design rationale, message-type to record map, |
65 | | -state diagrams, open questions, and per-phase deliverables. |
| 82 | +``` |
| 83 | +java-sdk/ |
| 84 | +├── settings.gradle.kts |
| 85 | +├── build.gradle.kts |
| 86 | +├── gradle/libs.versions.toml |
| 87 | +├── arcp-core/ |
| 88 | +├── arcp-client/ |
| 89 | +├── arcp-runtime/ |
| 90 | +├── arcp/ # umbrella |
| 91 | +├── arcp-runtime-jetty/ |
| 92 | +├── arcp-otel/ |
| 93 | +├── arcp-middleware-spring-boot/ |
| 94 | +├── arcp-tck/ |
| 95 | +├── docs/diagrams/ # 6 Graphviz diagrams (light + dark SVGs) |
| 96 | +└── examples/ |
| 97 | + ├── submit-and-stream/ |
| 98 | + ├── cancel/ |
| 99 | + ├── heartbeat/ |
| 100 | + ├── cost-budget/ |
| 101 | + ├── result-chunk/ |
| 102 | + ├── agent-versions/ |
| 103 | + ├── list-jobs/ |
| 104 | + ├── lease-expires-at/ |
| 105 | + ├── idempotent-retry/ |
| 106 | + └── custom-auth/ |
| 107 | +``` |
66 | 108 |
|
67 | | -## Quickstart (Phase 0) |
| 109 | +## Build |
68 | 110 |
|
69 | 111 | ```bash |
70 | | -git clone <repo> |
71 | | -cd arpc/java-sdk |
72 | | -export JAVA_HOME=$(/usr/libexec/java_home -v 25) |
73 | | -./gradlew check # green |
74 | | -./gradlew :cli:run --args="" # prints arcp-java 0.1.0-SNAPSHOT |
| 112 | +export JAVA_HOME=$(/usr/libexec/java_home -v 21) |
| 113 | +./gradlew build # compile + test all modules |
| 114 | +./gradlew :examples:submit-and-stream:run |
| 115 | +./gradlew :arcp-runtime-jetty:test |
75 | 116 | ``` |
76 | 117 |
|
77 | | -End-to-end examples (`:examples:run01` … `:run06`) ship in Phase 7. |
| 118 | +## Features |
| 119 | + |
| 120 | +- §5.1 envelope with `arcp: "1"` and `FAIL_ON_UNKNOWN_PROPERTIES=false` |
| 121 | +- §6.1 bearer auth via `BearerVerifier` SPI; `acceptAny` and `staticToken` helpers |
| 122 | +- §6.2 capability intersection with rich `agents` shape (name + versions + default) |
| 123 | +- §6.3 resume buffer (in-memory ring) and rotating `resume_token` |
| 124 | +- §6.4 heartbeats: scheduler-driven ping; client and runtime treat two missed |
| 125 | + intervals as `HEARTBEAT_LOST` |
| 126 | +- §6.5 `session.ack` with auto-emit rate limit on the client side |
| 127 | +- §6.6 `session.list_jobs` scoped to the session's principal |
| 128 | +- §7.1 `job.submit` with `lease_request`, `lease_constraints`, `idempotency_key` |
| 129 | +- §7.2 idempotency: identical `(principal, key, payload)` reuses the prior `job_id`; |
| 130 | + conflicting payload yields `DUPLICATE_KEY` |
| 131 | +- §7.4 cooperative cancellation via `JobContext.cancelled()` + `Thread.interrupt` |
| 132 | +- §7.5 agent versioning: `name@version` grammar; bare names resolve to advertised |
| 133 | + default; unknown versions surface `AGENT_VERSION_NOT_AVAILABLE` |
| 134 | +- §7.6 subscribe / unsubscribe with optional history replay; subscribers do not |
| 135 | + carry cancel authority |
| 136 | +- §8.2 ten event kinds via sealed `EventBody`, including `progress` (current ≥ 0) |
| 137 | +- §8.4 `result_chunk` reassembly via `ResultStream` (in-memory or `OutputStream` sink) |
| 138 | +- §9 lease grammar + subset check (`Lease.contains`); `LeaseGuard` enforces glob |
| 139 | + patterns with `*` and `**` semantics |
| 140 | +- §9.5 lease expiration: strict UTC-`Z` parsing; scheduled watchdog terminates |
| 141 | + jobs whose lease expires while running |
| 142 | +- §9.6 `cost.budget` via per-currency `AtomicReference<BigDecimal>` counters |
| 143 | + with `USE_BIG_DECIMAL_FOR_FLOATS` on the wire mapper |
| 144 | +- §11 OpenTelemetry trace propagation via `ArcpOtel.withTracing(transport, tracer)`; |
| 145 | + `arcp.session_id` / `arcp.job_id` / `arcp.trace_id` attributes on every span |
| 146 | +- §12 fifteen-code error taxonomy with sealed `ArcpException` / |
| 147 | + `RetryableArcpException` / `NonRetryableArcpException` split |
| 148 | + |
| 149 | +## Concurrency |
| 150 | + |
| 151 | +Virtual threads (JEP 444, stable in JDK 21) drive every per-job worker and |
| 152 | +every transport publisher. `StructuredTaskScope` is intentionally not used |
| 153 | +in published bytecode: it's preview in JDK 21 and finalized in JDK 25 with |
| 154 | +a different shape, and the SDK targets `--release 21`. |
| 155 | + |
| 156 | +A single `ScheduledExecutorService` per runtime drives heartbeat ticks and |
| 157 | +lease expiry watchdogs; client-side, a similar scheduler emits `session.ack` |
| 158 | +and watches the inbound idle timer. |
| 159 | + |
| 160 | +## Conformance |
| 161 | + |
| 162 | +See [CONFORMANCE.md](CONFORMANCE.md) for the spec §-keyed table with file:line |
| 163 | +references. Tests at a glance: |
| 164 | + |
| 165 | +- `arcp-core:test` — envelope round-trip, unknown-field tolerance, capability |
| 166 | + intersection, feature decode |
| 167 | +- `arcp-runtime:test` — agent version resolution, budget counters, |
| 168 | + lease guard, expiry, subset checks |
| 169 | +- `arcp-client:test` — smoke round-trip, idempotency reuse + conflict, |
| 170 | + subscribe with history replay, result-chunk reassembly |
| 171 | +- `arcp-otel:test` — outbound + inbound spans through `InMemorySpanExporter` |
| 172 | +- `arcp-runtime-jetty:test` — end-to-end client + runtime over loopback WebSocket |
| 173 | +- `arcp-middleware-spring-boot:test` — Spring Boot 3.x autoconfig + WebSocket |
| 174 | + handler driven from a `@SpringBootTest` with an embedded Tomcat |
| 175 | +- `arcp-tck:test` — seven dynamic conformance tests via JUnit `@TestFactory`, |
| 176 | + reusable by downstream JVM implementations |
| 177 | + |
| 178 | +Diagrams under [`docs/diagrams/`](docs/diagrams/): module graph, session |
| 179 | +lifecycle, job lifecycle, capability negotiation, heartbeat + ack, result-chunk |
| 180 | +reassembly. Light + dark variants render via `make -C docs/diagrams`. |
78 | 181 |
|
79 | 182 | ## License |
80 | 183 |
|
81 | | -Apache License 2.0 — see [`LICENSE`](LICENSE). |
| 184 | +[Apache-2.0](./LICENSE). |
0 commit comments