Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Audit logs and data retention for AI

After this lesson: I can write audit records with no private content and delete them when their retention time ends.

60 min lesson4 commandsAI coding agent lab: use opencode to explain and fix code, and check its work

Who asked the assistant to do that

An audit record says who did what and when, without keeping the private text. It matters because, when something goes wrong, you need to reconstruct what happened without having copied everyone's messages.

A tool refuses a strange request on Tuesday. On Friday someone asks why. A record with a user id, a tool name, an outcome and a time answers that. A copy of the chat would answer it too, and become a liability.

By the end of this lesson

Note

I can write audit records with no private content and delete them when their retention time ends.

Key words

WordWhat it meansExample
Audit recorda note of who did what, when and with what result
Retentionhow long a record is kept
Purgeto delete the records that are past their time
Fake clocka time value your code sets, so a test controls time
Identifiera code that stands for a person without naming themu-17

The cells students fill in are left blank in this preview.

Record who, what and when

Write one small record for each thing the feature did. Keep to a fixed list of fields. Anything else, such as a prompt or a reply, is dropped before the record is saved.

Use an identifier for the person, not a name. Append records and never edit them, so the log can be trusted.

Worked example: one audit record

FieldExampleWhy it is there
ata timeWhen it happened
useru-17Who asked, without a name
actionread_noteWhat the feature did
outcomeallowedWhat the checks decided
tokens120How much it cost, in a count
(not kept)the prompt textIt may hold personal data

The cells students fill in are left blank in this preview.

Keep records only as long as useful

Every record has a reason, and the reason ends. Pick a retention time for each kind: a short one for chat text, a longer one for audit records. Then delete what is older.

Legal duties vary by country and by field, so check the ones that apply to you before you choose a number.

Write the audit script, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const DAY = 24 * 60 * 60 * 1000;' 'let now = Date.UTC(2026, 0, 1);' 'const allowed = [\"user\", \"action\", \"outcome\", \"tokens\"];' 'const log = [];' '' '// An audit record holds who, what and when. Never the text.' 'function record(fields) {' '  const clean = { at: now, id: log.length + 1 };' '  for (const key of allowed) {' '    if (key in fields) clean[key] = fields[key];' '  }' '  log.push(clean);' '  return clean;' '}' > audit.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the audit script, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'record({ user: \"u-17\", action: \"read_note\", outcome: \"allowed\", tokens: 120, prompt: \"Hi I am Amira\" });' 'now = now + 20 * DAY;' 'record({ user: \"u-18\", action: \"run_shell\", outcome: \"refused\", tokens: 0 });' 'now = now + 20 * DAY;' 'record({ user: \"u-17\", action: \"add_numbers\", outcome: \"allowed\", tokens: 60 });' 'console.log(\"Fields kept: \" + Object.keys(log[0]).join(\", \"));' 'console.log(\"Prompt stored: \" + (\"prompt\" in log[0]));' >> audit.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the audit script, part 3

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Retention: keep a record for 30 days, then delete it.' 'const KEEP = 30 * DAY;' 'function purge(list, at) {' '  return list.filter(function (item) { return at - item.at <= KEEP; });' '}' 'const left = purge(log, now);' 'console.log(\"Before: \" + log.length + \", after: \" + left.length);' 'console.log(\"Kept ids: \" + left.map(function (item) { return item.id; }).join(\", \"));' >> audit.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run the audit script

Command
docker exec -w /home/node/project agent node audit.js

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

What the script prints

LineWhat it shows
Fields kept: at, id, user, action, outcome, tokensThe prompt field was dropped
Prompt stored: falseNothing private reached the log
Before: 3, after: 2The first record is 40 days old and the limit is 30
Kept ids: 2, 3The two recent records survive

The cells students fill in are left blank in this preview.

A list stands in for real storage

The script keeps its log in an array and moves a fake clock by hand. A real system uses a database, with backups and access control, and a scheduled job that purges.

Backups matter too. A record deleted from the database is still in last month's backup unless the backup expires as well.

A common mistake

Warning

Many people think that more logging is always safer. A log that keeps every prompt is itself a store of personal data, and it must be protected and deleted like one. Log the fields you will need to answer a question, and no more.

About this lab and its source: AI coding agent lab: use opencode to explain and fix code, and check its work

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.