Threads AI

CLI

Serve a host and inspect, move and clean up threads from the command line.

The threads CLI is a thin wrapper over library calls. Both languages ship the same commands, and both read the same store format, so a store written by TypeScript can be inspected with the Python CLI and the reverse.

threads dev [module] [--port N]           serve a host locally; prints each channel's webhook URL
threads start [module] [--port N]         serve a host in production
threads timeline <thread_id> [--branch B] print a branch's steps as JSON lines
threads export <branch_id>                write a branch's log (JSONL) to stdout
threads import <file>                     verify and store an export
threads repair <branch_id>                make an imported branch with a torn tail runnable
threads delete <thread_id> | --tenant T   delete a thread, or every thread of a tenant
threads gc [module] [--grace-days N]      release leftover sandboxes, remove unreferenced artifacts
threads eval [--agent M] [--live] ...     run saved cases: free checks, drift, a judged live run

global options: --store <dir> (default .threads), --tenant <id> (default local)

Running it from source

Packages are not published yet, so run the CLI from your clone. See Installation.

# from your project directory
bun /path/to/threads/typescript/packages/cli/src/main.ts dev

The examples below write threads for either.

Serving: dev and start

Both load your host module, call ready(), and serve it.

TypeScriptPython
Default modulethreads.config.tsapp (the module app.py)
What the module providesexport default host({...})exactly one host(...) at module level, or name it: module:attribute, path/to/file.py[:attribute]
ServerBun.serveuvicorn (host extra)
dev port87878787, on 127.0.0.1
start port8787$PORT, else 8000, on 0.0.0.0
threads dev
# threads: dev listening on http://localhost:8787
#   slack: http://localhost:8787/channels/slack/events

The store the host uses is the one in your module (host({ store: sqlite(".threads") })); --store applies to the other commands.

Inspecting: timeline

threads timeline 01a0ceb2-0776-7e19-9c4c-0a3ed10d8985
threads timeline <thread_id> --branch <branch_id>

Prints one JSON object per step of the main branch (or the given one): the logged event plus whether it is a place you can fork from. See Timeline.

Moving threads: export, import, repair

threads export <branch_id> > branch.jsonl
threads --store ./other-store import branch.jsonl   # prints the imported branch id

The export is the branch's exact log bytes, so it verifies byte-for-byte on import, in either language.

An export holds the log, not the stored artifacts it points to (recorded model requests, large tool outputs). Importing into a different store fails with artifact_missing unless that store's artifacts/ directory already has them, so copy the source store's artifacts/ directory along.

A file cut off mid-write (a torn tail) still imports, for inspection only. threads repair <branch_id> marks it runnable again.

Deleting: delete

threads delete <thread_id>          # one thread (of --tenant, default local)
threads delete --tenant acme        # every thread of a tenant

Deletion removes the thread's log and records that it was deleted. Threads its subagents ran are deleted with it; threads it handed off to are not. Sandboxes it still held are marked for release by the next gc. Deleting a team's lead deletes the whole team: its members, its team log and everything indexed for it. Everything goes in one step, or nothing does:

  • busy: something in it is still running, has an effect whose outcome is unknown, or has a log this version can't verify. Cancel it, wait for it to stop, resolve any parked effect (or run threads repair on a torn branch), then delete. A log that no repair can fix (damaged in the middle, not just a torn tail) keeps its thread, and a whole-tenant delete that includes it, refused. Delete it with the version that wrote it; if that isn't possible, erasing the tenant means recreating the store without it (export the tenants you keep, then import them into a fresh store).
  • thread_in_team: the thread is a team member or a team log. Delete its lead instead.

A refusal prints <code>: <message> and exits nonzero.

Cleaning up: gc

threads gc                  # remove unreferenced artifacts older than 7 days
threads gc app --grace-days 1

With a host module, gc also releases sandboxes that runs left behind, using that module's sandbox providers. It never runs on its own: schedule it (for example with cron) if you want it regular.

Evals: eval

threads eval [--agent <module>] [--cases <dir>] [--case <name>]... [--live] [--store <dir>] [--strict] [--out <file>]

Runs every saved case under --cases (default cases). Without --agent it loads no user code and runs the free checks; --agent adds drift against your current agents; --live grades with a judge model. --store keeps the live and judge threads (by default they are not kept), --strict also fails on stale and skipped cases, and --out writes the report as JSON. Exit 0 when the run passed, 1 when it failed, 2 for a usage or config error. See Running evals.

Exit codes

0 on success, 1 when the command failed (the error code and message go to stderr, for example not_found: ...), 2 for a usage error.

Edit on GitHub

On this page