Skip to content

Commit db8be7d

Browse files
nficanoclaude
andcommitted
docs: align session/replay docs with shipped behavior; add YARD + agent-versioning guide
The previous docs and gemspec promised transparent session resume with a SQLite-backed event log. The SDK actually ships in-memory event buffering with replay via `subscribe_job(history: true, from_event_seq: ...)`, and the resume-token wiring is not finished. Update README, CONFORMANCE, architecture, recipes, and the sessions/resume/leases/jobs/troubleshooting guides to describe what we actually ship, and mark §6.3 transparent resume as deferred. Also add YARD-style docstrings to the public Client, Job::Handle, Lease, Runtime, EventLog, and AgentInventory surfaces, refresh the module-deps diagrams, and add a new agent-versioning guide. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c9fe438 commit db8be7d

21 files changed

Lines changed: 210 additions & 116 deletions

CONFORMANCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ v1.0.0). No spec MUST/SHOULD in §4–§16 is unimplemented.
1818
| §6.2 Capability negotiation (intersection) | yes | `lib/arcp/session/capability_set.rb#intersect` |
1919
| §6.2 Feature names: heartbeat, ack, list_jobs, subscribe, lease_expires_at, cost.budget, progress, result_chunk, agent_versions, model.use, provisioned_credentials | yes | `lib/arcp/session/feature.rb` |
2020
| §6.3 session.welcome with resume_token + resume_window_sec | yes | `lib/arcp/session/welcome.rb`, `lib/arcp/runtime/session_actor.rb` |
21-
| §6.3 Resume by last_event_seq | yes | `lib/arcp/runtime/event_log.rb` |
21+
| §6.3 Resume by last_event_seq | deferred | n/a |
2222
| §6.4 session.ping / session.pong heartbeats | yes | `lib/arcp/session/ping.rb`, `lib/arcp/session/pong.rb`, `lib/arcp/client.rb#start_heartbeat!` |
2323
| §6.4 HEARTBEAT_LOST MUST NOT terminate jobs | yes | `lib/arcp/runtime/session_actor.rb` |
2424
| §6.5 session.ack with last_processed_seq | yes | `lib/arcp/session/ack.rb`, `lib/arcp/client.rb#ack` |

README.md

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ ARCP itself is a transport-agnostic wire protocol for long-running AI agent jobs
2727

2828
## Installation
2929

30-
Requires Ruby 3.3 or later. The gem runs on the `socketry/async` reactor and pulls in `async-websocket` for the default networked transport and `sqlite3` for the resume log; no separate extras are needed. Add it to a `Gemfile`:
30+
Requires Ruby 3.3 or later. The gem runs on the `socketry/async` reactor and pulls in `async-websocket` for the default networked transport. The runtime currently buffers events in memory for replay; durable persistence is not shipped yet. Add it to a `Gemfile`:
3131

3232
```ruby
3333
gem 'arcp', '~> 1.0'
@@ -83,7 +83,7 @@ This is the whole shape of the SDK: open a session, submit work, consume an orde
8383

8484
ARCP organizes everything around four concerns — **identity**, **durability**, **authority**, and **observability** — expressed through five core objects:
8585

86-
- **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).
86+
- **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 keeps a replay window in the runtime's in-memory event log. Transparent reconnect resume is not wired through yet; use `history: true` and `from_event_seq` when you need to replay events. Jobs outlive the session that started them. See [§6](https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md).
8787
- **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).
8888
- **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).
8989
- **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).
@@ -93,9 +93,9 @@ The SDK models each of these as first-class objects; the rest of this README sho
9393

9494
## Guides
9595

96-
### Sessions and resume
96+
### Sessions and replay
9797

98-
Open a session, negotiate features, and reconnect transparently after a transport drop using the resume token — jobs keep running server-side while you're gone.
98+
Open a session, submit work, and replay buffered events from the retained log window when you need to recover missed history.
9999

100100
```ruby
101101
require 'async'
@@ -108,22 +108,15 @@ Sync do
108108
client_name: 'resumable'
109109
)
110110

111-
session_id = client.session.id
112-
resume_token = client.session.resume_token
113-
last_seq = Hash.new(0)
114111
handle = client.submit_job(agent: 'long-runner')
115112
handle.subscribe(client: client).each do |event|
116-
last_seq[handle.job_id] = event.body.respond_to?(:seq) ? event.body.seq : last_seq[handle.job_id]
113+
puts "#{event.kind}: #{event.body.to_h}"
117114
end
118115

119-
# ... transport drops ...
120-
121-
resumed = Arcp::Client.open(
122-
transport: new_transport,
123-
auth: { 'scheme' => 'bearer', 'token' => ENV.fetch('ARCP_TOKEN') },
124-
resume: { 'token' => resume_token, 'last_event_seq' => last_seq }
125-
)
126-
# The runtime replays every event with event_seq > last_seq, then resumes live streaming.
116+
replay = client.subscribe_job(job_id: handle.job_id, from_event_seq: 0, history: true)
117+
replay.each do |event|
118+
puts "[replay] #{event.kind}"
119+
end
127120
end
128121
```
129122

@@ -277,7 +270,7 @@ Full API reference — every type, method, and event payload — is in [`docs/`]
277270

278271
## Versioning and compatibility
279272

280-
This 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.
273+
This SDK speaks **ARCP v1.1 (draft)**. The SDK follows semantic versioning independently of the protocol; the negotiated runtime version is available on `client.session.runtime_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.
281274

282275
## Contributing
283276

arcp.gemspec

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ Gem::Specification.new do |spec|
1111
spec.summary = 'Reference Ruby implementation of the Agent Runtime Control Protocol (ARCP).'
1212
spec.description = <<~DESC
1313
Ruby SDK for ARCP: envelope and message model, fiber-based runtime, client,
14-
WebSocket / stdio / in-memory transports, SQLite-backed resume log,
15-
capability negotiation, leases with budget and expiration, streamed results,
16-
and OpenTelemetry trace propagation. Built on socketry/async.
14+
WebSocket / stdio / in-memory transports, in-memory event buffering for
15+
replay, capability negotiation, leases with budget and expiration, streamed
16+
results, and OpenTelemetry trace propagation. Built on socketry/async.
1717
DESC
1818
spec.homepage = 'https://github.com/nficano/arpc'
1919
spec.license = 'Apache-2.0'

docs/architecture.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Arcp
4646
JobContext # passed to handlers
4747
LeaseManager # lease + budget accounting
4848
SubscriptionManager # cross-session observers
49-
EventLog # replay window
49+
EventLog # in-memory replay window
5050
Transport
5151
Base / MemoryTransport / WebSocketTransport / StdioTransport
5252
```
@@ -113,7 +113,7 @@ Arcp::Session::Feature::RESULT_CHUNK # 'result_chunk'
113113
Arcp::Session::Feature::AGENT_VERSIONS # 'agent_versions'
114114
```
115115

116-
`Arcp::Session::Feature::ALL` is a frozen Array of all nine.
116+
`Arcp::Session::Feature::ALL` is a frozen Array of all eleven.
117117

118118
### Negotiation
119119

@@ -163,8 +163,8 @@ client.submit_job(agent: 'code-refactor@1.0.0') # pins to 1.0.0
163163
```
164164

165165
An unknown version raises `Arcp::Errors::AgentVersionNotAvailable` with
166-
`details['available_versions']` populated. `AgentInventory#resolve(ref)`
167-
can validate a ref before submit:
166+
`details['available']` populated. `AgentInventory#resolve(ref)` can
167+
validate a ref before submit:
168168

169169
```ruby
170170
client.session.capabilities.agents.resolve('code-refactor@1.0.0')

docs/diagrams/module-deps-dark.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ digraph ArcpModuleDeps {
8686
SubMgr [label="SubscriptionManager"];
8787

8888
EventLog [
89-
label=<<FONT POINT-SIZE="10">EventLog</FONT><BR/><FONT POINT-SIZE="8" COLOR="#64748B">SQLite</FONT>>,
89+
label=<<FONT POINT-SIZE="10">EventLog</FONT><BR/><FONT POINT-SIZE="8" COLOR="#64748B">In-memory</FONT>>,
9090
shape=cylinder, fillcolor="#1E293B"
9191
];
9292
}

docs/diagrams/module-deps-dark.svg

Lines changed: 1 addition & 1 deletion
Loading

docs/diagrams/module-deps-light.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ digraph ArcpModuleDeps {
8888
SubMgr [label="SubscriptionManager"];
8989

9090
EventLog [
91-
label=<<FONT POINT-SIZE="10">EventLog</FONT><BR/><FONT POINT-SIZE="8" COLOR="#94A3B8">SQLite</FONT>>,
91+
label=<<FONT POINT-SIZE="10">EventLog</FONT><BR/><FONT POINT-SIZE="8" COLOR="#94A3B8">In-memory</FONT>>,
9292
shape=cylinder, fillcolor="#FAFBFC"
9393
];
9494
}

docs/diagrams/module-deps-light.svg

Lines changed: 1 addition & 1 deletion
Loading

docs/guides/agent-versioning.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: Agent versioning
3+
sdk: ruby
4+
kind: guide
5+
order: 13
6+
spec_sections: [§7.5]
7+
---
8+
9+
# Agent versioning
10+
11+
Agents are registered under a stable name and an optional set of
12+
published versions. When a client submits `name@version`, the runtime
13+
resolves that exact version if it exists; when the client submits only
14+
`name`, the runtime uses the registered default.
15+
16+
## Register versions
17+
18+
```ruby
19+
runtime.register_agent(
20+
name: 'code-refactor',
21+
versions: %w[1.0.0 2.0.0],
22+
default: '2.0.0',
23+
handler: ->(ctx) { ctx.finish(result: ctx.agent) }
24+
)
25+
```
26+
27+
## Submit a version-pinned job
28+
29+
```ruby
30+
handle = client.submit_job(agent: 'code-refactor@1.0.0')
31+
result = handle.get_result(client: client)
32+
```
33+
34+
## Validate a ref first
35+
36+
`Arcp::Session::AgentInventory#resolve(ref)` returns the normalized
37+
`name@version` string or `nil` if the ref is unknown. On failure, the
38+
runtime raises `Arcp::Errors::AgentVersionNotAvailable` with
39+
`details['available']` populated with the registered versions.
40+
41+
```ruby
42+
inventory = client.session.capabilities.agents
43+
inventory.resolve('code-refactor@1.0.0') # => "code-refactor@1.0.0"
44+
inventory.resolve('code-refactor@9.9.9') # => nil
45+
```
46+
47+
## See also
48+
49+
- `guides/sessions.md`
50+
- `guides/jobs.md`

docs/guides/jobs.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ HANDLER = lambda do |ctx|
107107
end
108108
```
109109

110-
`try_spend!` atomically decrements the lease's `BudgetCounter`. If the
111-
balance goes negative, the runtime emits `job.error` with code
110+
`Arcp::Runtime::LeaseManager#try_spend!` atomically decrements the
111+
lease's `BudgetCounter` by calling `BudgetCounter#try_decrement`. If the
112+
balance is insufficient, the runtime emits `job.error` with code
112113
`BUDGET_EXHAUSTED`.
113114

114115
When spend is enforced by an upstream gateway instead of local counters,
@@ -121,7 +122,7 @@ issued key — see `guides/credentials.md`.
121122
begin
122123
handle.get_result(client: client)
123124
rescue Arcp::Errors::BudgetExhausted => e
124-
e.details # { 'currency' => 'USD', 'requested' => ..., 'remaining' => ... }
125+
e.details # { 'currency' => 'USD', 'remaining' => '0.30' }
125126
end
126127
```
127128

0 commit comments

Comments
 (0)