Skip to main content

Content Mediation

Everything an agent does outside its sandbox passes through AgentVisor — LLM requests, MCP tool calls, messages to other agents, writes to its store and checkpoints — and so does each reply. The Policy Engine decides whether each call is allowed. Content mediation looks at what the call carries, in both directions, and decides whether it may go through.

Content mediation is the capability. Content middleware is what you configure to carry it out: an ordered chain: of checks and the raw_request: / raw_response: hooks, all under content_middleware:. Some checks are built into AgentVisor; for the rest, you point AgentVisor at a service of your own and it calls that service for each decision. Each check permits the content, denies it, or — where a rewrite is possible — modifies it, and every decision is recorded.

Policy EngineContent mediation
Asksmay this call be made?may what it carries go through?
Looks atwho is calling, the operation, the resource, and with enrichment an MCP tool call's arguments or an A2A task's metadatathe payload: prompts, tool arguments, results, messages
Decides fromPolicyDomain rules you writecontent middleware you configure
Canallow or denypermit, deny, or (on HTTP) modify
Coversthe agent's calls, and starting a runboth directions: what goes out and what comes back
Try it

The Content Mediation tutorial module walks you through how to redact sensitive content from agent traffic and read the audit trail, hands-on, with no model-provider key.

What it is for​

  • Stop data leaving. Redact or refuse what must not leave, on whichever path it would take.
  • Shape LLM traffic. Restrict which models an agent may call, and lower the token ceiling it asked for.
  • Govern what comes back. Inspect a provider's reply, a tool's result or another agent's answer before the agent sees it.
  • Record token usage and decisions. Meter tokens per principal, and keep an audit record of every decision. The record says what was decided, not what the agent sent: AgentVisor does not copy a prompt, a tool argument or a reply into it.

Where it sits​

Content mediation sits on both directions of the sandbox boundary, at every step of a turn. Each turn of an interaction is its own run — a resume after an interrupt included — and each run invokes the agent once.

Step of a turnPolicy EngineContent mediation
1. The run's input arrives from the caller✓ when the run is created✓ when the run is created
2. The agent makes its calls✓ each call¹✓ each call
3. Replies come back to the agent—✓
4. The agent answers its caller—✓ each streamed chunk, and the result

¹ Except checkpoint saves and telemetry, which only content mediation checks.

1. The run's input arrives from the caller. What a caller asked the agent to do passes through content mediation before the agent is given it. The Policy Engine decided this caller may start a run; content mediation decides whether the agent is invoked with this input. A denial refuses the request, so the run never starts and nothing it would have done — no model call, no tool call — happens. The input is checked once, however many times the run is retried or resumed. It is also the only point that sees the request whole, which is what lets a record tie a tool call to what was asked.

2. The agent makes its calls. Every call out of the sandbox — to a model, a tool, another agent, its store — passes through these layers in order:

OrderLayerDecides
1Policy Enginewhether this principal may make this call at all
2Content mediationwhether what the call carries may go through, and in what form
3Credential brokeringwhich real secret replaces the agent's symbolic token (HTTP)
4Egress controlswhether the resolved address may be reached (HTTP)

A call the Policy Engine refuses never reaches content inspection. And on HTTP, content middleware runs before credentials are substituted, so no middleware — including a third-party service you configure — is handed a real API key. It sees the symbolic token instead (for example mav-tok-…).

3. Replies come back to the agent. A provider's response, a tool's result, another agent's answer, a value read back from the store — each passes through content mediation before the agent sees it. The call was authorized on the way out, so the Policy Engine is not consulted again; content mediation is the check on what came back.

4. The agent answers its caller. What the agent streams to its caller is inspected chunk by chunk as it goes, and the run's result is inspected before the caller is given it. A denial withholds the result — though not chunks already streamed; see Current limitations.

Two kinds of middleware​

The chainRaw hooks
Configured aschain: — an ordered list of entriesraw_request: and raw_response:
Seesan LLM request's conversational text, lifted out as spans; any other HTTP body, if text or JSON, as one span, or two for JSON (as sent, and with escapes decoded)the payload exactly as it was sent or received
Runs onevery HTTP request the agent makes (not a WebSocket upgrade)every hooked path
Canpermit or deny any HTTP request; edit an LLM request's text, and lower the generation parameters of any JSON request; on the reply, only record what it finds — it cannot block or change itpermit, deny; rewrite only an HTTP call or its reply
Written byAgentVisor's built-ins, or your own http checkeryour own service

The chain inspects the agent's HTTP calls, and understands LLM requests in particular. An extractor recognizes the provider's payload shape and hands each middleware the text in it, so one rule finds the same SSN in an OpenAI request and an Anthropic one without knowing which it is reading. Any other HTTP body it sees whole, as one span it can permit or deny but not rewrite, or two for a JSON body: as sent, and with escapes decoded. Off HTTP there is nothing for an extractor to model, so the chain does not run there at all.

A raw hook does not parse the payload, so it works on every path. It hands your service the bytes, or the RPC message, as they were sent or received — and on every path but HTTP it is the only middleware there is.

Use the chain for HTTP traffic, and raw hooks everywhere else.

What it reaches​

Content mediation hooks the agent's paths out of the sandbox, each in the direction that carries content:

FamilyOperationsNotes
HTTPagentvisor:http:request, agentvisor:http:upgradehttp:request is the only operation the chain runs on
MCPagentvisor:mcp:tool:*, agentvisor:mcp:resource:*arguments going out, results coming back
A2Aagentvisor:a2a:*tasks sent to other agents, and their answers
Run boundarycontent:run:input, content:run:outputthe run's own input and its result
Streamed outputcontent:stream:chunkthe agent's reply to its caller, a chunk at a time
Persistenceagentvisor:store:*, agentvisor:thread-kv:*, content:checkpoint:*the agent's hot loop — a checkpoint is saved on every graph step
Telemetrycontent:telemetry:spans, content:telemetry:logwhere a tracer can leak a prompt that was redacted elsewhere

An agentvisor: operation is one the Policy Engine also evaluates. A content: operation exists for content mediation alone: no PolicyDomain rule reaches it, and on those paths content middleware is the only governance there is. The Content Middleware Reference lists every operation and the phase it is hooked on.

Current limitations​

Content mediation does not yet cover the following:

  • What is sent over a WebSocket. Opening a WebSocket is hooked: the upgrade request (agentvisor:http:upgrade) is inspected, and a deny refuses the connection. An upgrade request that is not a GET with no body is refused. Once it is open, the messages exchanged over it are not inspected.

  • The terminal of an agentvisor exec session — what you type and what the tool prints. The tool's own outbound calls are inspected as usual.

  • AgentVisor's own logs. Only the agent's logs are hooked.

  • Streamed replies, as a whole. Each streamed chunk is inspected on its own and, if permitted, delivered at once. A deny ends the run, but chunks already delivered stay delivered. Content split across chunks — an SSN arriving a few characters at a time on a token-mode stream — matches no per-chunk check. The run's result is inspected only when the run ends, after every chunk has been delivered, so a deny there withholds the result but cannot take back what the caller already has.

    Judge the reply before it is streamed

    Hook the content where it enters the agent instead. raw_response with delivery: whole on the LLM call sees the complete model reply before the agent does, so a deny there means there is nothing to stream. A tool result the agent might repeat can be refused the same way, on the tool call's response.

Securing your middleware​

Your middleware sees the content itself, which on most paths means real data:

  • On an HTTP call (chain or raw hook), it sees the agent's symbolic credential, never the real one.
  • On every other path — MCP, A2A, persistence, telemetry, the run's input and result — only raw hooks run, and nothing is substituted. A store value, a tool result or a run's input reaches the hook verbatim, secrets included.

The built-in checks run inside AgentVisor and need no setup. If you bring your own service instead, AgentVisor calls it over HTTP, in one of two ways:

  • As an http chain entry — a check in the chain that is sent the text of each HTTP call as spans. AgentVisor can authenticate itself to your service and verify the service's certificate against a private CA, so the service can run anywhere.

  • As a raw hook — your service is sent the payload exactly as it was sent or received, on any hooked path, and returns a verdict.

    Run a raw hook on loopback

    Raw hooks have no tls: or header settings yet, so AgentVisor can neither prove who it is nor verify the service it calls, and the service's reply is trusted as-is. Run the service on loopback, or behind https with a publicly-trusted certificate. A private CA, mTLS and request authentication are not supported for raw hooks.

Every decision is recorded​

Each verdict becomes an OTLP log record, sent to the collector you configure under telemetry.audit and from there to your log store or SIEM. It is a log rather than a trace span because spans can be sampled, but every decision must be recorded. It says who, what and the decision. AgentVisor never copies message text or tool-argument values into it: a finding names a location, a type and a score. Two things it does record come from the agent: a finding's location names the tool-argument keys above the value, which the agent chose, and model_policy's refusal quotes the model the agent asked for, cut to 64 characters. What your own checker or raw hook returns, such as a deny reason, is recorded as you send it. Keep matched text out of it, and the audit stream will not become a copy of the data it governs.

What operators use it for:

  • Evidence. Every permit, deny and rewrite, with the principal, agent, run and the check that decided it — the record a compliance review asks for.
  • Investigation. trace_id, thread_id and run_id tie a decision to the run and the trace around it, so a refused call can be followed back to the request that caused it.
  • Tuning checks. Findings are recorded on permits as well as denies, so you can see what a rule matches before making it block. A check on the async lane records what it would have decided (enforced: false) without blocking anything — a way to trial a new rule on live traffic.
  • Spend. Token counts from token_meter are recorded per principal, which is how usage per person or per agent is answered.
  • Coverage. A payload that passed uninspected — a binary body, say — is recorded with the reason, so gaps show up in the stream rather than staying silent. A check that failed and was let through by on_error: allow is named in failed_open.

Every field is listed in the Content Middleware Reference.

Next steps​