| title | Sessions | ||||||
|---|---|---|---|---|---|---|---|
| sdk | java | ||||||
| spec_sections |
|
||||||
| kind | guide | ||||||
| since | 1.0.0 |
A session is one transport connection with one negotiated feature set. Everything — jobs, events, heartbeats — flows through a session.
-
Client sends
session.hellocarrying:auth— a bearer tokenfeatures— list of capability strings the client supports
-
Runtime verifies the token, intersects feature lists (
Capabilities.intersect), and replies withsession.welcomecarrying:session_id— the new session IDfeatures— the negotiated intersectionresume_token— opaque token for reconnect (§6.3)agents— map of registered agent names → available versions
-
Both peers MUST NOT use any feature outside the negotiated intersection.
ArcpClient client = ArcpClient.builder(transport)
.bearer("my-token")
.features("heartbeat", "ack", "progress") // request capabilities
.build();
client.connect(Duration.ofSeconds(5)); // blocks until session.welcomeThe SDK automatically performs the handshake inside connect. The
negotiated feature set is accessible via client.session().features().
| Feature string | Spec | What it enables |
|---|---|---|
heartbeat |
§6.4 | session.ping / session.pong liveness probing |
ack |
§6.5 | session.ack for buffer reclamation |
progress |
§8.2.1 | ProgressEvent emission |
result_chunk |
§8.4 | Chunked result streaming |
credentials |
§9.8 | CredentialProvisioner lifecycle |
model_use |
§9.7 | model.use lease namespace |
When both peers negotiate heartbeat, the runtime sends session.ping
after each idle interval. The client replies session.pong. Two missed
intervals on either side surface HEARTBEAT_LOST.
Configure the interval on the runtime side:
ArcpRuntime runtime = ArcpRuntime.builder()
.heartbeatIntervalSec(30) // default: 30 s
.build();HeartbeatLostException is retryable — resume the session:
} catch (ExecutionException ex) {
if (ex.getCause() instanceof HeartbeatLostException) {
reconnect(); // session.hello with resume_token
}
}The runtime's heartbeat scheduler runs on a platform-thread
ScheduledExecutorService; the ping dispatch runs on a virtual thread.
A client with ack negotiated emits session.ack { last_processed_seq }
periodically so the runtime can free buffered events earlier. Advisory,
not flow-controlling.
By default the client auto-acks every 200 ms. Override the interval:
ArcpClient client = ArcpClient.builder(transport)
.ackInterval(Duration.ofSeconds(1))
.build();Disable auto-ack (manual mode):
ArcpClient client = ArcpClient.builder(transport)
.autoAck(false)
.build();
// ...
client.ack(handle.lastReceivedSeq());Internally two AtomicLong counters (lastSeenSeq / lastAckedSeq) gate
the ack so it is only sent when there is forward progress.
After any disconnect, the client can reconnect and replay missed events:
String resumeToken = client.session().resumeToken();
long lastSeq = client.session().lastReceivedSeq();
// ... transport reconnects ...
ArcpClient fresh = ArcpClient.builder(newTransport)
.bearer("my-token")
.resumeToken(resumeToken)
.lastEventSeq(lastSeq)
.build();
fresh.connect(Duration.ofSeconds(5));
// Events from lastSeq+1 onward are replayedThe runtime replays events from its in-memory
ResumeBuffer.
The default window is 60 s. After the window, RESUME_WINDOW_EXPIRED is
returned (non-retryable — start a fresh session).
See resume.md for the full reconnect flow.
Retrieve all jobs associated with the current principal:
Page<JobSummary> page = client.listJobs(JobFilter.all());
while (page.hasNext()) {
page.items().forEach(s -> System.out.println(s.jobId() + " " + s.status()));
page = page.next();
}Jobs are scoped to the authenticated principal; no cross-principal data is exposed.
client.close(); // sends session.bye, then closes transportArcpClient implements AutoCloseable; use try-with-resources for
guaranteed cleanup.