atif-make
Make ATIF v1.7 trajectories from agent logs — Claude Code, Codex, Copilot CLI, and HAR captures.
Zero runtime dependencies. Python 3.12+.
Install
uv tool install atif-make # puts `atif-make` on your PATH
Or as a library in a project:
uv add atif-make
For a browser view of what you convert, see the companion
atif-view, which depends on this package.
uv tool install builds an isolated environment, so nothing lands in your
project or system Python. To follow local edits instead, use
uv tool install --editable .; to remove it, uv tool uninstall atif-make.
Running from a checkout without installing works too: uv run atif-make ....
atif-make ~/.claude/projects/my-project/session.jsonl # convert one log
atif-make index --stats # what do I have, across agents?
Commands
atif-make <file> convert (shorthand for `atif-make convert`)
atif-make convert <file> convert one log
atif-make convert <dir|archive> convert every log inside
atif-make index [roots...] scan for sessions across agents
atif-make formats list supported input formats
| Flag | Command | Meaning |
|---|---|---|
-o, --output |
convert | output path (default <input>.trajectory.json) |
-f, --format |
convert | force the input format instead of detecting it |
--json |
convert | write one self-contained document to stdout |
--bundle OUT.zip |
convert | zip the trajectory with its images and subagents |
--split-subagents |
convert | write subagents as sibling files, not embedded |
--indent N |
convert | JSON indent (default 2) |
-q, --quiet |
convert | suppress progress output |
--stats |
index | print a per-agent summary |
--add |
index | merge into the existing index instead of replacing it |
Supported inputs
Most agents write two unrelated log shapes — what the CLI streams, and what it persists on disk — and they are not interchangeable. atif-make reads both.
| Format | Source |
|---|---|
claude-code-transcript |
~/.claude/projects/<project>/<session>.jsonl |
claude-code-stream |
claude -p --output-format stream-json |
codex-rollout |
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl |
codex-exec |
codex exec --json |
copilot-cli |
Copilot CLI session logs |
har |
Anthropic Messages, OpenAI Chat Completions, OpenAI Responses |
atif |
An ATIF trajectory that is already converted |
Directories, .zip and .tar.gz (also .tgz, .tar.bz2, .tar.xz) are read
as containers: every log inside is found and converted. That closes the loop on
--bundle — the zip atif-make hands you to send someone opens again in atif-make,
images and all.
Archives are extracted to a temporary directory, once per run. Members naming
absolute paths or climbing out with .. are refused rather than quietly
sanitised, and an archive that expands past 8 GB or 20,000 entries is rejected
outright.
Format is detected from content, never from the extension. atif exists so a
trajectory someone sends you opens like anything else — it is loaded, not
reparsed, and unknown fields from a newer ATIF minor version are dropped rather
than rejected.
What it gets right
These are the things that are easy to get wrong, and that silently corrupt a trajectory when you do:
Split messages. Claude Code writes one API response as several JSONL lines
that share a message.id — thinking, text, and each parallel tool_use arrive
separately, with the same usage object repeated on every line. Treating those
as separate turns inflates step counts and multiplies token totals. atif-make
coalesces them and counts usage once.
Out-of-order tool results. Parallel calls come back interleaved, and a slow
call can return several turns after it was issued. Pairing results to calls by
position drops some and misattributes others. atif-make pairs by tool_use_id.
Byte-capped detection. A JSONL preamble (hook events, rate-limit notices) can push the identifying line kilobytes into a file. atif-make scans whole lines.
Subagent structure. Claude Code links a delegated agent through a .meta.json
sidecar (toolUseId) and an agentId field on the result line — not through
anything in the result text. atif-make links by call id, so refs actually resolve
instead of leaving orphaned subagents.
Images. Codex embeds screenshots as base64 data URLs and Claude Code as
base64 content blocks — 24 of the sessions on one test machine carried them, and
a single Codex session held 65 images totalling 14 MB. Dropping them loses the
thing the agent was actually looking at, and inlining them makes an unreadable
document. atif-make writes them to images/ and references them by relative path,
which is what the spec asks for.
Malformed timestamps. ATIF requires ISO 8601. A truncated or hand-edited log can carry something else, and passing it through would make the whole trajectory fail validation, so an unparseable timestamp is dropped rather than emitted.
HAR tool results. In a HAR capture a tool's output is not in the response that called it — it appears in the next request's message history. atif-make harvests results across entries and pairs them back by id, while emitting the shared conversation prefix only once.
Output — a trajectory is a directory, not a file
ATIF references images and split subagents by path relative to the trajectory file, so anything with attachments is inherently multi-file:
session.trajectory.json the document
session.trajectory.<agent-id>.json subagents, with --split-subagents
images/<sha>.png images, referenced as "images/<sha>.png"
Images are de-duplicated by content hash, so the same screenshot pasted five times is stored once.
Three ways out, depending on where it's going:
atif-make session.jsonl -o out/t.json # directory form: t.json + images/
atif-make session.jsonl --json # one self-contained doc; images inlined as data: URIs
atif-make session.jsonl --bundle send.zip # zip of the whole directory — for sending someone
--json is the exception that proves the rule: stdout has no directory to put
siblings in, so images become data URIs to keep the document standalone.
Output carries timestamp, reasoning_content, per-step metrics, multimodal
ContentPart message content, and subagents either embedded
(subagent_trajectories) or split into sibling files with resolvable
trajectory_path refs.
Viewing
Conversion is all this package does. To browse trajectories in a browser,
install the companion atif-view, which depends on this
package and reads the index below.
Identity
Every session gets a content key — a hash of the file's first line. Logs are append-only, so that line never changes while a live session grows; hashing a fixed byte span re-keys a small file on every write. Documents that are not line-oriented open with a bare brace and fall back to a bounded head read.
The obvious alternatives do not work. A path changes when a file moves, and a
browser upload has none. session_id is run-scoped, not document-scoped — Codex
continuations inherit their parent's, so two distinct rollouts share one — and
some logs carry none at all. Their first lines still differ.
The key is what lets atif-view attach a name or a folder to a transcript and
have it survive the file moving, or a full re-index.
Where sessions come from
atif-make index scans your own machine — nothing is uploaded, and no network call
is ever made. By default it looks in:
~/.claude/projects/ Claude Code sessions
~/.codex/sessions/ Codex sessions
The result is cached at ~/.atif-make/index.json. Indexing reads only each file's
leading lines, so a 143 MB rollout costs the same as a 4 KB one; full conversion
happens lazily, when you actually open a session.
Scan somewhere else, or add a single file someone sent you:
atif-make index ~/work/logs # replace the index with this scan
atif-make index --add received.trajectory.json # merge one file into the existing index
atif-make index --add received-bundle.zip # or a whole bundle
--add merges; without it, index replaces — except for files opened by hand,
which live outside the scan roots and are carried across so a rescan cannot
silently drop them. Both raw logs and already-converted ATIF trajectories can be
added.
With a library present, --stats also groups by collection, so the command line
and the viewer describe the same corpus.
Tests
uv run pytest # unit tests, all synthetic fixtures
uv sync --extra spec # pulls harbor (large)
uv run --extra spec pytest # + validate against the reference ATIF models
The spec suite validates every fixture against harbor's own pydantic models —
ground truth for whether the output is really ATIF, rather than what atif-make
believes ATIF to be.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file atif_make-0.2.0.tar.gz.
File metadata
- Download URL: atif_make-0.2.0.tar.gz
- Upload date:
- Size: 182.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5bfe343fba8cfb1edb7883d968f36db47c1fb1826eee826d114b28b165db47b
|
|
| MD5 |
b5f191e43ef7175f5bc7727e1e5a392e
|
|
| BLAKE2b-256 |
cb427dab63e2f08697bcd9cb43e21c10ada0381d68785e3343bba70ef7a2a4cc
|
File details
Details for the file atif_make-0.2.0-py3-none-any.whl.
File metadata
- Download URL: atif_make-0.2.0-py3-none-any.whl
- Upload date:
- Size: 38.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da52e55bd2f5ea1692f75c6a08de29d7665062aca3c7d1f57ff0c60d99d42b24
|
|
| MD5 |
f8c8f57bbda1ee047b6cb36e8db3b700
|
|
| BLAKE2b-256 |
5836f941fe92eff1a494367562f7d5482fa3ba47d0511e74e329bd63e38c04ad
|