Classic painting used as the article cover
← Back to blog

AGENT OPERATIONS

What Is an Agent Audit Trail?

NIST already specifies what an audit record must establish. Agents break exactly one of the six elements, and AWS and Google both ship the field that fixes it.

•Jul 31, 2026•Updated Oct 13, 2026•15 min
Audit TrailsAccountabilityAgent Operations

TL;DR

An agent audit trail is the record NIST already specified, with exactly one of its six elements broken. SP 800-53 control AU-3 says an audit record must establish what type of event occurred, when it occurred, where, the source, the outcome, and the "identity of any individuals, subjects, or objects/entities associated with the event." Five of those are easy. The sixth now has two correct answers at once.

AU-10, non-repudiation, names the case precisely: irrefutable evidence that "an individual (or process acting on behalf of an individual) has performed" an action. An agent is that process. A schema with one identity column cannot tell the agent apart from the person whose authority it borrowed.

The usual instinct is that more logging closes this. It doesn't. The two largest cloud audit logs solved the identity half years ago, in fields most teams never populate: CloudTrail's sourceIdentity and onBehalfOf, Cloud Audit Logs' serviceAccountDelegationInfo.

We are not arguing for a new logging product either. OCSF, now a Linux Foundation project, publishes an API Activity class with uid 6003 whose required attributes already include actor, api, src_endpoint and time. The schema exists.

Three things to do today: add the acting-user field, record the authorization decision on denied calls and not only on allowed ones, and check whether the data-access logs you assume are running are switched on. Google's are off by default.

Overview

An agent audit trail is a record of what an AI agent did. Not what it was configured to do, not what it was allowed to do: what it actually did, action by action, in the systems it touched.

Take a data-analytics agent that queries a customer warehouse on a Tuesday afternoon. Six months later someone asks whether that query was allowed. The warehouse log shows a connection from a shared service account and a SELECT, which is aact:audit-granularity/none-distinguishable. It does not name the analyst who asked, record that a scope check ran, or say what the check decided. The query is logged. The event is not.

That gap is unusually well specified, which is the useful part. One control says what an audit record must establish, a second covers the delegated case, and two cloud providers already ship the delegation fields. This post is about what makes a record an audit trail rather than a log, against those references. Designing the schema field by field is covered in audit logging best practices, and live telemetry in what is agent visibility.

What a Standard Requires

NIST SP 800-53 Revision 5 puts the definition in control AU-3, Content of Audit Records. An audit record has to establish six things: "What type of event occurred; When the event occurred; Where the event occurred; Source of the event; Outcome of the event; and Identity of any individuals, subjects, or objects/entities associated with the event."

Read that against an ordinary application log line and four of the six are usually present. Type, time, place, source. Outcome is patchy, because plenty of services log the request and not the result, and a record without an outcome cannot tell an attempt from an act. Identity is where agents break the control, and AU-3(1) is the escape hatch: "Generate audit records containing the following additional information".

AU-10, Non-repudiation, names the agent case without ever mentioning agents. It asks for "irrefutable evidence that an individual (or process acting on behalf of an individual) has performed" some organization-defined set of actions. The parenthesis is the whole subject here. Its first enhancement, AU-10(1), asks you to "bind the identity of the information producer with the information", which for an agent means binding two producers, because both are real: the process that issued the call and the person whose authority made it permissible.

A record that answers "who did this" with one name is not incomplete by accident. Where a process acts for a person, the honest answer has two parts, and a schema with one identity column forces you to throw one of them away.

AU-9 comes at it from the other direction: protect audit information "from unauthorized access, modification, and deletion", alert on any of the three, and at the High baseline implement "cryptographic mechanisms to protect the integrity of audit information and audit tools". A trail an administrator can quietly edit is evidence of nothing.

Beyond Application Logs

Most enterprises already have logs. Whether those logs can reconstruct an agent's behaviour is a different question, and the reason they usually can't has nothing to do with volume.

An application log is written from the perspective of the service handling the request. It knows the endpoint, the caller's credential, and a status code. Right granularity for debugging a microservice, wrong one for an autonomous actor, because the facts that matter about an agent's action are not properties of the service. They are properties of the decision: who it was for, what allowed it, what came of it.

MCP's own security guidance makes the point from the attacker's side. Under "Token Passthrough", the 2026-07-28 specification lists accountability among the harms: a server that forwards tokens it was not issued leaves the downstream resource server with "logs may show requests that appear to come from a different source with a different identity". Nothing about that entry looks wrong to its owner. It is confidently naming the wrong actor, which beats a gap for damage, because a gap announces itself during an investigation and a wrong name does not.

What It Captures

A useful entry ties identity, intent, authorization and outcome together for one action, which is aact:audit-granularity/per-action-with-principal-chain. The list below is AU-3's six elements with the AU-3(1) additions agents specifically need.

  • The agent: which agent initiated the action, and which version of it.
  • The acting user: the human the agent was acting for, if any, held separately from the agent's own identity rather than in place of it.
  • The action: what was attempted, named as the operation rather than as an HTTP verb.
  • The tool or system: the destination, whether a SaaS platform, an internal API, or a database.
  • The data: what was read or modified, at enough resolution to judge sensitivity and blast radius.
  • The authorization decision: whether the scope check and the action check passed, which one failed if the call was refused, and the specific permission that was evaluated.
  • The outcome: succeeded, failed, or denied, plus the reference the target system returned.
Audit storeWarehouseAgentAnalystAudit storeWarehouseAgentAnalystsix months laterrequest, under the analyst's identitycheck: scope = read-only, tables = anonymizedquery, carrying agent + user identityrows returnedwrite event: agent, user, decision, outcome, trace_idqueried by agent, user, or trace_id

Figure 1 — One action produces one connected event, not a scattering of facts across three systems. The event is what gets queried later, not the systems it touched.

Serialized, a single entry for a procurement agent might read like this:

json
{
  "timestamp": "2026-07-22T14:08:31Z",
  "agent": "procurement-assistant@v3.2",
  "acting_for_user": "j.okafor@company.com",
  "action": "create_purchase_order",
  "tool": "erp.purchasing.api",
  "data": { "vendor": "Northwind Supply", "amount_usd": 18400 },
  "authorization": { "permission": "po.create:under_20k", "granted": true },
  "outcome": "success",
  "reference": "PO-2026-04471",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}

Anyone reading that six months later sees not only that a purchase order was created but by which agent, for which requester, against which evaluated permission, to which result. The trace_id is a 32-hex-character W3C Trace Context trace ID, which costs nothing and is what joins this record to whatever the ERP wrote on its own side.

The Second Identity

The delegated-identity problem is neither new nor unsolved. Both large cloud audit logs model it, and the fields have existed long enough that the interesting question is why so few agent implementations fill them in.

AWS CloudTrail carries the acting identity in userIdentity. On a call made with temporary credentials from an assumed role, sessionContext.sessionIssuer describes the role and sessionContext.sourceIdentity "identifies the original user identity making the request, whether that user's identity is an IAM user, an IAM role, a user authenticated by using SAML-based federation, or a user authenticated by using OpenID Connect (OIDC)-compliant web identity federation". Through IAM Identity Center, userIdentity.type is IdentityCenterUser and an onBehalfOf block carries userId and identityStoreArn. Where a third party acts with delegated access, invokedByDelegate.accountId names the account.

Google Cloud Audit Logs splits the same information across authenticationInfo and authorizationInfo. principalEmail is the caller, serviceAccountDelegationInfo carries the delegation chain, and authorizationInfo records the permission checked plus a granted boolean, which is the closest thing in either provider to a first-class authorization-decision field.

The questionAWS CloudTrailGoogle Cloud Audit Logs
Which identity issued the calluserIdentity.arn, userIdentity.typeauthenticationInfo.principalEmail
On whose behalfsessionContext.sourceIdentity, onBehalfOf.userIdauthenticationInfo.serviceAccountDelegationInfo
What was attemptedeventName, requestParametersmethodName, resourceName
Was it allowederrorCode, errorMessageauthorizationInfo[].permission, .granted

Table 1 — The same two-identity question, answered by two providers' own field names.

Two things stand out. AWS needs configuration to produce the field that matters: sourceIdentity appears only after an administrator sets STS up to require it, so a default install logs the role and loses the person. And only Google models the authorization decision as data. On AWS you infer it from an errorCode, so a denial and a timeout are distinguishable only by reading message text.

delegates

call

allowed

denied

Person
j.okafor

Agent
procurement-assistant@v3.2

Authorization check
permission evaluated

ERP

no call made

Audit record
agent + acting user
+ decision + outcome

Agent (procurement-assistant@v3.2) -> Authorization check: permission evaluated |-- allowed --> ERP ------------
|-- denied --> no call made ----> Audit record: agent + acting user + decision + outcome Both branches write a record. The denied branch is the one usually missing. -->

Figure 2 — Both branches write a record. The denied branch is the one most schemas drop, and it is the one an investigator asks for first.

Borrow a Schema

The temptation here is to design a field list. That is the wrong first move, because a maintained schema already exists and a private one costs you every integration you will later want.

The Open Cybersecurity Schema Framework describes itself as "an extensible framework for developing schemas, along with a vendor-agnostic core security schema", hosted and governed by the Linux Foundation since 19 November 2024 under Apache 2.0. It sorts events into six categories, including Identity & Access Management as category 3 and Application Activity as category 6. Inside category 6 sits API Activity, class uid 6003, whose required attributes are activity_id, actor, api, category_uid, class_uid, metadata, severity_id, src_endpoint, time and type_uid. The actor object is "the user, process, or service that initiated the activity on the host", and it composes a Process and a User object rather than a single string, which is the shape the delegated case needs.

The argument is not elegance. OCSF records are already ingestible by the tools a security team runs, so an agent emitting class 6003 with the acting user in actor.user inherits search, correlation and retention it did not have to build. Where a field is genuinely missing, extend it. That is what AU-3(1) and OCSF's extension model are both for.

Where the Record Goes Quiet

Four failure modes turn a healthy-looking trail into one that cannot answer a question. Each is a default rather than a bug, which is why they survive reviews.

The logs were never switched on. Google's documentation is blunt: "Except for BigQuery, Data Access audit logs are disabled by default." Admin Activity logs are always written, so a console check looks reassuring while the reads and writes an agent actually performs go unrecorded. The symptom is a trail complete for configuration changes and empty for data.

Only the successful path is written. A denied call is often the most informative entry in the store, because it is evidence a boundary held and a burst of them is evidence of something trying. If the record is written by the code that handles a response, the denial path returns earlier and writes nothing.

The permission recorded is too coarse to mean anything. MCP's security guidance lists this under scope minimisation as a direct cost: "Audit noise: single omnibus scope masks user intent per operation." An entry saying the call was permitted by files:* tells a reviewer nothing the absence of an error would not have. The same page tells servers to "log elevation events (scope requested, granted subset) with correlation IDs", which is worth lifting verbatim into a schema.

The identity is present but wrong. The token-passthrough case above, and the only one of the four you cannot find by auditing your own records, because nothing about the entry looks unusual. The defence is upstream: MCP states that servers "MUST NOT accept any tokens that were not explicitly issued for the MCP server".

Integrity and Retention

Two properties decide whether a trail is still evidence when someone finally reads it: whether it can be altered, and whether it still exists.

AU-9(3) asks for cryptographic protection of audit integrity without prescribing a mechanism. CloudTrail's log file integrity validation is a readable implementation of one. It uses "SHA-256 for hashing and SHA-256 with RSA for digital signing"; every hour CloudTrail writes a digest file referencing the previous hour's log files with a hash of each, signs it, and stores digests under a separate prefix so the two can carry different access rules. The detail that makes it a chain rather than a pile: "Each digest file also contains the digital signature of the previous digest file if one exists." That is what lets you assert not only that a file is unmodified but that none was deleted.

Retention diverges by jurisdiction and by system, so the numbers are worth keeping in one place.

ObligationWhat it requiresWhere it comes from
EU AI Act Article 12Automatic recording of events "over the lifetime of the system"; for Annex III point 1(a) systems, the start and end time of each use, the reference database, the matching input data and the persons verifying resultsArticle 12
EU AI Act Article 26(6)Deployer keeps logs under its control "for a period appropriate to the intended purpose ... of at least six months"Article 26
HIPAA 45 CFR 164.316(b)(2)(i)Retain required documentation "for 6 years from the date of its creation or the date when it last was in effect, whichever is later"45 CFR 164.316
Google Cloud _Required bucket400 days, not configurableCloud Logging quotas
Google Cloud _Default bucket30 days by default, custom retention configurable from 1 to 3,650 daysCloud Logging quotas

Table 2 — Retention floors and platform defaults. The last two rows are the ones that quietly decide the others.

Read the bottom two rows against the top three. A deployer under Article 26(6) needs six months. A default Google project keeps data-access logs, once enabled, for thirty days. Nothing warns you about the arithmetic, and it surfaces only when a request arrives for a window that no longer exists.

The AI Act's high-risk obligations do not bite yet. The Digital Omnibus moved them to 2 December 2027 for Annex III systems and 2 August 2028 for Annex I. Material still citing 2 August 2026 is out of date.

In Practice

Consider an IT-operations agent doing routine remediation across a fleet. One morning a batch of production hosts is found degraded and several teams are pointing at the agent. With a usable trail the on-call engineer pulls its activity for the window in minutes: twelve service restarts under a scoped remediation permission, each matched to a health-check failure, one retried after a failure, and nothing at all against the three hosts that actually degraded. Those were changed by a deployment pipeline. The investigation closes on evidence.

Without one, the team knows only that the agent's permissions allowed it. Suspicion lingers, the agent is paused as a precaution, and a working automation sits idle for a week. Three traits separate the two versions: a record for every action, denied and failed included; the context at the moment of the action rather than a pointer to state that may have changed; and queryable by agent, acting user, tool and time, because a trail nobody can search is storage.

Where Fabriq Fits

Agentic Fabriq holds credentials per user in a vault and attaches them server-side at the moment of the call, so the agent never handles the secret. Every call carries two identities, the agent and the acting user, and produces an audit record, which is the AU-10 shape rather than a single-identity log. An agent's effective tool list is the intersection of the user's scopes and the agent's own, so the permission recorded is the one actually evaluated rather than the widest either party holds. Records are queryable in the console with filters; export is API-only, as NDJSON.

What it does not do is reach into the provider on your behalf. Clearing a vault copy of a credential is not the same act as revoking a grant upstream, so the call to the provider's revocation endpoint remains yours to make, and the audit record should say which of the two happened.

Frequently Asked Questions

What is an agent audit trail? A structured, queryable record of each action an agent took, carrying the agent's identity, the person it acted for, the permission evaluated, and the outcome. AU-3 defines the minimum: type, time, place, source, outcome, and identity of the parties involved.

What is the difference between an audit trail and a log? A log records that a system event happened. An audit trail records a decision, written to be read by someone who was not present, which is why outcome and identity are mandatory. Volume is not the difference.

What fields does an agent audit record need? AU-3's six elements, with identity split into the agent and the acting user, plus the evaluated permission and its result. For a list someone else maintains, OCSF's API Activity class (uid 6003) requires activity_id, actor, api, metadata, severity_id, src_endpoint, time and the two uid fields.

Should prompts and model outputs go in the audit trail? Usually not verbatim. AU-3(3) tells you to "limit personally identifiable information contained in audit records to the following elements identified in the privacy risk assessment", and prompt text is the least bounded field an agent produces. Store a reference and a hash, and keep the content under its own access rules.

How long do agent audit logs have to be kept? It depends on the system, not the agent. A deployer of a high-risk AI system keeps logs at least six months under EU AI Act Article 26(6), and those duties start 2 December 2027 for Annex III and 2 August 2028 for Annex I. HIPAA documentation runs six years under 45 CFR 164.316(b)(2)(i). Check the platform default against whichever applies: Google's _Default log bucket keeps 30 days.

Do denied actions need to be logged? Yes, and they are the entries most often missing. A denial is evidence that a check ran and held, which is the only way to show a control works rather than assert it exists. Record the permission evaluated and the reason, not just a boolean.

How do we prove the trail was not tampered with? Hash each record or batch, chain each digest to the previous one, and sign them with a key the log's writers cannot use. CloudTrail's digest files are a working example, and the chain is what turns "we see no edits" into "an edit would be detectable".

Trust Requires Evidence

Most of what makes an agent audit trail is not new. AU-3 lists what a record must establish, AU-10 covers a process acting for a person, AU-9 asks you to protect the result, and OCSF publishes a class whose actor object holds more than one party.

What is new is that the identity field has two answers, and every layer below the agent was built to record one. That is the whole gap. It closes with a column and the discipline to write a record on the denied path as well as the allowed one.

The test we would apply before trusting a trail: pick an action from last quarter and see whether the record alone names the agent, the person, the permission evaluated, and the result. If any of the four needs a second system or someone's memory, it is a log.

For enterprise AI, trust requires evidence. The audit trail is where that evidence lives.

Sources

All URLs read 2026-09-29.