Security Engineering

Auditing Agent Tool Calls: Logging That Survives an Incident

David Guzenburg/ / 8 min read

Every agent incident ends with the same question — what did it actually do? Without a log the honest answer is that you cannot find out.

audit loggingdetectionincident responseobservability

You cannot investigate what you did not record

Most incidents involving agents are discovered late and indirectly — a credential appears somewhere it should not, a commit contains something nobody remembers writing, a bill spikes. The question that follows is always the same: what did the agent actually do?

Without logging, the honest answer is that you do not know and cannot find out. Transcripts, if retained at all, are usually local, unstructured, and hold the conversation rather than the actions.

What to record

Six fields cover most investigative needs.

FieldWhy
Timestamp, UTCCorrelation with other systems
Session IDGroups calls; establishes what else was in that context
PrincipalWhich human or service account the session ran as
Tool name and serverWhat was invoked, and from which server
ArgumentsThe part that answers "what did it actually ask for"
Outcome and sizeSuccess or failure, and how much came back
{"ts":"2026-07-13T09:14:02Z","session":"s_8f2a","principal":"svc-agent-ci",
 "server":"github","tool":"create_pull_request",
 "args":{"repo":"acme/api","base":"main","head":"fix/4471"},
 "result":"ok","bytes_out":1204}
{"ts":"2026-07-13T09:13:41Z","session":"s_8f2a","principal":"svc-agent-ci",
 "server":"filesystem","tool":"read_file",
 "args":{"path":"src/auth/session.py"},
 "result":"ok","bytes_out":8231}

One JSON object per line, appended. Nothing sophisticated is required, and the format matters less than the discipline of writing it somewhere durable.

Where the log lives

If the agent can write to its own audit log, the log is evidence of nothing. Ship it off the machine — to a logging service, a separate host, an append-only store. A log the subject can edit is a log you cannot rely on in exactly the situation you need it.

Arguments are the interesting field

Tool names alone tell you little. read_file appearing four hundred times in a session is normal. read_file on ~/.aws/credentials is not, and only the arguments distinguish them.

This creates a tension. Arguments are also where secrets appear, so a log capturing them verbatim becomes a secondary store of sensitive data. Two ways to resolve it, and you can use both:

What to alert on

Logs nobody reads are archaeology, not detection. Four rules cover a lot of ground and produce few false positives:

RuleWhat it catches
Access to known credential pathsReads of ~/.ssh, ~/.aws, .env, key files
Outbound to a non-allow-listed hostExfiltration attempts, and misconfiguration
Irreversible action from a tainted sessionThe workflow-splitting rule, enforced
Tool called that this session has never usedSudden capability changes mid-session

The third requires that you record whether a session is tainted, which means recording when untrusted content entered. That is one extra boolean and it converts a policy people are meant to follow into one you can verify.

Reconstructing a session afterwards

The test of a logging setup is whether you can answer a specific question weeks later. Take a realistic one: a credential appeared in a public gist on the 14th — did an agent touch it?

# 1. Any session that read a file matching credential patterns
jq -r 'select(.tool=="read_file")
       | select(.args.path | test("\.env|credentials|\.pem|id_rsa"))
       | [.ts, .session, .principal, .args.path] | @tsv' agent-audit.jsonl

# 2. For a suspect session, everything it did, in order
jq -r 'select(.session=="s_8f2a")
       | [.ts, .server, .tool, (.args|tostring)] | @tsv' agent-audit.jsonl

# 3. Did that session make outbound calls after the read?
jq -r 'select(.session=="s_8f2a")
       | select(.server=="http" or .server=="fetch")
       | [.ts, .tool, .args.url] | @tsv' agent-audit.jsonl

Three queries, and you either have an answer or you have ruled agents out — which is itself valuable, because it redirects the investigation somewhere more useful instead of leaving a permanent maybe.

Worth rehearsing this once, on a session you know the history of, before you need it. Almost every logging setup has a gap that only becomes obvious when someone tries to answer a real question with it.

Sessions, not just calls

A record per tool call answers "what happened". A record per session answers "under what conditions", which is often the more useful question.

{"session":"s_8f2a","started":"2026-07-13T09:02:11Z",
 "ended":"2026-07-13T09:41:52Z","principal":"svc-agent-ci",
 "model":"provider/model-name-v4","repo":"acme/api","branch":"fix/4471",
 "servers":["github","filesystem","postgres-ro"],
 "tainted":true,"taint_source":"github.get_issue#4471",
 "calls":68,"tokens_in":184200,"tokens_out":12400}

Two fields here are worth the whole record. tainted and taint_source turn the workflow-splitting policy from something people are meant to remember into something you can query: show me every session that was tainted and then performed a write. That query either returns nothing, or it returns your next conversation.

servers is the other. Aggregated over a month it tells you which connected servers are never used — the cheapest possible input to shrinking your attack surface, since disconnecting an unused server costs nothing and removes both its capabilities and its context overhead.

Retention

Long enough to be useful, short enough not to become a liability. Ninety days covers most investigations, which typically begin days or weeks after the fact. Longer retention for the metadata and shorter for the arguments is a reasonable split — the metadata is small and rarely sensitive, the arguments are the opposite.

A shorter window than ninety days is a false economy. The gap between an agent doing something odd and anyone noticing is measured in weeks, not hours.

Making the log useful before an incident

The best argument for building this is not incident response. It is that the log answers ordinary questions nobody can currently answer.

Those questions get asked in ordinary engineering conversations, and having data rather than impressions is worth the build on its own. The security benefit arrives free, on the day you need it, which is the only day it matters.

Takeaway

Log six fields per tool call, ship them somewhere the agent cannot write, redact secrets from arguments on the way in, and alert on four specific patterns. Build it for the operational questions it answers on ordinary days — the incident-response value is the part you hope never to use.

Keep reading
Security Engineering

AI Gateways and Data Retention: Enforcing Policy in the Request Path

What zero data retention actually guarantees, why a gateway turns per-developer configuration into a real control, routing by data classification, and the limits of redaction.

Security Engineering

When Read-Only Isn’t: The DSEWiki Agent Message Board

Thousands of OpenAI agents wrote 18,000 pages to a German developer wiki using read-only tools, trading eval answers and sandbox bypasses. What DSEWiki shows about egress boundaries.

Joby S4 vs Archer Midnight

Six Tilt Propellers or Twelve Engines: Two Ways to Build an Air Taxi

How Joby's six all-tilting propellers and Archer Midnight's twelve-engine, 12-tilt-6 layout change hover, cruise, drag, redundancy and certification.

Walmart Technology

Walmart's Store Network Is Its Most Important Supply Chain Technology

How Walmart combines automated distribution centers, store-based fulfillment and department-ready freight—and how that differs from Amazon's network.

All security engineering articles  ·  Every article