Derive what a run actually did from its own tool calls.
A worker's report is free text, so an orchestrator reading it cannot
distinguish "the worker ran mix compile" from "the worker said it
compiled". Live runs produced exactly that failure: a deliverable claiming
"the app compiles cleanly with no new errors" for a run in which no build
command was ever executed.
These events are read off the message history rather than the prose, so the
claim becomes checkable — both by the model (the footer rides back with the
worker's report) and by deterministic rails (changed_files/1,
commands/1).
Three event kinds are recorded, in call order:
{:write, path}— a file the run mutated.{:command, cmd}— a shell command that ran and exited zero.{:failed_command, cmd}— one that ran and exited non-zero. Bash returns{:ok, …}whatever the exit code, so without this a worker could watch the suite go red and still satisfy a rail asking whether the change was exercised.
Order is preserved because it carries information a set cannot: "wrote a
test, ran it, then edited source" and "edited source, then added a test"
have the same files and commands but very different meanings — see
test_first?/1.
Summary
Functions
Distinct files the run mutated, in first-seen order.
Distinct commands the run executed, in first-seen order — including ones that exited non-zero, which still ran.
Ordered events derived from a run's messages.
Distinct commands that exited non-zero.
A one-line factual summary to append to a worker's report, or nil when the
worker neither changed nor ran anything (a pure read-only explorer).
Recover events from footers embedded in a transcript.
Whether a command runs a test suite.
Whether a path looks like a test rather than production code.
Whether a test file was written before the first source file.
Types
Functions
Distinct files the run mutated, in first-seen order.
Distinct commands the run executed, in first-seen order — including ones that exited non-zero, which still ran.
@spec events( [ExAthena.Messages.Message.t()], keyword() ) :: [event()]
Ordered events derived from a run's messages.
Options:
:mutating_tools— extra tool names (custom/MCP) to treat as writes. Their path is read from apath/file_pathargument when present.
Distinct commands that exited non-zero.
A rail asking "was this change exercised?" must not be satisfied by a suite that ran and went red.
Recover events from footers embedded in a transcript.
A tool cannot write into loop state, so the footer is the only channel by
which an orchestrator's rails learn what its workers did — the text is a
parsing contract, not merely display. Prose around the footers is ignored,
and an elided (+N more) remainder simply does not come back.
Whether a command runs a test suite.
A build is not a test: the failure this exists for compiled cleanly and raised on every page load, so "it compiles" must not satisfy a rail asking whether anything actually exercised the change.
Whether a path looks like a test rather than production code.
Either it lives under a test directory, or its basename carries a test affix. Deliberately ecosystem-spanning: the rails that use this run against whatever project the agent was pointed at, not just Elixir.
Whether a test file was written before the first source file.
This is the one question a set of events cannot answer, and the reason
events/1 preserves order: "wrote a test, ran it red, then implemented" and
"implemented, then bolted a test on" touch the same files and run the same
commands. Vacuously true when no source was written.