Getting Started
This page takes you from cargo add acdp to a signed publish request and a
verified retrieval. For the protocol-level meaning of the objects you build
here, see RFC-ACDP-0001 (Core)
and RFC-ACDP-0002 (Context Body).
Install
cargo add acdp # client profile (default)
cargo add acdp --no-default-features # types + crypto only, no HTTP
cargo add acdp --features server # add the registry-side validator
cargo add acdp --features cli # add the `acdp` binaryMSRV is 1.86. The crate is #![forbid(unsafe_code)].
Which feature do I need?
| Feature | Default | Pulls in | Use when you are… |
|---|---|---|---|
client | ✓ | reqwest, tokio, rustls, lru | an agent or consumer retrieving and verifying contexts. |
| (none) | — | serde, sha2, ed25519-dalek | building or signing contexts offline, or embedding in a binding (HTTP done by the host). |
server | ✗ | validation + store traits | implementing a registry. |
cli | ✗ | client + tokio macros | you want the acdp command-line tool. |
tracing | ✗ | tracing | you want #[instrument] spans on async ops. |
The pure-types/crypto core (--no-default-features) has no HTTP stack —
this is exactly what the language bindings build against.
Your first publish request (producer)
A producer wraps a signing key and a did:web identity, then uses a fluent
builder. .build() validates the request, computes content_hash, and signs
— all in one call.
use acdp::{
crypto::SigningKey,
producer::Producer,
types::{AgentDid, ContextType, DataRef, DataRefType, Visibility},
};
// In production, load the seed from secure storage (HSM, env, KMS).
let key = SigningKey::generate();
let producer = Producer::new(
key,
AgentDid::new("did:web:agents.example.com:my-agent"),
"did:web:agents.example.com:my-agent#key-1", // the verification method id
);
let req = producer
.publish_request()
.title("Q1 2026 revenue snapshot")
.context_type(ContextType::DataSnapshot)
.visibility(Visibility::Public)
.description("Quarterly revenue figures aggregated by region.")
.tags(vec!["finance", "revenue", "q1-2026"])
.domain("finance")
.data_refs(vec![DataRef::uri(
DataRefType::PrimaryResult,
"https://data.example.com/revenue/q1-2026.parquet",
)])
.build()
.expect("build failed");
println!("content_hash: {}", req.content_hash);
println!("signature: {} ({})", req.signature.value, req.signature.algorithm);
// What you POST to the registry:
let json = serde_json::to_string_pretty(&req).unwrap();The full producer API — supersession, lineage, every data-ref form, and the
acdp_version gotcha — is in Producing contexts.
Your first verified retrieval (consumer)
With the client feature, VerifiedContext::fetch does retrieve + schema
validate + hash recompute + DID resolve + signature verify in one call. It
returns only if every step passes.
# #[cfg(feature = "client")]
# async fn run() -> Result<(), acdp::AcdpError> {
use acdp::{
client::{RegistryClient, VerifiedContext},
did::WebResolver,
types::CtxId,
};
let client = RegistryClient::new("https://registry.example.com")?;
let resolver = WebResolver::new();
let ctx_id = CtxId("acdp://registry.example.com/12345678-1234-4321-8123-123456781234".into());
let ctx = VerifiedContext::fetch(&client, &resolver, &ctx_id).await?;
println!("title: {}", ctx.body().title);
println!("status: {:?}", ctx.registry_state().status);
# Ok(()) }The verification stages, the diagnostic fetch_report variant, and
cross-registry resolution are covered in Consuming & verifying.
Run the examples
The repo ships two runnable examples:
cargo run --example producer # build + sign a request, print the JSON
cargo run --example consumer --features client # verify the sig-001 golden vector offlineThe consumer example deliberately runs offline against the sig-001
golden vector so it needs no live registry — it exercises the same primitives
VerifiedContext::fetch uses internally.
Common first errors
| Symptom | Cause | Fix |
|---|---|---|
RegistryClient::new returns Err for an http:// URL | HTTPS is mandatory (RFC-ACDP-0008). | Use https://. For tests against a local listener, see Security. |
RegistryClient::new rejects an IP-literal URL | IP literals are blocked to force DNS-time SSRF filtering. | Use a hostname. |
build() returns AcdpError::SchemaViolation | A field exceeds a length/size cap, or data_refs are malformed. | Check the message; see Producing contexts. |
fetch returns AcdpError::KeyResolution | The producer's did:web document didn't resolve, or its host resolved to a blocked IP range. | Confirm the DID host serves did.json over HTTPS on a public IP. |
fetch returns AcdpError::InvalidSignature | The signature didn't verify against the resolved key — possibly tampering or an acdp_version mismatch. | See the acdp_version note. |
The full error taxonomy and which errors are safe to retry is in Errors & retries.
Pre-PR checks
If you're contributing to the crate, the CI-equivalent local check set is:
cargo fmt --all -- --check
cargo clippy --all-features --all-targets -- -D warnings
cargo clippy --no-default-features --all-targets -- -D warnings
cargo test --all-features
cargo test --no-default-features
ACDP_SPEC_DIR=../agentcontextdistributionprotocol cargo test --test conformanceSee Conformance & testing for what the spec-dir variable does.