diff --git a/README.md b/README.md index 410bb3d..34aad51 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ async fn main() -> Result<(), Box> { MessageType::JobAccepted(p) => println!("accepted: {}", p.job_id), MessageType::JobCompleted(p) => { println!("done: {:?}", p.value); break; } MessageType::JobFailed(p) => return Err(format!("{}: {}", p.code, p.message).into()), - other => println!("[seq={:?}] {}", env.event_seq, other.type_name()), + other => println!("[msg_id={}] {}", env.id, other.type_name()), } } transport.close().await?; @@ -141,7 +141,7 @@ let MessageType::SessionAccepted(welcome) = transport.recv().await?.ok_or("eof") let session_id = welcome.session_id.clone(); let mut last_seq: u64 = 0; -// ... read envelopes, tracking the highest env.event_seq in `last_seq` ... +// ... read envelopes, tracking the highest countable sequence in `last_seq` ... // ... transport drops ... // Reconnect on a fresh transport and resume from `last_seq`: @@ -200,9 +200,9 @@ use arcp::Envelope; let mut last_seq: u64 = 0; while let Some(env) = transport.recv().await? { - if let Some(seq) = env.event_seq { - last_seq = seq; - } + // Track the highest countable sequence in your own application state. + // let seq = highest_countable_sequence_from_your_runtime(); + // last_seq = seq; match env.payload { MessageType::Log(p) => println!("[log {:?}] {}", p.level, p.message), MessageType::Metric(m) => println!("metric[{}] = {} {}", m.name, m.value, m.unit), @@ -378,7 +378,7 @@ Full API reference — every type, method, and event payload — is in [`docs/`] ## Versioning and compatibility -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. +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.accepted`. 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. ## Contributing diff --git a/docs/getting-started.md b/docs/getting-started.md index b6f313e..ba5bf14 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -38,14 +38,14 @@ advertises anonymous auth. ## Run examples -Examples are the fastest way to see client/runtime flows. Some are compact -illustrations with setup elided; the integration tests under [`tests/`](../tests/) -show fully exercised paths. +Examples are the fastest way to see client/runtime flows. The commands +below point at runnable demos with real setup; the integration tests under +[`tests/`](../tests/) show fully exercised paths. ```sh -cargo run --example submit_and_stream -cargo run --example resumability -cargo run --example job_subscribe +cargo run --example session_ack +cargo run --example result_chunk +cargo run --example session_list_jobs cargo run --example cost_budget cargo run --example provisioned_credentials ``` diff --git a/docs/guides/jobs.md b/docs/guides/jobs.md index 80d87b9..a6150ee 100644 --- a/docs/guides/jobs.md +++ b/docs/guides/jobs.md @@ -12,7 +12,9 @@ Spec reference: [§7](../../../spec/docs/draft-arcp-1.1.md#7-jobs). submit-and-await flows. It sends a job request, waits for acceptance, and resolves on the terminal result. -See [`examples/submit_and_stream.rs`](../../examples/submit_and_stream.rs). +See the illustrative [`examples/submit_and_stream.rs`](../../examples/submit_and_stream.rs) +for the submit-and-await shape, and the runnable [`examples/result_chunk/`](../../examples/result_chunk/) +for an end-to-end streaming demo. ## Runtime dispatch diff --git a/docs/guides/resume.md b/docs/guides/resume.md index 48e8175..82ba56c 100644 --- a/docs/guides/resume.md +++ b/docs/guides/resume.md @@ -24,8 +24,11 @@ session. ## Example -[`examples/resumability/`](../../examples/resumability/) demonstrates replaying -events after a session boundary. +[`examples/resumability/`](../../examples/resumability/) sketches the replay +flow after a session boundary, but it is illustrative and still contains +placeholder setup. Use the runnable [`examples/session_ack/`](../../examples/session_ack/) +and [`examples/result_chunk/`](../../examples/result_chunk/) demos for concrete +client/runtime flows. ## Failure modes diff --git a/examples/README.md b/examples/README.md index 6dbfa23..7a030fa 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,17 +1,19 @@ # ARCP Rust examples -Fourteen single-purpose codebases, each named for the protocol primitive +Thirteen single-purpose codebases, each named for the protocol primitive it demonstrates. Mirrors the Python tree at [`python-sdk/examples/`](https://github.com/agentruntimecontrolprotocol/python-sdk/tree/main/examples). -> **Illustrative, not runnable.** Each example imports the in-repo `arcp` +> **Mostly illustrative.** Most examples import the in-repo `arcp` > crate as if it were a published `arcp = "1"`. Setup boilerplate > (transport URL, identity, auth) is elided with `let client: Client = > todo!();`. LLM and framework calls live in tiny stub modules > (`agents.rs`, `steps.rs`, `synth.rs`, ...) so the protocol code in -> `main.rs` is what you read. +> `main.rs` is what you read. The runnable end-to-end examples are the +> concrete demos with real runtime setup, such as `session_ack`, +> `result_chunk`, and `session_list_jobs`. -## The fourteen +## The thirteen | Example | Demonstrates | Spec | |---|---|---| @@ -23,7 +25,7 @@ it demonstrates. Mirrors the Python tree at | [`handoff/`](./handoff) | `agent.handoff` with transcript packed as an artifact, runtime fingerprint pinned. | §14, §16, §8.3 | | [`heartbeats/`](./heartbeats) | Worker federation; heartbeat-loss reroute via `idempotency_key`. | §10.3, §6.4 | | [`capability_negotiation.rs`](./capability_negotiation.rs) | Capability-driven peer routing; standard `cost.usd` rollups. | §7, §17.3.1, §18.3 | -| [`resumability/`](./resumability) | **Real crash and resume.** `std::process::exit(137)` mid-flight; second invocation picks up at the next step. | §10, §19, §6.4 | +| [`resumability/`](./resumability) | **Illustrative crash and replay.** Shows the two-step flow, but still contains placeholder setup. | §10, §19, §6.4 | | [`reasoning_streams/`](./reasoning_streams) | `kind: thought` stream + a peer runtime that subscribes and delegates critiques back. | §11.4, §13, §14 | | [`extensions.rs`](./extensions.rs) | Custom `arcpx.sdr.*.v1` extension namespace + unknown-message handling. | §21 | | [`cancellation.rs`](./cancellation.rs) | Cooperative `cancel` (terminate) vs `interrupt` (pause and ask). | §10.4–§10.5 | @@ -60,11 +62,12 @@ it demonstrates. Mirrors the Python tree at ## Reading order For a brisk tour: `subscriptions`, `leases`, `delegation`, -`resumability` (this one actually crashes and recovers), `cancellation`, -`extensions`, `mcp`. These seven exercise the bulk of the protocol. +`resumability` (illustrative only), `cancellation`, `extensions`, +`mcp`. These six exercise the bulk of the protocol, plus the +placeholder-heavy `resumability` sketch. ## Numbered companions -`01_minimal_session.rs` and `02_tool_invoke.rs` predate this index; -they're a runnable end-to-end against the in-process runtime + memory -transport. The fourteen above are illustrative. +The examples above are illustrative. The runnable end-to-end demos are +the concrete runtime-backed examples in this tree, including +`session_ack`, `result_chunk`, and `session_list_jobs`. diff --git a/src/client/api.rs b/src/client/api.rs index 3c30c14..9895adb 100644 --- a/src/client/api.rs +++ b/src/client/api.rs @@ -615,6 +615,20 @@ impl std::fmt::Debug for ARCPClient { impl ARCPClient { /// Construct over an attached transport. + /// + /// ## Examples + /// + /// ```rust + /// use arcp::transport::paired; + /// use arcp::ARCPClient; + /// + /// # fn main() -> Result<(), arcp::ARCPError> { + /// let (_server_t, client_t) = paired(); + /// let client = ARCPClient::new(client_t).open()?; + /// let _ = client; + /// # Ok(()) + /// # } + /// ``` #[must_use] pub const fn new(transport: T) -> Self { Self { diff --git a/src/envelope.rs b/src/envelope.rs index 6fd994a..309f353 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -17,6 +17,16 @@ //! //! The two are interconvertible via [`Envelope::into_raw`] / //! [`RawEnvelope::try_into_typed`]. +//! +//! ## Examples +//! +//! ```rust +//! use arcp::messages::{MessageType, PingPayload}; +//! use arcp::Envelope; +//! +//! let env = Envelope::new(MessageType::Ping(PingPayload::default())); +//! assert_eq!(env.payload.type_name(), "ping"); +//! ``` use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/lib.rs b/src/lib.rs index 7c25746..4f172cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,45 @@ //! The public API centers on [`ARCPClient`] for consumers and [`ARCPRuntime`] //! for runtimes. //! +//! ## Examples +//! +//! ```rust +//! use arcp::auth::BearerAuthenticator; +//! use arcp::messages::{AuthScheme, Capabilities, ClientIdentity, Credentials}; +//! use arcp::runtime::ARCPRuntime; +//! use arcp::transport::paired; +//! use arcp::ARCPClient; +//! +//! # async fn demo() -> Result<(), Box> { +//! let runtime = ARCPRuntime::builder() +//! .with_authenticator(Box::new(BearerAuthenticator::new().with_token("tok", "alice"))) +//! .build() +//! .await?; +//! +//! let (server_t, client_t) = paired(); +//! let _server = runtime.serve_connection(server_t); +//! +//! let client = ARCPClient::new(client_t) +//! .open()? +//! .authenticate( +//! Credentials { +//! scheme: AuthScheme::Bearer, +//! token: Some("tok".into()), +//! }, +//! ClientIdentity { +//! kind: "demo-client".into(), +//! version: "1.0.0".into(), +//! fingerprint: None, +//! principal: None, +//! }, +//! Capabilities::default(), +//! ) +//! .await?; +//! let _ = client; +//! # Ok(()) +//! # } +//! ``` +//! //! [rfc]: https://github.com/agentruntimecontrolprotocol/spec/blob/main/docs/draft-arcp-1.1.md #![deny(unsafe_code)] diff --git a/src/runtime/server.rs b/src/runtime/server.rs index 6855207..ba6ee6d 100644 --- a/src/runtime/server.rs +++ b/src/runtime/server.rs @@ -223,6 +223,40 @@ impl std::fmt::Debug for ARCPRuntime { impl ARCPRuntime { /// Construct via [`RuntimeBuilder`]. + /// + /// ## Examples + /// + /// ```rust + /// use arcp::auth::BearerAuthenticator; + /// use arcp::messages::{AuthScheme, Capabilities, ClientIdentity, Credentials}; + /// use arcp::runtime::ARCPRuntime; + /// use arcp::transport::paired; + /// use arcp::ARCPClient; + /// + /// # async fn demo() -> Result<(), Box> { + /// let runtime = ARCPRuntime::builder() + /// .with_authenticator(Box::new(BearerAuthenticator::new().with_token("tok", "alice"))) + /// .build() + /// .await?; + /// let (server_t, client_t) = paired(); + /// let _server = runtime.serve_connection(server_t); + /// + /// let _session = ARCPClient::new(client_t) + /// .open()? + /// .authenticate( + /// Credentials { scheme: AuthScheme::Bearer, token: Some("tok".into()) }, + /// ClientIdentity { + /// kind: "demo-client".into(), + /// version: "1.0.0".into(), + /// fingerprint: None, + /// principal: None, + /// }, + /// Capabilities::default(), + /// ) + /// .await?; + /// # Ok(()) + /// # } + /// ``` #[must_use] pub fn builder() -> RuntimeBuilder { RuntimeBuilder::new() diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 7ca0c38..af23856 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -5,6 +5,22 @@ //! minimal: send one envelope, receive the next, close when done. Higher- //! level concerns (idempotency, ordering, backpressure) live above this //! layer. +//! +//! ## Examples +//! +//! ```rust +//! use arcp::messages::{MessageType, PingPayload}; +//! use arcp::transport::{paired, Transport}; +//! use arcp::Envelope; +//! +//! # async fn demo() -> Result<(), Box> { +//! let (sender, receiver) = paired(); +//! sender.send(Envelope::new(MessageType::Ping(PingPayload::default()))).await?; +//! let env = receiver.recv().await?.expect("envelope"); +//! assert_eq!(env.payload.type_name(), "ping"); +//! # Ok(()) +//! # } +//! ``` use async_trait::async_trait;