# delegated
Fail-closed trust evaluation for agentic AI systems.
`delegated` verifies delegation tokens and enforces policy on every agent action — before your tools, APIs, or downstream agents run anything. Drop it in as Tower middleware, call the standalone adapters, or use the client SDK to attach trust claims to outbound requests.
[](https://crates.io/crates/delegated)
[](https://docs.rs/crate/delegated/latest)
## What it does
- **Verifies** Ed25519-signed delegation tokens and agent identity documents
- **Enforces** configurable policy: allowed actions, max spend, delegation depth, calendar constraints, email domain allowlists, cognitive and reputation gates
- **Blocks** revoked tokens, emergency-denied agents, nonce replays — fail-closed on backend errors
- **Audits** every decision to a structured JSONL sink
- **Issues** delegation tokens and identity documents via fluent builders with key rotation support
## Feature flags
| *(none)* | Core evaluation pipeline, adapters, builders, file-backed state |
| `async` | `AsyncTrustStateStore` trait + async engine variants |
| `axum` | `DelegatedLayer` Tower middleware for axum |
| `client` | `DelegatedClient` for sending trust-validated outbound requests |
| `redis` | `RedisTrustStateStore` backed by Redis (async) |
| `tracing` | `tracing` spans on the evaluation hot path |
| `metrics` | `metrics` counters and histograms |
| `oidc-bridge` | `IdentityVerifier` trait for OIDC-based identity verification |
## Quickstart — server side (axum middleware)
```rust
use std::sync::Arc;
use delegated::{
DelegatedLayerBuilder, InMemoryAsyncTrustState, JsonlFileAuditSink,
};
let trust_state = Arc::new(InMemoryAsyncTrustState::new());
let sink = Arc::new(JsonlFileAuditSink::new("audit.jsonl"));
let layer = DelegatedLayerBuilder::new(trust_state, sink)
.with_max_body_bytes(1024 * 1024) // optional, default is 1 MiB
.build();
let app = axum::Router::new()
.route("/tools/call", axum::routing::post(my_handler))
.layer(layer);
```
Every POST to `/tools/call` is evaluated before `my_handler` runs. Denied requests return 403 with `{allowed, stage, reason}` JSON; allowed requests pass through. Oversized request bodies return 413.
## Quickstart — client side
Build a `RequestEnvelope` with the issuance builders and attach it to outbound requests:
```rust
use delegated::{
DelegatedClient,
issuance::{AgentIdentityDocumentBuilder, DelegationTokenBuilder, RequestEnvelopeBuilder},
};
use ed25519_dalek::SigningKey;
let key = SigningKey::from_bytes(&secret_key_bytes);
let doc = AgentIdentityDocumentBuilder::new()
.agent_id("agent:example:scheduler:v1")
.owner_id("org:example")
.issuer("https://trust.example.ai")
.identity_type("spiffe")
.subject("spiffe://example.ai/agents/scheduler")
.key_id("key-2026-01")
// Register an additional key for rotation:
.additional_public_key("key-2026-02", &rotation_key.verifying_key())
.supported_protocol("http")
.supported_auth_method("delegation_token")
.endpoint("http", "https://agents.example.ai/scheduler")
.build_and_sign(&key)?;
let token = DelegationTokenBuilder::new()
.issuer("https://trust.example.ai")
.agent_id("agent:example:scheduler:v1")
.delegator_id("user:alice")
.owner_id("org:example")
.audience("tool:google-calendar")
.allowed_action("calendar.create_event")
.key_id("key-2026-01")
.expires_in(chrono::Duration::hours(1))
.build_and_sign(&key)?;
let envelope = RequestEnvelopeBuilder::new()
.identity_document(doc)
.token(token)
.audience("tool:google-calendar")
.action("calendar.create_event")
.build()?;
let client = DelegatedClient::new();
let resp = client.evaluate_http("https://api.example.com/trust", &envelope).await?;
if resp.is_allowed() {
// proceed
}
```
## Standalone adapters (without axum)
Call the adapters directly in any async or sync context:
```rust
// HTTP (sync)
use delegated::{handle_http_json_request, JsonlFileAuditSink};
use chrono::Utc;
let sink = JsonlFileAuditSink::new("audit.jsonl");
let response = handle_http_json_request(&raw_body, Utc::now(), &sink);
// response.status_code, response.body["allowed"]
// MCP
use delegated::handle_mcp_jsonrpc_request;
let response = handle_mcp_jsonrpc_request(&raw_body, Utc::now(), &sink);
// A2A
use delegated::handle_a2a_request;
let response = handle_a2a_request(&raw_body, Utc::now(), &sink);
```
## Host context vs request runtime context
`runtime_context` in `RequestEnvelope` is caller-provided data used for request-specific checks (spend amount, target email/calendar, etc).
Security-sensitive trust signals (delegation depth, cognitive/reputation/risk assessments, extra approvals, clock leeway) are supplied by your infrastructure via `HostContext`/`HostContextProvider` and are never trusted from inbound request JSON.
## Trust state
**Sync** (suitable for single-process / CLI use):
```rust
use delegated::{InMemoryTrustState, FileBackedTrustState, TrustStateAdmin};
use std::sync::Arc;
// In-memory with interior mutability — share as Arc<InMemoryTrustState>
let state = Arc::new(InMemoryTrustState::new());
state.revoke_token("dlg_abc")?;
state.emergency_deny_agent("agent:bad")?;
state.revoke_tokens(&["dlg_1", "dlg_2", "dlg_3"])?;
state.clear_emergency_deny_list()?;
state.flush_expired_nonces(chrono::Utc::now())?;
// File-backed (advisory lock, CLI / single-process only)
let state = FileBackedTrustState::new("~/.delegated/trust-state.json");
```
**Async** (Redis for production):
```rust
#[cfg(feature = "redis")]
use delegated::RedisTrustStateStore;
let state = Arc::new(RedisTrustStateStore::connect("redis://127.0.0.1").await?);
// revoke_tokens uses a Redis pipeline; clear_emergency_deny_list uses SCAN+DEL
```
## Revocation and control plane
```rust
use delegated::{revoke_token_with_receipt, emergency_deny_agent, InMemoryTrustState};
let state = InMemoryTrustState::new();
// Revoke with an auditable receipt
let op = revoke_token_with_receipt(
&state, "req_123", "dlg_abc".to_string(), "user:operator",
Some("compromised".to_string()), chrono::Utc::now(),
)?;
println!("receipt: {}", op.receipt.request_id);
```
## Trust pipeline
Every evaluation runs these stages in order, fail-closing at the first failure:
1. `normalize_request` — parse and contract-validate the request envelope
2. `validate_profile_compatibility` — SPIFFE / OIDC / Developer profile checks
3. `verify_signatures` — Ed25519 on identity document and delegation token
4. `validate_identity_document_lifetime` — expiry with configurable clock leeway
5. `enforce_revocation_and_redelegation` — revocation, emergency deny, nonce replay, delegation depth
6. `validate_token_lifetime` — token `issued_at` / `expires_at` window
7. `validate_token_binding` — `agent_id`, `delegator_id`, audience cross-check
8. `evaluate_policy` — allowed actions, max spend, calendar constraints, cognitive/reputation gates
## Custom policy
```rust
use delegated::{Policy, PolicyCheck, RequestEnvelope, HostContext};
struct MyPolicy;
impl Policy for MyPolicy {
fn evaluate(&self, envelope: &RequestEnvelope, ctx: &HostContext) -> Vec<PolicyCheck> {
vec![PolicyCheck {
name: "my_check".to_string(),
passed: envelope.agent_id.starts_with("agent:trusted:"),
reason: "agent not in trusted namespace".to_string(),
}]
}
}
// Pass to evaluate_request_with_policy / evaluate_and_audit_with_policy
```
## CLI
```bash
cargo run --bin delegated-cli -- help
# Sign an identity document
delegated-cli sign-identity identity.json <base64url-private-key>
# Sign a delegation token
delegated-cli sign-token token.json <base64url-private-key>
# Verify a request envelope offline
delegated-cli verify-request request.json
# Interactive grant approval
delegated-cli approve-grant-interactive proposal.json user:operator
# Revoke a token (persists to ~/.delegated/trust-state.json)
delegated-cli revoke-token req_123 dlg_abc user:operator --reason "manual revoke"
```
## Repository layout
```
src/
engine.rs — trust evaluation orchestration
stages.rs — individual evaluation stages
policy.rs — built-in policy checks
revocation.rs — TrustStateStore/Admin traits + InMemoryTrustState + FileBackedTrustState
revocation_async.rs — AsyncTrustStateStore/Admin + InMemoryAsyncTrustState
revocation_redis.rs — RedisTrustStateStore (feature: redis)
issuance.rs — DelegationTokenBuilder + AgentIdentityDocumentBuilder + RequestEnvelopeBuilder
adapters/
http.rs — HTTP adapter
mcp.rs — MCP JSON-RPC adapter
a2a.rs — A2A adapter
axum_layer.rs — Tower Layer middleware (feature: axum)
guard.rs — rate limit + concurrency guard
client.rs — DelegatedClient (feature: client)
audit.rs — AuditSink trait + JsonlFileAuditSink
discovery.rs — DiscoveryService + JWKS handlers
control_plane.rs — revocation receipts + operational reports
crypto.rs — signing + verification primitives
tests/
conformance.rs — end-to-end allow/deny/replay/revocation
interop_harness.rs — cross-adapter and cross-profile parity
reference_cli.rs — CLI signing/verification/approval workflows
integration_server.rs — real axum server + DelegatedClient round-trips
```
## Testing
```bash
# Core tests
cargo test
# Integration tests (requires axum + client features)
cargo test --features "axum,client" --test integration_server
# With all optional features
cargo test --features "async,axum,client,tracing,metrics"
```
## License
Licensed under either [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.