Skip to main content
The agent-feedback crate provides a cloneable Tower Layer and Service for Axum. Finite JSON object and HTML responses are instrumented in-place; responses without an exact bounded Content-Length — including streams — are left completely untouched. Telemetry is dispatched through a bounded Tokio mpsc channel and never blocks the product response.

Requirements

  • Rust 1.88 or later
  • Axum 0.8
  • Tower 0.5
  • A Tokio async runtime

Installation

1

Add the crate

2

Create a product key

Open the Agent Feedback dashboard, create a product, and copy the API key. Store it as AGENT_FEEDBACK_KEY in your environment.

Creating the layer

Call AgentFeedbackLayer::new with an Options value built using the builder API. Clone the layer freely — all clones share the same telemetry queue.

Options builder methods

Construct an Options value using Options::new(api_key) and chain the builder methods below. Only the API key is required.
impl Into<String>
required
Constructs a new Options value with the given API key. The key must match the pattern af_live_<keyId>_<secret>. AgentFeedbackLayer::new returns an error if the key format is invalid.
impl Into<String>
Override the Agent Feedback API endpoint. Trailing slashes are trimmed. Defaults to https://agent-feedback-api-production.up.railway.app.
impl IntoIterator<Item = impl Into<String>>
Glob-style path patterns that opt routes in. When empty, all routes are instrumented (subject to default and custom excludes). Supports * (single segment) and ** (any path). Example: .include(["/search", "/docs/**"]).
impl IntoIterator<Item = impl Into<String>>
Glob-style patterns to skip. The default exclusion list (/health, /healthz, /metrics, /favicon.ico, /robots.txt, /_agent-feedback/*, /api/v2/outcomes) is always applied. Patterns you provide here extend that list.
impl Fn(&Request<Body>) -> Option<String> + Send + Sync + 'static
Closure that extracts an opaque customer identifier from the request. Used to group interactions by account in the dashboard.
impl Fn(&Request<Body>) -> Option<String> + Send + Sync + 'static
Closure that extracts a session identifier from the request. Links a sequence of interactions for session-level analytics.
impl Fn(&Request<Body>) -> Option<String> + Send + Sync + 'static
Closure that returns a hint about the agent runtime. Used for runtime-level breakdown in the dashboard.

FeedbackMode

Set feedback_mode directly on the Options struct (it is a public field). The enum has three variants:

Stream detection

The layer reads the response body size hint from the http_body::Body trait before buffering. Responses without an exact bounded size — including streams — bypass instrumentation entirely and are forwarded untouched to the client. The body size cap is 1 MiB; responses larger than this limit are also forwarded without modification.

Graceful shutdown

Call feedback.shutdown().await at application shutdown. The method sends a shutdown signal to the Tokio telemetry worker and waits up to 2 seconds for in-flight events to flush.

Agent helper functions

The crate exports feedback_from_response and submit_product_outcome for building feedback-aware agent runtimes.

feedback_from_response

Extracts the Envelope from a response’s HeaderMap and body bytes. Checks the JSON body for _agentFeedback, then the HTML body for <script id="agent-feedback">, then the agent-feedback response header.

submit_product_outcome

Submits a compact outcome review to the URL embedded in the envelope. Validates the contract, enforces the allowed-origin allow-list, and returns a serde_json::Value with the API response.
submit_product_outcome returns a SubmitError::UntrustedOrigin error if the submit URL’s origin is not in the allowed list. Pass a non-empty slice only when submitting to a self-hosted endpoint.
AgentFeedbackLayer is Clone, so you can hold one value at the application level for shutdown and pass clones into the Tower layer stack without any additional reference-counting overhead.