Skip to main content

Content Middleware Reference

The operations content middleware reaches, the two wire contracts, and the audit record. For what content mediation is, see Content Mediation; for how to configure it, the Content Middleware guides, which cover chain entries and raw hooks; the section's own settings are in the Configuration Reference.

Hooked operations​

OperationRequestResponsePolicy-checked firstChainRewritable
agentvisor:http:request✓✓yes✓✓ (a reply: raw hook only)
agentvisor:http:upgrade✓yes
agentvisor:mcp:tool:list✓✓yes
agentvisor:mcp:tool:call✓✓yes
agentvisor:mcp:resource:list✓✓yes
agentvisor:mcp:resource:read✓✓yes
agentvisor:a2a:agent:list✓✓yes
agentvisor:a2a:agent:describe✓✓yes
agentvisor:a2a:task:send✓✓yes
agentvisor:a2a:task:get✓✓yes
agentvisor:a2a:task:cancel✓yes
agentvisor:a2a:task:stream✓✓ (per update)yes
agentvisor:store:create✓yes
agentvisor:store:update✓yes
agentvisor:store:read✓yes
agentvisor:store:search✓yes
agentvisor:thread-kv:write✓yes
agentvisor:thread-kv:read✓yes
content:run:input✓ (ingress)yes, at run creation
content:run:output✓no — the run was authorized when it was created
content:stream:chunk✓ (per chunk)no
content:telemetry:spans✓no
content:telemetry:log✓no
content:checkpoint:save✓no
content:checkpoint:write✓no
content:checkpoint:load✓no
content:checkpoint:list✓no
  • Request / Response — the phase on which the operation is hooked. raw_request runs on the request phase and raw_response on the response; a hook or chain entry scoped to an operation on a phase it is not hooked on refuses to start. phase is the direction of the RPC, so on content:run:input the request phase is content arriving at the agent.
  • Policy-checked first — whether the Policy Engine evaluates the call before content middleware sees it. An agentvisor: operation is one the Policy Engine also evaluates; a content: operation exists for content mediation alone, and no PolicyDomain rule reaches it.
  • Chain — whether chain entries run there. Elsewhere only raw hooks run.
  • Rewritable — whether a modify verdict is applied. Elsewhere it is ignored and logged.

Operation patterns in operations: and exclude_operations: accept a trailing * as a prefix glob, and must match at least one operation in this table on the hook's phase.

Checker contract​

What an http chain entry sends and expects. Versioned by the X-AgentVisor-Content-Protocol request header, currently 1. See Writing a Content Checker.

Request​

FieldTypeMeaning
operationstringalways agentvisor:http:request today
resourcestringthe destination's MRN
phasestringrequest or response
principalobject{"claims": {…}} — the subject only, unless widened by principal_claims; absent when principal_claims: []
thread_id, run_id, agent, agentvisor_idstringwhere the call came from
spansarray{role, text, ref, number} — the text to inspect; an empty list on the response phase, which is recorded but not enforced and reaches a checker only with observe_response: true. ref is a JSON Pointer, or key:<object pointer>#<n> for an object key, n counting the object's keys from 0, each once, in byte order after JSON decoding; a key cannot be edited. number is true on a span lifted from a JSON number, which cannot be edited either, and absent otherwise. ref is empty ("") on a span holding a whole body no extractor recognizes, or its text with escapes decoded; such a span is read-only. role is the provider's own, or empty where there is none
fieldsobjectwhat AgentVisor knows about the call: url and method on both phases; model, max_tokens, max_completion_tokens, route, path_model and body_unreadable on the request; model, request_model, extractor, usage and usage_absent on the response. See Checker fields

Checker fields​

What AgentVisor sends in the request's fields. It is kept apart from the top-level keys, so the top-level shape stays the same as keys are added.

keyphasewhat it is
url, methodboththe outbound URL and HTTP method
modelrequestthe model the request names, if any
max_tokens, max_completion_tokensrequestthe output limit the request asked for, under whichever name it used
routerequestthe extractor whose path the URL matched, e.g. openai_chat, on a recognized LLM API; absent elsewhere
path_modelrequestthe model the URL names: a Bedrock model ID or ARN, or an Azure deployment name
body_unreadablerequesttrue when the call is to a recognized LLM API but its body is not a JSON object, or names model, max_tokens or max_completion_tokens more than once, or in another letter case such as Model
modelresponsethe model the provider answered with
request_modelresponsethe model the request asked for
extractorresponsethe extractor that recognized the call, e.g. openai_chat
usage, usage_absentresponsethe provider's token counts, or why there are none

model and the output limits are read from top-level JSON fields, so they arrive even when no extractor recognized the request. The last three rows arrive only when one did.

Response​

FieldTypeMeaning
actionstringrequired — permit, deny or modify
reasonstringwhy; recorded
editsarray{ref, text} — replacement text for a span; honored on modify only
parameter_editsarray{ref, value} — a numeric generation parameter, at one of /max_tokens, /max_completion_tokens, /temperature, /top_p, /top_k, /n, /presence_penalty, /frequency_penalty. Honoured on modify only; value is required. A token limit is a whole number of at least 1 and is only ever lowered: the lowest edit wins, and none exceeds what the request asked for; on a request that set none, the edit adds it
findingsarray{ref, type, confidence} — recorded whatever the verdict
fieldsobjectyour annotations, recorded as checker_fields and not passed to later checks or the provider; every checker's fields share one record, a key from a checker listed later in the chain replacing one of the same name from a checker listed earlier, on either phase; keys AgentVisor sent in the request's fields are dropped, and past 4 KiB encoded the whole object is replaced by {"truncated": true, "bytes": N}

A non-2xx status, unparseable body or unrecognised action is an error, handled by the entry's on_error. Redirects are not followed: a 3xx is a failed call.

Raw hook contract​

What raw_request and raw_response send and expect. See Raw Content Hooks.

Request​

FieldTypeMeaning
operationstringone of the hooked operations
resourcestringthe MRN the call names
phasestringrequest or response
principalobject{"claims": {…}} — every claim in the caller's token. An http checker is sent only the subject by default; a raw hook always gets them all
thread_id, run_id, agent, agentvisor_idstringwhere the call came from
method, urlstringHTTP only; absent elsewhere
headersobjectHTTP only — name to list of values
statusnumberHTTP response only — the upstream's status code
batchobjectraw_response with delivery: batched only — {correlation_id, sequence, final}
bodystringbase64. On HTTP, the bytes as sent or received. For content:run:input and content:run:output, the run's input or output as JSON; for content:stream:chunk, {"chunk_type": …, "data": …}; elsewhere, the RPC message as protojson

Response​

FieldTypeMeaning
actionstringrequired — permit, deny or modify
reasonstringwhy; recorded whole. Where it ends a run (content:run:output, content:stream:chunk), the run's error keeps at most 4 KiB of it, marked as truncated
headersobjectreplacement headers; applied on a rewritable operation only. On a request, names are matched regardless of case, so authorization is the Authorization header that credential substitution reads; two spellings of one name are one header with both values
bodystringreplacement body, base64; applied on a rewritable operation only

Any status other than 200 is an error, handled by the hook's on_error. Redirects are not followed. A modify on a rewritable operation must carry headers, body or both. There is no field for the destination. The response may be at most four times the size of the request it answers, or 4 MiB, whichever is larger.

Service URLs​

The url of an http checker or a raw hook must be http or https with a host, and is checked at startup. Logs, errors and the audit record show only its scheme and host, never its credentials, path or query. Percent-encode any credentials in it: a URL with an @ after its host is refused, since that is how an unencoded password is misread as a host.

Audit record​

Each verdict is emitted as an OTLP log record with the body content middleware verdict and these attributes. Empty values are omitted.

AttributeMeaning
operation, resource, phasewhat was inspected; on HTTP, resource names the host and path
methodHTTP only — the request method. The URL's query string is never recorded
enforcedwhether this record's verdict was acted on. false on every record from an async entry, which nothing waits for, and on every response record where a chain entry ran, since the response phase only observes. It describes the chain's verdict, so a response record that also names your raw hook in evaluated is still false even though your hook's allow decided the reply
verdictpermit, deny or modify; absent when nothing was inspected
reasonthe verdict's reason, or the error
decided_bythe entry that denied; absent when AgentVisor itself refused, under on_unknown_format: deny or when a verdict's edits could not be applied. On a reply a raw_response hook refused, it names the hook and reason is the hook's; any findings and checker_fields come from response-phase entries that observed the reply, since a raw hook reports neither
modified_byeach entry whose edits were applied. Empty on a deny, since nothing was sent
raw_rewritten_bythe raw hook that replaced the payload. Kept on a deny, because the chain judged the replaced payload
evaluatedevery entry that ran, listed after your raw hook when it ran and let the payload through. An async entry is listed only on its own record, and an entry that abstained is not listed
failed_openeach entry or raw hook that failed and was let through by on_error: allow. It is listed in evaluated too, because it was called
spanshow many spans were inspected
max_spans_exceededhow many spans the request had, when that was more than max_spans, so it was handled by on_unknown_format; set on whatever record the request produced
findingsJSON array of {ref, type, confidence} — never the matched text
parameter/<name>a generation parameter that was changed, with its new value — e.g. parameter/max_tokens. Absent on a deny, since nothing was sent
usage.<name>token counts, one attribute per count. When more than one entry reports counts, or why there are none, the record keeps the answer of the entry listed first in the chain, on either phase
usage_absentwhy there are no counts, when a meter looked and found none, including a reply cut off before the provider reported them
checker_fieldsJSON object of what an http checker returned in fields, up to 4 KiB
not_inspectedwhy a payload was not examined. A payload that could not be rendered as JSON for a raw hook goes ahead uninspected, whatever on_error says
principalthe subject only
thread_id, run_id, agent, agentvisor_id, execution.id, trace_idcorrelation

A record has warning severity when an entry failed open, or when a verdict that was enforced denied; otherwise it has info severity.

A run's content:run:input record is written when the run is created, before any execution exists, so it carries no execution.id. Each attempt's content:run:output record carries that attempt's. Join a run's records on run_id. When the input is refused, no run is created, so that record's run_id, and a stateless run's thread_id, name nothing else.

AgentVisor never writes conversation content into an attribute. Text your checker or raw hook returns is recorded as sent, in reason, in findings and in checker_fields, so whatever it puts there reaches the audit stream.