Read the audit of a session
When you want to know what an agent did in a session, such as why it skipped a step or how often smllm refused an event, read the session’s audit. smllm writes one line for each call that drove the session: each event that the agent fired, each stop that the harness asked about, and the session’s start, with the outcome of each.
Steps
The steps use this config and the machine of Your first state machine:
.smllm/config.toml
[machines]
files = ["fix.smllm.yaml"]
.smllm/fix.smllm.yaml
id: fix
description: Fix a bug, from a test that reproduces it to the fix.
initial: TRIAGE
meta:
smllm: 1
instance:
kind: bug
ref: { param: bugId, pattern: "^BUG-[0-9]+$", description: "The bug's id, such as BUG-7." }
events:
reproduced:
description: Fire after a test reproduces the bug.
fixed:
description: Fire after the test passes.
states:
TRIAGE:
meta: { entryPoint: true }
entry: { type: prompt, params: { text: "Write a test that reproduces the bug, then fire reproduced with the bug's id." } }
on:
reproduced: { target: FIX, actions: setRef }
FIX:
entry: { type: prompt, params: { text: "Change the code until the test passes, then fire fixed." } }
on:
fixed: DONE
DONE:
type: final
entry: { type: prompt, params: { text: "Summarise the cause of the bug and the fix." } }
-
Let the agent work in a session. In this session, the agent enters the machine, fires
fixedtoo early, tries to stop, and then reproduces the bug:$ smllm fire --session sm-nqvfvw enter --param stateMachine=fix --json {"ok":true,"session":"sm-nqvfvw","location":{"machine":"fix","state":"TRIAGE","instance":"i-419405","ref":null},"text":"<smllm>\nsession sm-nqvfvw · fix › TRIAGE · bug i-419405\nArrived by: enter (new bug)\nParams: stateMachine = \"fix\"\n<instructions>\nWrite a test that reproduces the bug, then fire reproduced with the bug's id.\n</instructions>\n</smllm>\n"} $ smllm fire --session sm-nqvfvw fixed --json {"ok":false,"session":"sm-nqvfvw","location":{"machine":"fix","state":"TRIAGE","instance":"i-419405","ref":null},"text":"<smllm>\nsession sm-nqvfvw · fix › TRIAGE · bug i-419405\nerror: fixed is not offered here (offered: reproduced, yield, pause, unmatched)\nFire one event: smllm({ session: \"sm-nqvfvw\", event, params })\n<events>\n- reproduced: Fire after a test reproduces the bug.\n bugId (required, pattern: ^BUG-[0-9]+$): The bug's id, such as BUG-7.\n- yield: Stop for now and stay in TRIAGE.\n note (optional): What you are waiting for.\n- pause: Put bug i-419405 aside and return to idle.\n- unmatched: The request fits none of these; handle it from idle, then resume.\n</events>\n</smllm>\n"} [exit 1]When the agent tries to stop, Claude Code’s stop hook asks smllm, and smllm refuses the stop with this text:
<smllm> session sm-nqvfvw · fix › TRIAGE · bug i-419405 Fire one event: smllm({ session: "sm-nqvfvw", event, params }) <events> - reproduced: Fire after a test reproduces the bug. bugId (required, pattern: ^BUG-[0-9]+$): The bug's id, such as BUG-7. - yield: Stop for now and stay in TRIAGE. note (optional): What you are waiting for. - pause: Put bug i-419405 aside and return to idle. - unmatched: The request fits none of these; handle it from idle, then resume. </events> </smllm>$ smllm fire --session sm-nqvfvw reproduced --param bugId=BUG-7 --json {"ok":true,"session":"sm-nqvfvw","location":{"machine":"fix","state":"FIX","instance":"i-419405","ref":"BUG-7"},"text":"<smllm>\nsession sm-nqvfvw · fix › FIX · bug BUG-7\nArrived by: reproduced from TRIAGE\nParams: bugId = \"BUG-7\"\n<instructions>\nChange the code until the test passes, then fire fixed.\n</instructions>\n</smllm>\n"} -
Find the key of the session. Each
<smllm>header shows it, aftersession, andsmllm session listlists every session, the most recently active first, with its harness, where it is, when it was last active, and its folder. smllm prints the full path of each folder, and this guide shortens your home folder to~:$ smllm session list sm-nqvfvw claude fix i-419405 last active 2026-01-05 09:04 UTC ~/project -
Print the audit of the session:
$ smllm session show sm-nqvfvw --audit 2026-01-05 09:00 UTC agent bind idle accepted 2026-01-05 09:01 UTC agent fire enter fix i-419405 idle → TRIAGE accepted 2026-01-05 09:02 UTC agent fire fixed fix i-419405 TRIAGE refused: fixed is not offered here (offered: reproduced, yield, pause, unmatched) 2026-01-05 09:03 UTC agent stop fix i-419405 TRIAGE block 2026-01-05 09:04 UTC agent fire reproduced fix i-419405 TRIAGE → FIX acceptedEach line starts with the time of the call, in UTC, and its source:
agent, orhostfor an event that only the host that embeds smllm can fire. The call follows:bindwhen a harness binds its session to smllm,fireand its event, orstopwhen the harness asks whether the agent can stop. Then come the machine, the instance, and the state before the call. When the call moved the session,→and the state after the call follow.The outcome ends the line:
accepted,refusedwith the reason,blockfor a stop that smllm refused, orrunawayfor a stop that smllm allowed because the agent tried to stop again without firing an event. -
To filter the audit, or to keep it with a report, print it as JSON lines, one object for each call:
$ smllm session show sm-nqvfvw --audit --json {"at":1767603600000,"session":"sm-nqvfvw","source":"agent","call":"bind","event":null,"machine":null,"instance":null,"from":null,"to":null,"params":{},"offered":["enter"],"outcome":"accepted","reason":null,"trace":[],"binding":{"harness":"claude","hostSession":"cc-1","cwd":"~/project","configs":["~/project/.smllm/config.toml"]},"reply":"sha256:e32bba214c735287af4ff7a2590c135f3c5c643ebd26df19f906428b1dc4cc23"} {"at":1767603660000,"session":"sm-nqvfvw","source":"agent","call":"fire","event":"enter","machine":"fix","instance":"i-419405","from":null,"to":"TRIAGE","params":{"stateMachine":"fix"},"offered":[],"outcome":"accepted","reason":null,"trace":[],"binding":null,"reply":"sha256:ea7f7859c16348a748d815d7479e0a07af296be7572d634718dfc4ca8f1f1255"} {"at":1767603720000,"session":"sm-nqvfvw","source":"agent","call":"fire","event":"fixed","machine":"fix","instance":"i-419405","from":"TRIAGE","to":"TRIAGE","params":{},"offered":["reproduced","yield","pause","unmatched"],"outcome":"refused","reason":"fixed is not offered here (offered: reproduced, yield, pause, unmatched)","trace":[],"binding":null,"reply":"sha256:4f74289a359e206952a50a14a262ac1d163cbb48796beadda42644f890f8d96b"} {"at":1767603780000,"session":"sm-nqvfvw","source":"agent","call":"stop","event":null,"machine":"fix","instance":"i-419405","from":"TRIAGE","to":"TRIAGE","params":{},"offered":["reproduced","yield","pause","unmatched"],"outcome":"block","reason":null,"trace":[],"binding":null,"reply":"sha256:03ef41907cf05fd767716289eed9eb53feeb34c86ad415595a17dc74bfaf40cf"} {"at":1767603840000,"session":"sm-nqvfvw","source":"agent","call":"fire","event":"reproduced","machine":"fix","instance":"i-419405","from":"TRIAGE","to":"FIX","params":{"bugId":"BUG-7"},"offered":[],"outcome":"accepted","reason":null,"trace":[],"binding":null,"reply":"sha256:2b22dc5d256dfc28505f76bbfb423df2232dced33f0096a21fa4c86a30508aaa"}Each object has the fields of the line, and more.
atholds the time in Unix milliseconds, andparamsthe params of the call.offeredlists the events that the reply offered.tracerecords what the call ran, such as the result of each guard.The call that created the session also has
binding: the harness, its session, the folder, and the configs. That call is abind, or afire enterthat started a session without a key.This command lists only the calls that smllm didn’t accept:
smllm session show sm-nqvfvw --audit --json | jq -c 'select(.outcome != "accepted") | {call, event, from, outcome, reason}'For the session of step 1, it prints:
{"call":"fire","event":"fixed","from":"TRIAGE","outcome":"refused","reason":"fixed is not offered here (offered: reproduced, yield, pause, unmatched)"} {"call":"stop","event":null,"from":"TRIAGE","outcome":"block","reason":null}
Troubleshooting
- The audit has no line for a reply’s text: each line holds
reply, the hash of the reply that the agent was shown, assha256:and the hex of its SHA-256, not the text. To check that the agent was shown a text, compare the SHA-256 of the text withreply. - The audit takes space: smllm keeps the file of each session,
audit/KEY.jsonlin the folder of the sessions, until you remove it. On Linux and macOS the folder issmllm/in$XDG_STATE_HOME, which is~/.local/state/by default. On Windows it issmllm\in%APPDATA%.