Skip to main content

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

atif_make-0.2.0.tar.gz (182.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

atif_make-0.2.0-py3-none-any.whl (38.4 kB view details)

Uploaded Python 3

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

Hashes for atif_make-0.2.0.tar.gz
Algorithm Hash digest
SHA256 a5bfe343fba8cfb1edb7883d968f36db47c1fb1826eee826d114b28b165db47b
MD5 b5f191e43ef7175f5bc7727e1e5a392e
BLAKE2b-256 cb427dab63e2f08697bcd9cb43e21c10ada0381d68785e3343bba70ef7a2a4cc

See more details on using hashes here.

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

Hashes for atif_make-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 da52e55bd2f5ea1692f75c6a08de29d7665062aca3c7d1f57ff0c60d99d42b24
MD5 f8c8f57bbda1ee047b6cb36e8db3b700
BLAKE2b-256 5836f941fe92eff1a494367562f7d5482fa3ba47d0511e74e329bd63e38c04ad

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page