AxonFlow SDK for Rust
Taking a sponsored workflow to production?
Choose the path that fits:
- Self-serve: free 90-day Evaluation License
- Paid production program: Design Partner or Confidential Pilot - one scoped workflow over 60 or 75 days, founder-led rollout support, upfront conversion pricing, and a fixed decision date; public track from $2,000 or confidential track from $4,000
The paid program requires a dated forcing event, written controls, an executive sponsor, and a technical owner. Prices are subject to eligibility and a signed agreement.
Enterprise-grade Rust SDK for the AxonFlow AI governance platform. Add invisible AI governance to your applications with production-ready features including retry logic, caching, fail-open strategy, and debug mode.
How This SDK Fits with AxonFlow
This SDK is a client library for interacting with a running AxonFlow control plane. It is used from application or agent code to send execution context, policies, and requests at runtime.
A deployed AxonFlow platform (self-hosted or cloud) is required for end-to-end AI governance. SDKs alone are not sufficient—the platform and SDKs are designed to be used together.
Installation
Add this to your Cargo.toml:
[]
= "0.1.0"
= { = "1", = ["full"] }
Quick Start
Basic Usage (Invisible Governance via Interceptor)
The most common way to use AxonFlow is via an Interceptor. This wraps your existing LLM client (e.g., an OpenAI-compatible client) and automatically applies governance to every call.
use ;
use ;
async
Manual Audit (Gateway Mode)
If you are making LLM calls directly and just want to log them for compliance and cost tracking:
use ;
let axon = new?;
// After your direct LLM call
axon.audit_llm_call.await?;
Examples
The SDK includes several runnable examples demonstrating common integration patterns. You can find them in the examples/ directory.
Running the Examples
Before running the examples, set your AxonFlow credentials as environment variables:
# Optional: defaults to http://localhost:8080
Then use cargo run --example <name> to execute an example:
- Basic Chat Governance:
- Model Context Protocol (MCP) Connectors:
- Multi-Agent Planning (MAP):
- Invisible Governance (Interceptors — OpenAI):
- Invisible Governance (Interceptors — Anthropic):
- Decision Explainability (ADR-043):
# from a recent blocked call or audit row - AuthZEN-native authorization (ADR-065) — nine steps, four of them refusals or unresolved:
AuthZEN-native authorization (ADR-065)
POST /api/v1/access/evaluation is the AuthZEN-shaped authorization surface. It is the surface to write new integrations against: at v11 the engine behind it becomes the ADR-065 Policy Decision Point with no wire change, so an integration written against it migrates once rather than twice. Nothing here is deprecated — the existing decision surface stays wire-stable through all of v11. See docs/AUTHZEN_MIGRATION_DRAFT.md.
use ;
let decision = client
.evaluate
.await?;
if !decision.allowed
for obligation in decision.mandatory_obligations
evaluate_all takes several preconditions of one operation and returns one decision: the entries combine to the least permissive outcome, so one denied entry denies the operation. An API returning a list would invite a caller to act on the entry it liked.
Known gotchas
A resolved attribute has three states, and Option carries two. Every attribute bag — subject.properties, action.properties, resource.properties, and context — holds Attribute<T> values, not Option<T>:
| meaning | wire | outcome | |
|---|---|---|---|
Attribute::known(v) |
the source answered with v |
the member, with its value | evaluated |
Attribute::absent() |
the source answered: there is no value | the MEMBER is omitted from the bag; the bag itself is still sent, so properties arrives as {} |
evaluated; a fact with no value changes nothing |
Attribute::unknown(why) |
the source could not answer | never reaches the wire | AuthZenEvaluationError::Unresolved, before the round trip |
Absent and unknown are not the same event. Dropping an unknown attribute from the request would obtain a decision that weighed every attribute except the one nobody could read — and report it as complete. That is the exact failure the server refuses on its side of the wire ("accepting it would report that it was considered when it was not"); Attribute is the same refusal on yours. Read a value with Attribute::fold, which does not compile until you have said what all three states mean; as_known() collapses two of them and is for logging.
A later write never overwrites an unresolved attribute. with_query and with_correlation decline a write over an Attribute::unknown, at both the bag and the leaf, so a caller that recorded "nobody could read the request body" and then wrote a recovered partial query does not end up sending a complete-looking envelope. The declined write is not silent: the surviving unknown refuses the envelope at its own pointer, carrying the reason. AttributeMap::insert is the ordinary map write and DOES replace — use record if you want the rule.
Only one refusal code is worth retrying. AuthZenEvaluationError::retryable() is the whole set in one place: a server refusal only when its code is evaluation_unavailable; a transport failure (timeout, connect, 5xx, 429); never an unreadable profile (retrying cannot make an older SDK able to read a newer one), never an unusable response, and never an unresolved attribute - that refusal is frozen inside the request, so every resend reproduces it. The OPERATION may succeed once the attribute resolves, but only after you build a new request.
This surface does not apply the client's RetryConfig: that executor is wired to the proxy path's request type, and retrying an authorization decision on your behalf is a policy decision this SDK does not make for you. Retry is yours, guided by retryable().
A refusal is not a denial. decision: false says the request was evaluated and denied. A refusal says it was never evaluated. They arrive as different types — Ok(decision) versus Err(Refused(..)) — so no caller branch can conflate an auth failure, a malformed envelope or an outage with a policy denial.
A local refusal names the same MEMBER the server would. The SDK validates before sending, and a local refusal carries the JSON Pointer the server would have sent for the same bytes - verified against a live server by runtime-e2e/authzen_evaluation. The CODE may be narrower on the server side, and that is not a defect in either: this client knows only that a required member is missing and says incomplete_evaluation, while the server additionally knows which values it can evaluate and narrows the same condition to unsupported_subject with a supported list. Branch on refusal.pointer for "which member"; read the code as the server's more specific reading when there is one.
allowed() requires the state, not just the boolean. It is true only when the collapsed boolean and the four-valued operational state both say ALLOW. A body where they disagree, one carrying no profile payload at all, or one written in a profile this build cannot read never becomes a decision — it becomes an error. There is no path that returns an allow the SDK could not fully read.
The types are generated, never hand-written. src/authzen/types_gen.rs is emitted from testdata/authzen-surface.json, the platform's canonical contract artifact, by tools/gen-authzen-types. Regenerate with cargo run -p axonflow-authzen-codegen; cargo test fails if the committed file is not what the artifact generates.
Advanced Features
Fail-Open Strategy
In Production mode, if the AxonFlow platform is unreachable, the SDK will "fail-open." This ensures your application remains available even if the governance layer is degraded.
Caching
The SDK includes a built-in async cache (powered by moka) with TTL support to reduce latency for redundant requests. Caching is automatically disabled for mutation operations like plan execution.
MCP & MAP Support
The Rust SDK provides full parity for Model Context Protocol (MCP) and Multi-Agent Planning (MAP):
- MCP: List, install, and query Model Context connectors with full policy enforcement.
- MAP: Generate and execute complex multi-agent plans programmatically.
Configuration
let config = AxonFlowConfig ;
Telemetry
The SDK includes a non-blocking background heartbeat that follows the AxonFlow telemetry contract: at most one ping per machine every 7 days to https://checkpoint.getaxonflow.com/v1/ping. Payload is classification-only — SDK version, OS, architecture, runtime version, deployment mode, an endpoint-type bucket (localhost / private_network / remote / unknown), and the deployment's org_id (the ORG_ID env value, or local-dev-org sentinel when unset). The raw URL is never sent.
AXONFLOW_TELEMETRY=off is the sole opt-out lever as of v0.2. There is no programmatic disable on the SDK config — the env-var-only pattern matches HashiCorp's CHECKPOINT_DISABLE, Docker, and Datadog Agent. Sandbox-mode clients (constructed via AxonFlowConfig::sandbox(...)) tag their pings with stream="sandbox" so analytics can distinguish dev/test usage from production heartbeat. DO_NOT_TRACK is intentionally not honored.
Scope of AXONFLOW_TELEMETRY=off
AXONFLOW_TELEMETRY=off disables the SDK heartbeat (version, OS, architecture, deployment org_id). On self-hosted and in-VPC deployments, that heartbeat is the only data the SDK sends to AxonFlow, so setting =off means we receive nothing. On Community SaaS (try.getaxonflow.com) the hosted service also processes operational data — registrations, audit logs, policy enforcement records, workflow state, plan data, and request-header metadata aggregated for usage analytics — as part of running the platform; that operational data flow is governed by the Privacy Policy, not by AXONFLOW_TELEMETRY.
See Telemetry Documentation for full details.
License
This project is licensed under the MIT License - see the LICENSE file for details.