How it works

A short tour of the append-only log behind every thread.

You don't need to know any of this to build with threads. It explains why the guarantees on other pages hold.

A thread is a log

Every conversation with an agent is a thread. A thread is an ordered list of events, and events are only ever appended, never edited. A run of a small agent looks like this:

thread_started       the agent's instructions, tools and model settings
user_input           "What is the weather in Paris?"
model_request        the exact request the model was sent
model_response       a call to get_weather
tool_call            get_weather {"city": "Paris"}
permission_decision  allowed: the tool is read-only
tool_result          "It is sunny in Paris."
model_request
model_response       "It is sunny in Paris."
turn_completed       end_turn

Everything else is computed from this list: the transcript the model sees next, the pending approvals, the todo list, the result of run(). Nothing is kept on the side, so the log is the complete story of what happened.

Stored in SQLite

sqlite(".threads") is a folder with threads.db and an artifacts/ folder for large outputs. sqlite(":memory:") is a throwaway store for tests.

Tamper-evident

Each event carries the hash of the one before it, so a changed or missing line is detected when the log is read.

What the log gives you

Branches

A thread starts with one branch. Each fork adds a branch that shares the history up to the fork point and then goes its own way. Handles like Thread always point at one branch.

main    ── input ── model ── tool ── model ── done

fork                          └── model ── tool ── done

Where the pieces run

PieceWhere it runs
The agent loop, your tools, hooksYour process, as a plain library
Built-in shell and file toolsThe sandbox, with no internet by default and no credentials
Channels, schedules, the HTTP APIThe optional host, started with threads dev or threads start

Learn more

Edit on GitHub

On this page