The action log
Every agent action on a box is a line in one file: BOX_HOME/log.jsonl. Task creation, every step, every tool call and result, every approval and answer, every cost and every blocked connection. The log is the truth. Everything else, including the portal and the live tables, is a view of it.
Format
Section titled “Format”One JSON object per line. Each event has a line number, a time, a type, and the task it belongs to:
{"seq":214,"at":"2026-10-24T19:02:07.118Z","type":"task.step","task":"tk3m9qz1a2f","text":"Plan: list road bikes under $800 from the shop listing"}{"seq":215,"at":"2026-10-24T19:02:09.530Z","type":"tool.call","task":"tk3m9qz1a2f","key":"mcp.apify.run.4f1c2a.1","tool":"apify.run","input":{"actor":"apify/rag-web-browser"}}{"seq":216,"at":"2026-10-24T19:02:21.004Z","type":"tool.result","task":"tk3m9qz1a2f","key":"mcp.apify.run.4f1c2a.1","ok":true,"output":{"count":3},"cents":50}seq starts at 1 and counts lines. The portal shows it on every step, so anything on screen can be found in the file.
The event types:
| Type | When |
|---|---|
task.created |
A task is created, with its full spec and its user |
task.state |
A task changes state, with the layers it started on, an error or a summary |
task.step |
The worker reports progress: a plan, a step, a diff, a final message |
tool.call |
A tool is about to run, with its input |
tool.result |
The tool returned, with its output or error and what it spent |
approval.asked |
A task paused for an OK |
approval.answered |
Someone answered, and who |
cost |
Model spending or other spending, with the model and token counts |
net.blocked |
The proxy refused a connection |
note |
Anything an app wants on the record |
The exact fields of each are in log events.
Written before it counts
Section titled “Written before it counts”The core appends each event and calls fsync before it acts on it. A step the log calls done is on disk. Before it logs a worker's step, the core also flushes the task's own files to disk, so a step and the file changes it describes survive a power cut together.
A hard power cut can leave half a line at the end of the file. At startup the core finds the last complete line and truncates anything after it. It works in bytes, so a multi-byte character earlier in the file cannot make it cut a good line.
Rebuilt at every boot
Section titled “Rebuilt at every boot”The core holds no task database. At boot it reads the log from the first line and applies each event in order to rebuild every task and every approval. Then it resumes whatever was queued, running or waiting. This is why a box can lose power in the middle of work and carry on. See resume after power loss.
Undo, too, is decided from the log. A workspace's layers are the tasks whose last state is done, in the order they finished.
SpacetimeDB holds a live mirror of tasks and approvals for the portal and phone. If it loses data, the core refills it from the log.
Reading the log
Section titled “Reading the log”From outside the box:
GET /api/log?after=<seq>&task=<id>returns events after a line number, optionally for one task. Good for polling with a cursor.GET /api/log/stream?after=<seq>&task=<id>streams events as Server-Sent Events. Each event hasid: <seq>. Reconnect with?after=or theLast-Event-IDheader, and the box replays from there, then streams live.GET /api/tasks/<id>returns one task with its whole log.
From inside the box, an app uses a log reader. The core feeds it every event in order, from the first, and saves its position in BOX_HOME/cursors/ after each one. If the reader throws, the core retries that event after 5 seconds. A reader never misses an event, even across restarts. A crash between handling an event and saving the position can hand the reader that event again, so a reader should be safe to repeat. See apps and slots.
Sending it elsewhere
Section titled “Sending it elsewhere”Two apps follow the log out of the box:
- Jira turns every task into an issue with its steps, approvals, blocked attempts and diff. See connect Jira.
- Elastic ships every event to an Elasticsearch index and puts an agent on top that answers questions about it. See send the log to Elastic.
Both read with a log reader, so they catch up after a restart from where they stopped.
What not to put in it
Section titled “What not to put in it”Tool results go into the log, and through it to the portal, Jira and Elastic. A tool must never return a secret. The test suite checks that no vault value reaches a task's files, environment or log, and several apps check their own log entries the same way.
The log is append-only. Nothing in the core edits or deletes a line. It is root-only (0600), so tasks cannot read it.
Limits
Section titled “Limits”- The log grows without limit. This release has no rotation or archiving.
- Every boot reads the whole file. For a long-lived box, that time grows with the log.
- The log records a failed task's error as
worker exited with <code>. The cause is usually in the steps just before it.