Audit & compliance · · 9 min read
What an agent audit row should carry
The fields a SIEM-grade record needs for an agent's autonomous actions, mapped to OCSF, with redaction, retention, and tamper-evidence considerations.
When a team first puts agents on production systems, the default pattern is to pipe application logs into the SIEM. The rows look like service-account requests because that's what they are: the agent runs under a service identity, calls a resource, gets a response. The SIEM has the action. The chain back to a person isn't in the row, and neither is the policy decision that allowed it.
The shape of the row matters more than the storage layer underneath it. A SIEM-grade record for an autonomous agent action needs a specific set of fields, mapped to an interchange schema, redacted at the boundary, retained on a tiered policy, and signed so it can be used as evidence later.
The minimum useful schema#
The fields a single row needs:
agent_id— which agent identity ran this. Distinct from the service account it borrowed credentials from. This is the durable identifier the security team queries when they want every action a particular agent has ever taken.operator_id— the human who launched the run. Empty for autonomous starts (scheduled triggers, sub-agent spawns); populated otherwise. The investigative path runs through this field: "show me everything Jane's runs did last week" is the question the row has to answer without a join.run_id— the unique id of the current agent run. Bounds the scope of the agent's permissions during this run.parent_run_id— if this row was emitted by a sub-agent, the run id of the parent. Sub-agents misbehave; the parent context is how you trace why.tool_or_resource— what was called.pg.writeis different frompg.read;github.repos.deleteis different fromgithub.repos.read. The distinction has to be in the row, not inferred from log text.arguments— the arguments the agent passed. Redacted for secret-bearing fields, kept verbatim for the rest. Redaction policy is part of the schema, not a post-hoc grep.policy_id— which policy version evaluated this action. Critical when the security team investigates an action months later; without it, they can't tell whether the action was allowed under the policy that was in force at the time.decision—allowed,allowed_with_approval,denied, orsuperseded.supersededcovers actions the agent attempted, the gate allowed, and a containment event later rolled back.outcome— what the called system returned: status code, error class, or success. The row records both the agent's intent and what actually happened.timestamp— when the action ran, not when the row was emitted. Distinguish these for clock-skew investigations.
Ten fields. The deliberate design choice is to denormalize so the row stands alone — no joins to identity, policy, or run tables to answer the basic questions.
What the row should let you answer#
Six questions a security analyst should be able to answer with a single query against the row:
- Which agent did this?
- On whose behalf?
- Against which resource?
- With what arguments?
- Did the policy gate allow it, and under which policy version?
- What was the outcome?
Joining out to other tables for any of these is a sign the schema needs another field. This is what bounds audit lag: the wall-clock time to answer "who approved this write" is however long the single query against this row takes, not an afternoon spent reconstructing it from three systems.
Mapping to OCSF#
The Open Cybersecurity Schema Framework (OCSF) is the cross-vendor standard for security event interchange. OCSF defines categories, event classes, and an attribute dictionary that lets SIEMs from different vendors consume the same events. Cloud-native security stacks have adopted it; AWS Security Lake is OCSF-native (docs).
There is not yet a dedicated event class for autonomous agent activity in OCSF; that work is in progress as of mid-2026. In the interim, agent actions fit naturally under existing classes: process activity (when the agent is calling a tool), authentication activity (for identity issuance), and API activity (for outbound calls). The fields above map onto the OCSF attribute dictionary as follows: actor.user.uid → operator, actor.process.uid → agent identity, target.resource.name → tool_or_resource, metadata.event_code → decision, with unmapped reserved for the policy_id and run-graph fields until a dedicated class lands.
A row written for OCSF compatibility can be ingested into any OCSF-aware SIEM without custom parsers. The discipline is to write the row once, in the richest schema you can support, then map fields outward toward whichever SIEM is consuming.
A sample row#
In JSON, an agent's github.repos.delete action against the evalops/proto-docs repository, allowed by policy version v2026.06, looks like this:
{
"timestamp": "2026-06-17T14:23:11.421Z",
"agent_id": "agent_4f2a_codeReview_v1",
"operator_id": "user_jhaas",
"run_id": "run_01HZK4M7",
"parent_run_id": null,
"tool_or_resource": "github.repos.delete",
"arguments": {
"owner": "evalops",
"repo": "proto-docs",
"token": "[REDACTED:credential]"
},
"policy_id": "policy_v2026.06.12",
"decision": "allowed_with_approval",
"approval_id": "approval_01HZK4M8",
"outcome": {
"status": 204,
"duration_ms": 312
}
}
The row contains everything the security analyst needs in one place. No join is required to answer the six questions above.
Two fields commonly missing in practice#
The two fields agent runtimes most often forget are policy_id and parent_run_id.
Without policy_id, the row can't tell you whether the agent's action was allowed under the policy that was in force at the time. Investigation case: a customer reports unauthorized access on May 1. The investigation runs on June 15. The policy was changed on May 12, partly in response to a different incident. Did the May 1 action fire under the old or new policy? Without the policy version on the row, the investigator has to reconstruct it from change logs and best-guess timestamps. Reconstructed-from-change-logs is not the same evidentiary standard as "on the row at write time." The investigation either delivers an inconclusive finding or escalates to forensic preservation, which means freezing systems that should still be serving traffic.
Without parent_run_id, a misbehaving sub-agent can't be traced to the parent run that spawned it. Agent stacks routinely fan out — a top-level run produces sub-agents to handle parallel work; sub-agents spawn their own. When one of those sub-agents commits an action that escapes the operator's intent, the only thread back is the run graph. Without it, the audit log records the misbehavior with no responsible parent.
Both fields are cheap to add at write time; backfilling either after the fact is nearly impossible.
Field-level redaction#
Some arguments must not appear in the audit row verbatim. Credentials, OAuth tokens, customer PII (in jurisdictions where PII is regulated independently of access logs), and any field the model risk team has classified as sensitive. The redaction has to happen at the boundary — in the runtime that emits the row — not in a SIEM-side post-processor.
Three redaction modes the row should support:
- Masked. Replace the value with a typed placeholder (
[REDACTED:credential],[REDACTED:pii_email]). The shape of the call is preserved; the secret is not. - Hashed. Replace the value with a stable hash. Useful when investigators need to confirm "this row touched the same row id as that row" without seeing the row id. Use a per-tenant pepper so hashes are not cross-tenant correlatable.
- Verbatim. The value is kept as-is, with the understanding that the SIEM's downstream access controls protect it. Default to verbatim only for arguments that have no sensitivity classification.
The choice between hashed and masked depends on whether investigators need to correlate across rows. Correlation requires hashing (with a per-tenant pepper); investigation that does not require correlation is better served by masking.
Three-tier retention#
Audit rows are not all worth the same per-byte storage cost over time. A practical retention policy ladders them:
- Hot tier, 30 to 90 days. Indexed, queryable on every field, supports real-time alerts. The SIEM's normal storage layer.
- Warm tier, 1 to 3 years. Less expensive storage, queryable for compliance audits and slower investigations. Object store with periodic indexing is common.
- Cold tier, 3 to 7 years (or longer where regulation requires). Write-once, immutable storage. Object store with object-lock, or a WORM-compliant archive. Read access is rare and authorized per-investigation.
The three-tier ladder costs about one to two orders of magnitude less than keeping everything in hot tier indefinitely, and it survives compliance audits that ask for evidence going back years. Most teams shipping agents to production discover this hierarchy only after a request from legal or a regulator forces them to pull rows from outside the SIEM's default retention window.
The reasoning trace question#
A common impulse is to log the agent's reasoning text — the chain of thought, the planning trace, the model's stated intent — into the audit row. This is mostly wrong, for three reasons.
First, the reasoning text is verbose. A single agent run can produce tens of thousands of tokens of internal reasoning. Indexed in a SIEM, that's storage and query cost that swamps the action rows themselves.
Second, the reasoning text is not falsifiable. The model says it intends to do X; what matters for the security team is what the model did. The audit row already records the action; the reasoning text duplicates the explanation surface without adding evidentiary weight.
Third, the reasoning text often contains content the agent ingested from customer data or third-party APIs. That content has its own privacy and licensing implications. Persisting it in the security audit log creates a much larger data-handling surface than the access decision itself required.
A useful alternative is to keep a structured reasoning record in a separate run-store, not in the SIEM-shaped audit log. The audit row references it by id; the reasoning store has its own retention, redaction, and access controls. Investigators who need the model's reasoning can reach for it; the SIEM doesn't carry it by default.
Tamper-evidence#
For the audit row to be useful as evidence — to a regulator, to a legal proceeding, or to an internal investigation — it has to be tamper-evident. The minimum bar is hash-chaining: each row carries a hash that incorporates the previous row's hash. An attacker that modifies an old row breaks the chain for every row after it. This is the same primitive that underlies blockchain audit logs and Sigstore's transparency log; it does not require a blockchain to deploy.
A higher bar is signing — each row is signed by the runtime that emitted it, with the signing key rotated through your KMS. Investigators verify rows independently; an attacker with database access cannot forge rows without the signing key.
For most enterprises shipping agents in 2026, hash-chaining is the right baseline. Signing is the right target if the use case is regulated — healthcare, financial services, public sector. The cost of hash-chaining is negligible at write time; retrofitting it later requires re-emitting history, which most teams will not do.
This is the schema Deixic writes. The runtime that emits it is on What you can do.