Picture the first hour of an investigation. A customer's records were deleted through an agent's database tool. The log has a timestamp, a tool name, an HTTP 200 and a model ID. It does not say which employee started the session, which credential the agent used, whether the statement was a DELETE or a SELECT, which policy allowed it, or whether a retrieved document told the agent to do it. Every one of those facts existed at decision time, and none was written down.
Agent telemetry fails this way because it is inherited from performance monitoring, which asks how long and how much. Security asks who, on whose authority, and why it was allowed. The schema below answers the second set of questions and is deliberately boring: flat fields, stable names, enumerated values, no free text where an enum will do.
The unit of record is a decision point: every model call, tool call, MCP request or agent-to-agent message a policy evaluated. One HTTP request can carry several tool calls, and a session summary cannot be joined to a specific action, so neither is the right grain. Three consumers read the same event. Detection needs indexed fields it can aggregate. Investigation needs linkage to rebuild a session hop by hop. Audit needs proof, months later, that a consequential action was authorized by a named rule or person.
Who caused the action, what it did, how it was decided, what context shaped it and what happened next. A missing band is a question an investigator cannot answer from the log.
Who: identity is five fields, not one
Collapsing layered identity into a single "user" field is the most common reason agent investigations stall. Record each layer separately, as identifiers rather than display names.
- User: the human principal the agent acts for, or an explicit marker that it is running autonomously.
- Agent: a stable agent ID plus a version or configuration hash, so you know which build acted.
- Runtime key: the credential's key ID or fingerprint, never its value. This is what you revoke, so it is what you must search by.
- Tenant: the organization, derived from the authenticated credential on the server. A tenant ID copied from the request body is a claim, not a fact.
- Session: the conversation or run that groups related actions. Without it you have a pile of calls instead of a story.
When agent A asks agent B to act, B's event should carry the ordered delegation chain and the originating user, so a series of handoffs cannot launder authority.
What: record the action, not just the tool name
A tool name says which function ran, not what it did. `run_sql` covers both a read and a table drop; `bash` covers both `ls` and `curl | sh`. Record three layers. Tool identity is the tool name, its server or connection, and for MCP a hash of the tool definition in effect. The normalized action is what the call does once its arguments are read, such as `sql.delete`, `git.push.force` or `message.send.external`; detections should key on it. The consequence tier is an ordinal for how bad the action is if wrong (read, reversible change, external effect, privileged, irreversible), which lets one rule cover "irreversible actions in production" instead of a list of tool names.
Arguments carry most of the risk and most of the sensitive data, so do not log them raw. Record their shape (names, types, lengths), the data classes detected in them, the destination for outbound calls, and a keyed hash of each value. An HMAC with a per-tenant secret answers "was the same recipient used in both calls" without storing the recipient, and resists the dictionary attack a plain SHA-256 of an email address invites.
Decision: the band most logs leave out
A record of an allowed action is not evidence of authorization unless it names what allowed it. This band is the difference between an activity log and an audit trail.
- Outcome: allow, flag (allowed and marked for review), ask (held for a person) or block. In monitor mode, record both the enforced outcome and what enforce mode would have done.
- Deciding rule and layer: the rule ID and version that produced the outcome, and where it lives (organization, agent, exception, built-in safeguard). Auditors ask whether something was allowed by default policy or by an exception someone granted.
- Policy snapshot: a hash of the full policy set evaluated, so the decision can be replayed against the exact rules in force.
- Signals: detector outputs that fed the decision, each with its source, so a false positive traces back to its detector.
- Approval: for asks, the approver's identity, decision, time and the scope of any remembered approval. An approval without an approver ID cannot be audited.
- Failure reason: if the enforcer could not decide and failed closed, say so instead of recording an ordinary block.
Context provenance, outcome and correlation
Indirect prompt injection is a provenance problem: the instruction behind the action came from content the user did not write. You do not need that content in the log. You need, for each input span in the model's window, its source type (user turn, system prompt, retrieved document, tool result, web page, another agent), a source identifier such as a document ID or host, a trust label, and a hash. Then "the agent emailed a file after reading an external page" is a query, not a forensic project.
The outcome band records upstream status or tool error type, latency, response size and data classes found in the response. Correlation ties events together: a W3C trace and span ID, the session ID, a parent event ID for the model call that proposed the tool call, a hop index, and the provider's response ID where one exists. That chain lets an investigator walk from a deleted record back to the user turn and the document that led to it.
Consider an illustrative finance-operations agent, ledger-bot, at a fictional logistics company. A clerk asks it to clean up duplicate vendor records; it proposes a SQL statement, and policy holds it for approval.
| Field | Example value | Why it is there |
|---|---|---|
| actor.user_id | idp|u_48213 (clerk) | The human principal; the person to interview. |
| actor.agent_id / version | ledger-bot / cfg 9f3a1c | Which agent and configuration acted. |
| actor.key_id | key_7Q2M (fingerprint only) | The credential to revoke; the value is never logged. |
| tenant_id | org_logistics_eu (from the key) | Scoping that a request body cannot spoof. |
| session_id / hop | sess_c41e / 7 | Rebuild the conversation in order. |
| tool / server | run_sql / finance-db | What was invoked, through which connection. |
| action.normalized / tier | sql.delete (CTE) / irreversible | What the call does and how bad a mistake would be. |
| args.shape / classes / hmac | query: string, 412 chars / none / h:5be0 | Evidence without the raw statement. |
| decision.outcome / mode | ask / enforce | What policy did, and whether it was enforced. |
| decision.rule / layer | org.sql-delete-asks v3 / organization | The authority for the outcome. |
| approval.by / at / scope | idp|u_10077 / 14:02:31Z / this call only | Who accepted the risk, and how broadly. |
| context.untrusted_spans | 1 tool_result (vendor-export.csv) | Whether untrusted content preceded the action. |
| outcome.status / latency_ms | 200 / 812 | What happened after approval. |
| trace_id / parent_event | 4bf92f35... / evt_model_6 | Correlation across hops and systems. |
Mapping to the OpenTelemetry GenAI conventions
Use OpenTelemetry where it defines a field and your own namespace only where it does not. The GenAI semantic conventions now live in their own repository and are marked Development, so pin the version you implement. They define an `execute_tool` span with `gen_ai.tool.name` required, `gen_ai.tool.call.id` when the provider supplies one, and `gen_ai.tool.call.arguments` and `gen_ai.tool.call.result` as opt-in because they may contain sensitive information [1]. Agent spans add `gen_ai.agent.id`, `gen_ai.agent.version` and `gen_ai.conversation.id` [2]; the MCP conventions add `mcp.method.name` and `mcp.session.id` [3].
Two details matter. The conventions say instrumentations should not capture instructions, inputs and outputs by default [1]; keep that default, because span attributes fan out to every observability backend. And the inference conventions say not to populate a conversation ID when none is available, and not to fall back to a new UUID, a trace ID or a hash of request content [5]. Security still needs a session, so assign one at the gateway in your own field and leave the GenAI attribute truthful.
| Schema concept | OpenTelemetry attribute | What security adds |
|---|---|---|
| Agent identity | gen_ai.agent.id, gen_ai.agent.version [2] | Delegation chain. |
| Tool identity | gen_ai.tool.name, gen_ai.tool.call.id [1] | Server identity and tool-definition hash. |
| Session | gen_ai.conversation.id, mcp.session.id [3][5] | A gateway-assigned session when the client sends none. |
| Arguments | gen_ai.tool.call.arguments, opt-in [1] | Shape, data classes and keyed hashes instead of values. |
| User, key, tenant | Not defined in the agent span conventions [2] | Principal, key fingerprint, server-derived tenant. |
| Normalized action, tier | Not defined | Action read from arguments; consequence ordinal. |
| Decision, rule, layer, approver | Not defined [2] | The whole decision band. |
| Context provenance | Not defined | Per-span source type, trust label and hash. |
The agent spans define no user identity, authorization outcome, policy or approval [2]. So a security event is a superset: an OpenTelemetry span with standard attributes plus a security namespace you own, written to the security pipeline and reduced before export to general observability tools.
Privacy: minimize, hash, never store secrets
A security log holding every prompt is a second copy of your most sensitive data with weaker access control than the original. OWASP's logging guidance lists what should usually not be recorded directly, including session identifiers (hash them if you must track sessions), access tokens, passwords, encryption keys, payment card data and sensitive personal data, which should be removed, masked, sanitized, hashed or encrypted [4]. Agents make them more likely: users paste credentials into chats.
- Minimize at the source. Detection rarely needs content; it needs classes, counts and hashes.
- Redact before write. Run secret and PII detection on argument values, error messages and retrieved titles before they reach the pipeline, not in a nightly job.
- Hash with a key. Use a per-tenant HMAC for values you correlate, and rotate it like any other secret.
- Never store secret values at any tier. A fingerprint is enough to match a leaked key to its events and revoke it.
- Isolate content capture. If a defined purpose requires raw prompts, keep them in a separate store with its own access list, retention and deletion path.
Detail decays with age: full metadata for detection, redacted arguments for investigation, and only decisions and approvals for audit. Secrets are never stored at any tier.
Tier retention by consumer; the durations are examples to adapt, not a standard. Hot storage (about 30 days) holds full metadata for detection and triage. Warm storage (about a year) keeps hashes and classes with no residual content, which covers investigations that start late. Cold storage, kept as long as audit policy requires, holds only decisions, approvals and identity. Telemetry retention and audit retention are different clocks.
Detections the schema enables, and mistakes that break them
Each detection below is a query over guaranteed fields. If a detection you want requires parsing free text, the schema is missing a field.
- Injection to action: an external-effect or irreversible action in a session shortly after an untrusted web or tool-result span entered context.
- Credential reuse: one key fingerprint seen with more than one agent ID, or from a new network origin.
- Rubber-stamping: an approver accepting asks faster than a person could read them, or accepting nearly every ask for one rule, which suggests the rule should change.
- Exception creep: a rising share of an agent's allows decided at the exception layer, or exceptions renewed back to back.
- Exfiltration shape: one argument hash sent to a new external destination from several sessions, or sensitive classes in a response followed by an outbound message.
The mistakes that break them are predictable. Logging full prompts "for debugging" gives the log every pasted secret and every customer record, then ships it to tools with broad access. Recording the outcome without the deciding rule and layer shows that something was allowed but never why. A missing session ID, or one only some components set, disconnects the injected document from the action it caused. Trusting a tenant field from the body records what the attacker claimed. Sampling security events drops exactly the one you need, and editing events in place on approval erases the original decision; append a new event that references the first.
How Intertrace records decisions
Intertrace's gateway emits a structured event for every decision on LLM, MCP, inference, service and agent-to-agent traffic: who acted, what the call did after action normalization read its arguments, the outcome, the rule and layer that decided it, and the evidence. Tenancy comes from the `itr_rt_*` runtime key, not the request body. Events export to your SIEM, the OpenTelemetry fan-out is metadata only, and discovery never stores key values, only masked forms and fingerprints. HIRO, the AI security analyst, reads these events and proposes changes a person approves.
What to do this quarter
- Pick one consequential agent and mark each field of the five bands as present, derivable or missing in its current events.
- Add the decision band first: outcome, deciding rule and version, layer, policy hash and approver. It is the cheapest to add and the most expensive to lack.
- Assign a session ID at your gateway or orchestrator for every run, and propagate trace context to tools and sub-agents.
- Replace raw argument logging with shape, data classes and per-tenant HMACs, and run secret detection ahead of the pipeline.
- Map fields to a pinned version of the GenAI conventions, keep security-only fields in your own namespace, and set hot, warm and cold retention separately.
- Add a test that fails if a canary secret appears in any tier, then run a tabletop where an investigator rebuilds one session from the log alone.