1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! Capability-based DHT discovery for the agent:// URI scheme.
//!
//! This crate provides DHT (Distributed Hash Table) infrastructure for
//! discovering agents by their capabilities. It includes:
//!
//! - **Key derivation**: [`DhtKey`] for Kademlia-style routing
//! - **Registration records**: [`Registration`] with endpoints and attestations
//! - **Trait interface**: [`Dht`], an async trait abstracting DHT backends
//! - **In-memory simulation**: [`SimulatedDht`] for evaluation and testing
//! - **Prefix matching**: [`PathTrie`], a standalone helper for callers that
//! want a local hierarchical index. Discovery itself does not use it: in this
//! crate a prefix lookup reads one materialized ancestor key. A networked
//! backend reads pointers from that key instead, which is one of several
//! differences the Scope section below sets out.
//!
//! # Overview
//!
//! [`SimulatedDht`] implements the **direct** record model of SPECIFICATION.md
//! ยง6.2: agents register at keys for their exact capability path and every
//! ancestor, and a prefix lookup is one exact-key read.
//!
//! ```text
//! key = SHA256(trust_root || "/" || capability_path)
//! ```
//!
//! That model holds where one store owns the namespace and a stored value has
//! no practical size bound, which is what an in-process index is. A Kademlia
//! overlay is not, and is required to use the sharded model instead; see
//! [`agent-uri-dht-libp2p`](https://crates.io/crates/agent-uri-dht-libp2p). The
//! [`Dht`] trait is the same either way.
//!
//! This enables capability-based discovery: "find me an agent at
//! `anthropic.com` that can do `assistant/chat`".
//!
//! # Scope
//!
//! [`SimulatedDht`] is the reference implementation and the test double: one
//! in-process index holding one authoritative copy, which is what you want to
//! develop, test, and benchmark against. It is not a deployment, and two things
//! follow from that.
//!
//! **Its limits are its own.** [`SimulationConfig`] bounds a key at 1000
//! registrations by default and bounds a stored value not at all. A Kademlia
//! overlay bounds a record at the wire size a peer will accept, which for
//! `libp2p-kad` is 1 to 27 registrations, so it stores pointers at ancestor keys
//! and sharded pages behind them. Code that asks whether a write succeeded and
//! pages through what a lookup returned ports unchanged. Configuration tuned
//! against these numbers does not.
//!
//! **A measurement taken against it is a measurement of indexing.** Nothing here
//! partitions, replicates, retries, or loses a peer, so a lookup returns exactly
//! what was written to it. That makes it a clean way to ask whether this crate
//! indexes and pages what it was given correctly, and no way at all to ask what
//! a distributed store returns under load or partition. The discovery precision
//! and recall figures in `agent-uri-eval` were measured against this crate and
//! are bounded by both facts; see its crate docs before quoting them.
//!
//! # Quick Start
//!
//! ```rust
//! use agent_uri::{AgentUri, CapabilityPath, TrustRoot};
//! use agent_uri_attestation::SigningKey;
//! use agent_uri_dht::{
//! Dht, Endpoint, MutationProof, Query, ReadOptions, Registration, SimulatedDht,
//! SimulationConfig, WriteOptions,
//! };
//! use futures::executor::block_on;
//!
//! // Disable verification only for this isolated indexing example. Production
//! // registration uses the secure default and a configured Verifier.
//! let dht = SimulatedDht::new(
//! SimulationConfig::default().with_verify_attestations(false)
//! );
//!
//! // Register an agent. The key it names is the only one that will be able to
//! // change or remove the record later.
//! let uri = AgentUri::parse(
//! "agent://anthropic.com/assistant/chat/llm_01h455vb4pex5vsknk084sn02q"
//! ).unwrap();
//! let endpoint = Endpoint::https("agent.anthropic.com:443");
//! let agent_key = SigningKey::generate();
//! let registration = Registration::new(uri, agent_key.verifying_key(), vec![endpoint]);
//! let proof = MutationProof::sign_registration(&agent_key, ®istration);
//!
//! // Every operation is async, because a real backend crosses the network.
//! block_on(dht.register(registration, &proof, WriteOptions::default())).unwrap();
//!
//! // Discover agents by capability. Results are paged.
//! let query = Query::prefix(
//! TrustRoot::parse("anthropic.com").unwrap(),
//! CapabilityPath::parse("assistant").unwrap(),
//! );
//! let page = block_on(dht.lookup(&query, &ReadOptions::default())).unwrap();
//!
//! assert_eq!(page.len(), 1);
//! assert!(!page.has_more());
//! ```
//!
//! # Operating Against a Network
//!
//! [`Dht`] describes a distributed store, not a map, and its shape reflects
//! that even when the backend is [`SimulatedDht`]:
//!
//! - **Every operation is async and takes a deadline.** [`WriteOptions`] and
//! [`ReadOptions`] carry a timeout; a call that does not finish returns
//! [`DhtError::Timeout`], which does not mean the write failed to apply.
//! - **Writes name a [`Quorum`].** How many replicas must acknowledge is the
//! caller's latency-versus-durability trade, and [`WriteReceipt`] reports
//! what was actually reached.
//! - **Lookups are paged.** [`Dht::lookup`] returns a [`Page`]; follow
//! [`Page::next_cursor`] until it is `None` or you are sampling rather than
//! enumerating.
//! - **Failures are distinguishable.** [`DhtError::is_transient`] separates a
//! slow or partitioned network from a rejected request. Collapsing the two
//! turns a partition into an apparently empty namespace.
//! - **Writes are authorized, not merely addressed.** An agent URI is public,
//! and so is the attestation token stored beside it, so every write carries a
//! [`MutationProof`] signed by the agent's own key. Without this, reaching a
//! replica would be enough to repoint an agent.
//!
//! `SimulatedDht` honors all of this against one in-process copy, so code
//! written against it does not acquire habits a real backend will punish.
//!
//! # Key Derivation
//!
//! DHT keys are derived deterministically from trust root and capability path
//! using SHA-256. This enables:
//!
//! - **Exact lookup**: find agents at a specific capability path
//! - **Prefix lookup**: find agents at a path and every path beneath it, from
//! the single key that path derives
//!
//! There is deliberately no cross-trust-root lookup. Every key is derived from
//! one trust root, so a query is answered by one authority, and a compromised
//! trust-root key reaches no further than the namespace it already owned.
//!
//! ```rust
//! use agent_uri::{TrustRoot, CapabilityPath};
//! use agent_uri_dht::DhtKey;
//!
//! let trust_root = TrustRoot::parse("anthropic.com").unwrap();
//! let path = CapabilityPath::parse("assistant/chat").unwrap();
//!
//! // Derive a key for this capability
//! let key = DhtKey::derive(&trust_root, &path);
//!
//! // Keys are deterministic
//! let key2 = DhtKey::derive(&trust_root, &path);
//! assert_eq!(key, key2);
//!
//! // Partial depth for hierarchical queries
//! let parent_key = DhtKey::derive_at_depth(&trust_root, &path, 1).unwrap();
//! ```
//!
//! # Agent Migration
//!
//! The identity stability of agent:// URIs means agents can migrate
//! between network locations without changing their identity:
//!
//! ```rust
//! use agent_uri::AgentUri;
//! use agent_uri_attestation::SigningKey;
//! use agent_uri_dht::{
//! Dht, Endpoint, Mutation, MutationProof, Registration, SimulatedDht, SimulationConfig,
//! WriteOptions,
//! };
//! use futures::executor::block_on;
//!
//! let dht = SimulatedDht::new(
//! SimulationConfig::default().with_verify_attestations(false)
//! );
//!
//! let uri = AgentUri::parse(
//! "agent://anthropic.com/assistant/chat/llm_01h455vb4pex5vsknk084sn02q"
//! ).unwrap();
//! let agent_key = SigningKey::generate();
//!
//! // Register at initial location
//! let registration = Registration::new(
//! uri.clone(),
//! agent_key.verifying_key(),
//! vec![Endpoint::https("us-east-1.agent.anthropic.com")]
//! );
//! let registration_proof = MutationProof::sign_registration(&agent_key, ®istration);
//! block_on(dht.register(registration.clone(), ®istration_proof, WriteOptions::default()))
//! .unwrap();
//!
//! // Migrate to new location (same identity). The proof is signed against the
//! // record as it stands, so an intercepted migration cannot be re-aimed at
//! // other endpoints, applied twice, or replayed after the agent re-registers.
//! let endpoints = vec![Endpoint::https("eu-west-1.agent.anthropic.com")];
//! let mutation = Mutation::UpdateEndpoint {
//! endpoints: &endpoints,
//! expires_at: registration.expires_at(),
//! };
//! let proof = MutationProof::sign_next(&agent_key, ®istration, &mutation);
//!
//! block_on(dht.update_endpoint(
//! &uri,
//! endpoints.clone(),
//! &proof,
//! WriteOptions::default(),
//! )).unwrap();
//! ```
pub use SimulationConfig;
pub use Endpoint;
pub use DhtError;
pub use DhtKey;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use Registration;
pub use SimulatedDht;
pub use ;
pub use Dht;
pub use PathTrie;