Skip to content

Step 7 · The audit log

What this step adds: the final rung — an append-only record of every decision the system takes part in.

JSONL (JSON Lines): a file with one complete JSON object per line. It’s the standard shape for audit logs for three reasons — you can append without rewriting the file, read it line by line without loading it all, and one corrupted line doesn’t destroy the rest.

Create step7.py. It’s step 6 plus a record at the end:

import ollama
import json
from datetime import datetime
def get_transaction(txn_id):
records = {"T-1002": {"amount": 4200, "country": "Nigeria", "card_present": False}}
return records[txn_id]
def decide(txn):
if txn["amount"] > 1000 and not txn["card_present"]:
return "FLAG"
return "ALLOW"
schema = {
"type": "function",
"function": {
"name": "get_transaction",
"description": "Look up a transaction's amount, country and card_present flag by its id.",
"parameters": {
"type": "object",
"properties": {"txn_id": {"type": "string"}},
"required": ["txn_id"],
},
},
}
messages = [
{"role": "system", "content": "You are a fraud-triage assistant. Explain briefly. Never block a card yourself; only a human can approve."},
{"role": "user", "content": "Review transaction T-1002."},
]
recommendation = "ALLOW"
txn = None
while True:
response = ollama.chat(model="qwen2.5:3b", messages=messages, tools=[schema])
messages.append(response.message)
if response.message.tool_calls:
for call in response.message.tool_calls:
txn = get_transaction(call.function.arguments["txn_id"])
recommendation = decide(txn)
messages.append({"role": "tool", "content": str(txn)})
else:
print("AGENT RECOMMENDS:", recommendation)
break
answer = input("\nApprove this recommendation? (yes/no) ")
action = "card blocked" if answer == "yes" and recommendation == "FLAG" else "none"
print("ACTION TAKEN:", action)
entry = {
"time": datetime.now().isoformat(timespec="seconds"),
"transaction": txn,
"recommendation": recommendation,
"human_answer": answer,
"action": action,
}
with open("audit_log.jsonl", "a") as f:
f.write(json.dumps(entry) + "\n")
print("Logged to audit_log.jsonl")
Terminal window
uv run step7.py

Answer the prompt, then look at what was written:

Terminal window
cat audit_log.jsonl
{"time": "2026-07-22T10:14:03", "transaction": {"amount": 4200, "country": "Nigeria", "card_present": false}, "recommendation": "FLAG", "human_answer": "yes", "action": "card blocked"}

Run it again and answer differently. A second line appends; the first is untouched.

One line answers every question an investigator would ask:

Field The question it answers
time When?
transaction What did the data say at decision time?
recommendation What did the system conclude?
human_answer What did the person say?
action What actually happened?

Note that transaction stores the data as it was, not a reference to it. If the record changes next week, the log still shows what the decision was based on. And the file is opened with "a" — append. An audit log you can overwrite is not an audit log; append-only is the property that makes it evidence.

The difference between “the AI flagged it” and “here is the data, the rule that fired, the recommendation, and who approved it at 10:14” is the difference between a system a regulator shuts down and one they sign off.

For production you’d add two fields: an authenticated approver identity (who is “yes”?) and a policy version, so the log survives the policy changing. Both are single lines.

Look at the ladder as a whole:

Step The system gained
1 A model — and proof it can’t know your data
2 A tool — facts the model can’t invent
3 Delegated choice — the model asks, you execute
4 The loop — an agent
5 A deterministic rule the model can’t vary
6 A human between recommendation and action
7 A record that survives scrutiny

Steps 1–4 are what most tutorials teach. Steps 5–7 are what make it deployable — and they were the easy steps: an if statement, an input(), a file append. Governance isn’t a framework you buy. It’s a design stance: the model judges, the code rules, a human decides, and everything is written down.

When it breaks — the failures that taught the most →

Then, when you want the production version of the same system: Part 2 · Eight steps to a production system →