Trajectory formats
Hansard exports any archived conversation as an agent trajectory: one JSON file that records each prompt, model response, tool call, and tool result in order. It writes two trajectory formats, ATIF and EATIF.
What EATIF adds to ATIF
Section titled “What EATIF adds to ATIF”EATIF, the Enhanced Agent Trajectory Interchange Format, is Hansard’s
extension of
ATIF,
the Agent Trajectory Interchange Format that the Harbor framework defines.
Every EATIF file is a valid ATIF file. The additions live in eatif keys
inside ATIF’s extra objects, and schema_version stays the ATIF version, so
any ATIF reader opens the file and ignores what it does not know.
| Information | ATIF | What EATIF adds |
|---|---|---|
| Origin | The harness name and version in agent |
The harness label, every harness version the session recorded, the source type, the Hansard importer version, and the import time |
| Workspace | No field | The repository, the working directory, and the Git branches recorded during the conversation |
| Conversation | Steps in order, with timestamps | The title, the session kind, start and end times, the parent of a subagent session, message counts, the archived messages behind each step, provider request ids, and whether a person typed each prompt |
| Tokens | prompt_tokens (all input), cached_tokens (cache reads), and completion_tokens, with cache writes and reasoning tokens in metrics.extra |
Fresh input, cache reads, cache writes, output, and reasoning as separate counts on each step and for the trajectory, and the totals the harness recorded for the session |
| Cost | An estimated cost_usd |
The pricing version behind the estimate, and counts of steps without a price or with only estimated usage |
| Tool calls | The call id, tool name, arguments, and result | The tool category (shell, edit, read, and others), the file or path the call acted on, its status and times, its MCP server, and an error flag on failed results |
| File changes | Only inside tool arguments | Lines added and removed on each edit result, and line totals and the files touched for the conversation |
| Commits | No field | Each commit’s SHA, branch, title, and time |
| Memory files | No field | The memory and instruction files the conversation loaded, read, or wrote |
| Harness records | Left out | Hook results, mode changes, and other records the harness wrote about itself, attached to the step they precede |
| Attachments | Left out | The name, media type, size, and SHA-256 hash of each image or file |
| Subagent runs | Each run embedded as a trajectory | A list of runs with the tool call that started each one, its title, and its agent type |
| The export | No field | The Hansard version, the export time and options, counts of what the export left out, and the redaction summary |
When to use ATIF or EATIF
Section titled “When to use ATIF or EATIF”Use ATIF when the file goes to Harbor, its validator, or a fine-tuning or evaluation pipeline that takes plain ATIF. ATIF gives the smaller file, and more tools read it.
Use EATIF when you want the context Hansard knows about the conversation in a file that stays ATIF-compatible:
- Cost analysis. The full token split on every step and the pricing version behind each cost estimate.
- Auditing what an agent changed. Line changes on each edit, the files touched, commits, and the hooks and mode changes around each step.
- Comparing harnesses. The harness and its versions, and the category of every tool call.
- Research datasets. The repository, the session kind, and which prompts a person typed.
An EATIF file holds the complete ATIF file. Remove every eatif key, and any
extra object that is then empty, and the result equals the ATIF export of the
same conversation with the same options. To do that with
jq, run:
jq 'walk(if type == "object" and (.extra | type) == "object" then .extra |= del(.eatif) | if .extra == {} then del(.extra) else . end else . end)' conversation.eatif.json > conversation.atif.jsonATIF export
Section titled “ATIF export”Hansard writes schema_version ATIF-v1.7, the version that adds embedded
subagent trajectories.
ATIF records a conversation as a list of steps. Hansard builds them from the normalized transcript in the archive, the same for every harness:
| Step | source |
What it holds |
|---|---|---|
| A prompt | user |
The prompt text. A message another agent sent also becomes a user step. |
| A model response | agent |
The reply text in message, reasoning in reasoning_content, every tool call the response made in tool_calls, and the result of each call in observation, linked by source_call_id. |
| A recorded system prompt | system |
The prompt text. |
| A context compaction | system |
Context compacted, the summary that replaced the earlier context as the observation, and extra.context_management with type compaction and boundary replace, the ATIF convention for a context boundary. |
| A subagent update | system |
The update text. |
A model response is one step when its parts share a provider request id. For
harnesses that record no request id, consecutive response messages form one
step until a tool result or a new prompt arrives. A response step with one
request id has llm_call_count 1.
Records a harness writes about itself, such as hook results, permission mode changes, and environment notices, are not part of the conversation and never become steps. An EATIF export keeps them as context entries.
Tool calls
Section titled “Tool calls”Each call keeps the harness’s own call id, tool name, and arguments. A call a
harness recorded as raw text, such as a code-mode script or a patch, keeps
that text under arguments.input. When a subagent ran for a call, the call’s
result carries a subagent_trajectory_ref to the subagent’s trajectory.
Subagent runs
Section titled “Subagent runs”Subagent conversations that Hansard archived are embedded in
subagent_trajectories, each a complete ATIF trajectory whose
trajectory_id is the subagent’s Hansard session id. Embedding stops at three
levels and 100 trajectories.
Token counts and cost
Section titled “Token counts and cost”metrics on each response step follows the ATIF accounting:
| Field | Value |
|---|---|
prompt_tokens |
Fresh input plus cache reads plus cache writes |
cached_tokens |
Cache reads |
completion_tokens |
Output, including reasoning tokens the provider counted separately |
cost_usd |
An estimate from Hansard’s model pricing snapshot |
extra.cache_creation_input_tokens |
Cache writes, which some providers bill at a separate rate |
extra.reasoning_tokens |
Reasoning tokens, when the provider reported them |
A request recorded on several messages counts once. Usage a harness only
estimated is left out of metrics. final_metrics totals the steps and every
embedded subagent trajectory.
What ATIF exports leave out
Section titled “What ATIF exports leave out”- Token ids and log probabilities. ATIF has fields for them because reinforcement learning needs the exact tokens a model sampled and their probabilities. Only the inference server sees those values, and agent harnesses do not record them, so no archived conversation has them.
- Tool definitions. Harnesses do not record the list of tools they
offered, so
agent.tool_definitionsis absent. - Images and other attachments. The archive keeps them beside the transcript. An EATIF export lists each one by name, type, size, and hash.
- Reasoning a harness kept hidden. Some harnesses store only a signed
placeholder for each reasoning block. Those steps have no
reasoning_content.
EATIF v1.0 specification
Section titled “EATIF v1.0 specification”Version 1.0 of EATIF has three rules:
- Every EATIF file is a valid ATIF file.
schema_versionstays the ATIF version, so ATIF validators and readers open the file unchanged. - Extensions live under one key. Every addition is in an
eatifobject inside an ATIFextrafield: on the trajectory, on steps, on tool calls, on observation results, and on subagent trajectory references. - Removing the extensions gives the ATIF export. Delete every
extra.eatifkey, and anyextraobject that is then empty, and the result equals the ATIF export of the same conversation with the same options. The steps, their order, and every ATIF field are identical.
extra.eatif.version names the EATIF version, EATIF-v1.0. A later 1.x
version only adds fields. Readers ignore fields they do not know.
Trajectory extension
Section titled “Trajectory extension”extra.eatif on the trajectory:
| Field | Contents |
|---|---|
version |
EATIF-v1.0 |
producer |
name (hansard) and version of the exporting software |
exported_at |
When the file was written |
options |
Whether reasoning, tool_output, and subagents were included |
source |
provider, harness, harness_label, source_type, parser_version (the Hansard importer that read it), harness_versions, and imported_at |
session |
title, kind (conversation, subagent, sdk_job, sdk_delegated, probe, or assessment), started_at, ended_at, parent_session_id, message_count, user_message_count, and human_user_message_count |
workspace |
repo, scope, cwd, and the git_branches recorded during the conversation |
models |
Each model and the number of steps it answered |
accounting |
The token split of this trajectory’s own steps, without embedded subagents (input_tokens, cache_read_input_tokens, cache_creation_input_tokens, output_tokens, reasoning_tokens), their cost_usd with cost_source estimated and pricing_version, counts of unpriced_steps and estimated_usage_steps, and recorded_session_totals, the totals the harness recorded for the whole session |
activity |
tool_calls, tool_errors, calls by_category (such as shell, edit, read, search, web, task, skill, and mcp), lines_added, lines_removed, files_touched, commits (sha, branch, title, timestamp), and interruptions |
memory |
Memory and instruction files the conversation loaded, read, and written, each with its path, scope, and source, when Hansard recorded them |
redaction |
The redaction summary of the archived transcript |
omissions |
Counts of reasoning blocks (reasoning) and tool outputs (tool_outputs) the export options left out, tool results with no matching call (unmatched_tool_results), steps without a timestamp (timestamps), and subagent runs that were not embedded (subagent_runs) |
context_records |
How many harness records the conversation has |
trailing_context |
Harness records after the last step, as context entries |
subagent_runs |
Each embedded subagent: trajectory_id, the tool_call_id that started it, title, and agent_type |
Step extension
Section titled “Step extension”extra.eatif on a step:
| Field | Contents |
|---|---|
kind |
prompt, agent_message, response, reasoning, system_prompt, compaction, delegation, or tool_result |
message_indexes |
The archived transcript messages the step was built from |
roles |
The original message roles, when the step combines several roles or has a role other than user, assistant, system, or tool |
human |
On user steps: whether a person typed the prompt (true) or a harness or agent wrote it (false), when the harness recorded it |
timestamp_status |
session-order-only when the harness recorded no reliable time and timestamp is left out |
provider_ids |
The provider’s request_ids, message_ids, and uuids |
usage |
The step’s token split. { "estimated": true } when the harness only estimated it |
cost_source |
estimated when metrics.cost_usd is set |
stop_reason, permission_mode |
As the harness recorded them |
sidechain, agent_id |
For subagent messages a harness kept inside the parent conversation |
delegation |
For subagent updates: message_type, task_name, author, and recipient |
attachments |
Each attachment’s name, media_type, bytes, and sha256 |
context |
Harness records written before this step, as context entries |
A context entry has message_index, timestamp, role, kind (the
harness’s record type, such as claude_hook_success), title, and text.
Tool call, result, and reference extensions
Section titled “Tool call, result, and reference extensions”extra.eatif on a tool call: title (the harness’s display name, when it
differs from the tool name), category, target (the file or path it acted
on), status, started_at, original_id (when Hansard renamed a repeated
call id), code_mode_wrapper, and mcp_server.
extra.eatif on an observation result: status, is_error, completed_at,
line_count, lines_added and lines_removed for file edits,
message_index, unmatched (a result whose call could not be found), and
attachments.
extra.eatif on a subagent trajectory reference: title, the title of the
subagent run.
Example
Section titled “Example”One response step from an EATIF file, with every value invented. The step
made one edit, and a permission mode change came just before it. Without its
three eatif objects, the step is the same step in the ATIF export.
{ "step_id": 4, "timestamp": "2026-03-02T14:05:09.000Z", "source": "agent", "model_name": "claude-opus-5-5", "message": "Added mint to the bed list.", "tool_calls": [ { "tool_call_id": "toolu_01", "function_name": "Edit", "arguments": { "file_path": "/home/user/garden/beds.md", "old_string": "- basil", "new_string": "- basil\n- mint" }, "extra": { "eatif": { "category": "edit", "target": "/home/user/garden/beds.md" } } } ], "observation": { "results": [ { "source_call_id": "toolu_01", "content": "The file /home/user/garden/beds.md has been updated.", "extra": { "eatif": { "status": "completed", "lines_added": 1, "lines_removed": 0, "message_index": 7 } } } ] }, "metrics": { "prompt_tokens": 160, "completion_tokens": 20, "cached_tokens": 100, "cost_usd": 0.000913, "extra": { "cache_creation_input_tokens": 50 } }, "llm_call_count": 1, "extra": { "eatif": { "kind": "response", "message_indexes": [5, 6, 7], "roles": ["assistant", "tool"], "provider_ids": { "request_ids": ["req_example_01"] }, "usage": { "input_tokens": 10, "cache_read_input_tokens": 100, "cache_creation_input_tokens": 50, "output_tokens": 20 }, "cost_source": "estimated", "context": [ { "message_index": 4, "timestamp": "2026-03-02T14:05:02.000Z", "role": "system", "kind": "claude_permission_mode", "title": "Claude permission mode", "text": "Claude permission mode: acceptEdits" } ] } }}The trajectory’s own extra.eatif holds the rest, for example
"workspace": { "repo": "github.com/example-labs/garden", "git_branches": ["main"] }.
Get an ATIF or EATIF file
Section titled “Get an ATIF or EATIF file”To save a trajectory in the app:
- Open a conversation.
- Select Share or export in the conversation header.
- Choose ATIF or EATIF, then save the file.
To write one from the CLI, run hansard export with --format atif or
--format eatif:
hansard export <session-id> --format eatifhansard export <session-id> --format atif --out ~/trajectories/The file is named for the conversation and ends in .atif.json or
.eatif.json. --no-reasoning, --no-tool-output, and --no-subagents
leave those parts out. See
Share a conversation for every option.
Validate a trajectory
Section titled “Validate a trajectory”hansard export --format atif and --format eatif check each file against
the ATIF rules before writing it. To check a file with Harbor’s own validator:
python -m harbor.utils.trajectory_validator conversation.eatif.json