Audit Logs by kirupa (https://www.kirupa.com/me/index.htm) | filed under AI Fundamentals (https://www.kirupa.com/ai_fundamentals/index.htm) Ships have kept logbooks for centuries, and the rules for an official log have long been simple and a bit strict. Write in ink, never pencil. Use a bound book with numbered pages, so nobody can slip a page out. If you make a mistake, strike it through with a single line so the original is still readable, and initial the correction. The point of all those rules was that months later, someone who wasn't on board could read the log and trust that it said what really happened: [Image: An open logbook with numbered pages and entries written in ink. One entry has been corrected with a single line through the old value and the writer's initials beside it, so the original stays readable.] (https://www.kirupa.com/ai_fundamentals/images/audit_logs_logbook.png) An audit log is that logbook for our software. It's the record of every consequential thing our system did, written so that people who weren't there (finance, security, a customer, an auditor) can trust it later. When the thing taking actions is an AI agent, audit logs get more important and a little more interesting. Let's see why. Onwards! A Question Two Months Later Let's go back to Copper Kettle, the kitchenware store from Least-Privilege Access for Tools (https://www.kirupa.com/ai_fundamentals/least_privilege_tools.htm). Our support agent can refund items, and any refund over $50 needs a person to approve it (https://www.kirupa.com/ai_fundamentals/human_approval_for_risky_actions.htm). Two months after the fact, Marcus from finance sends a message. Refund R-5512 paid out $640 for a stand mixer on order C-4410. Was that legitimate? Who asked for it, and who approved it? Our engineer starts looking, and finds three places that might have the answer: [Image: Marcus asks who approved refund R-5512. App logs only go back seven days and traces are sampled and kept for thirty days, so only the audit log, which records every action for years, can answer.] (https://www.kirupa.com/ai_fundamentals/images/audit_logs_drawers.png) The app logs only go back a week, and they're mostly noise anyway. The traces from Tracing and Prompt Logs (https://www.kirupa.com/ai_fundamentals/tracing_and_prompt_logs.htm) would have been a better bet, but we only keep 10% of successful traces, and only for 30 days, so whatever we had is long gone. Traces were designed for debugging, and for debugging, a sample is plenty. Marcus's question needs something that never skips a record. How Audit Logs Are Different At first glance, an audit log looks like any other log. The difference is in the promises it makes. A trace promises to help us understand how the system behaved. An audit log promises a complete and trustworthy record of what was done. Those two promises lead to very different rules: | Traces and prompt logs | Audit logs Main question | How did this request behave? | Who did what, and was it allowed? What's recorded | Every step, in detail, including full prompts | Each consequential action and decision, with references Sampling | Often sampled | Never sampled Can it be edited or deleted? | Usually, by the team that owns it | No. Append-only. How long it's kept | Days or weeks | As long as policy requires, often years Who reads it | Engineers | Security, finance, compliance, and sometimes customers Consequential is the key word. Reading the store's hours isn't worth an audit entry. Issuing a refund, sending an email to a customer, changing an account setting, exporting data, and having a tool call (https://www.kirupa.com/ai_fundamentals/tool_calling.htm) denied all are. What Goes in an Entry Here is the audit entry for refund R-5512: [Image: The audit entry for refund R-5512 records who was involved, what action was taken on which order and item, when it happened, the outcome, and why, including the approver's note and links to the conversation and the expired trace.] (https://www.kirupa.com/ai_fundamentals/images/audit_logs_entry.png) Each group of fields answers one of Marcus's questions. Who was involved, what action was taken on what, when it happened (using our server's clock, not a timestamp the model wrote), how it turned out, and why. The why fields mostly hold references. The approval note points to ticket T-8812, where the photo of the cracked bowl lives. The conversation and trace IDs point to the full story, if it's still around. Even though the trace has expired, the entry still holds the decision. Notice what isn't in the entry. There's no card number, no full transcript, and no system prompt. Those are either sensitive or huge, and an audit log that is kept for years is the last place we want a pile of personal data (the PII Redaction (https://www.kirupa.com/ai_fundamentals/pii_redaction.htm) article goes into this). Audit entries record the decision and point to the details. Who Did It? With Agents, It's a Chain In a traditional app, who did it is usually one person clicking one button. With an agent, there's a chain of actors, and an audit entry that records only one link in it can't answer Marcus's question: [Image: The refund passes through five actors: Jun asked for a refund, the support agent decided to call refund_item, the tool layer checked limits and required approval, Elena approved it, and the payments service paid 640 dollars.] (https://www.kirupa.com/ai_fundamentals/images/audit_logs_actors.png) Each link matters for a different reason. Jun is the person the action was taken on behalf of. The agent's name and version (and the model behind it) tell us which configuration made the decision, which is exactly what we'll want to know if one version turns out to be too generous with refunds. Elena is the human who said yes. If we record only the support agent refunded $640, we've lost the most important facts. There's one subtle trap here. The model can explain itself (I refunded this because the customer said the bowl was cracked), and that explanation is worth keeping as a hint. But it's only the model's claim. The evidence is what the model actually saw (the trace and the ticket) and what the people involved actually did. We should also log the actions that didn't happen. When the tool layer denies a call, like an attempt to refund an order that belongs to someone else, that denial goes in the audit log too. A cluster of denials is often the first sign that someone is probing our agent, or that a prompt injection (see Prompt Injection Defense (https://www.kirupa.com/ai_fundamentals/prompt_injection_defense.htm)) is trying to make it misbehave. Making It Trustworthy An audit log is only useful if people believe nobody has edited it behind their backs. Our ship's logbook used ink and numbered pages for that. The software version has three habits. The first is that the log is append-only. The code that writes entries can add new ones, and nothing in the application can update or delete them. The agent and the app get credentials that can only write, and the storage itself can be set to refuse changes for a retention period (many cloud storage services have a write once, read many or object-lock mode for exactly this). The second is that each entry is linked to the one before it. Every entry includes a hash of the previous entry, and its own hash covers that link. If someone changes an old entry, its hash changes, and the next entry's record of it no longer matches: [Image: Four chained audit entries. Someone edits entry 1042 to change 640 dollars to 64, which changes its hash from 3c7e to e519. Entry 1043 still stores 3c7e as the previous hash. The two highlighted values no longer match, so the tampering is visible.] (https://www.kirupa.com/ai_fundamentals/images/audit_logs_hash_chain.png) If this looks familiar, it's the same idea that powers Merkle trees, which we covered in Understanding Merkle Trees (https://www.kirupa.com/data_structures_algorithms/understanding_merkle_trees.htm). A change anywhere bubbles up into a hash that no longer matches. To make it even harder to cheat, teams periodically copy the latest hash somewhere separate, so rewriting the whole chain from the edit onward would also mean changing that separate copy. The third habit is that people can actually use it. An audit log that can't be searched is a very expensive diary. It needs to be indexed by actor, resource, action, and time, so questions like every refund Elena approved in October, everything the support agent did on behalf of Jun, or all denied tool calls yesterday take seconds to answer. Conclusion Marcus got his answer in about a minute. Refund R-5512 was requested in a conversation with Jun, proposed by version 3 of the support agent, approved by Elena at 14:02 with a note pointing to a photo of the cracked bowl, and paid by our payments service. The trace was long gone, but the audit log was designed to outlive it. That's the job of an audit log. Traces help us understand behavior, and they can be sampled and short-lived. Audit logs record every consequential action and decision, carry the whole chain of actors from the customer to the agent to the human approver, keep references instead of personal data, and are append-only and hash-chained so that nobody can rewrite history without it showing. Along with the rest of this part of the series, they help turn the AI did something weird into a question we can actually answer.