Skip to content

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.

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

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:

Terminal window
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.json

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.

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 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.

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.

  • 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_definitions is 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.

Version 1.0 of EATIF has three rules:

  1. Every EATIF file is a valid ATIF file. schema_version stays the ATIF version, so ATIF validators and readers open the file unchanged.
  2. Extensions live under one key. Every addition is in an eatif object inside an ATIF extra field: on the trajectory, on steps, on tool calls, on observation results, and on subagent trajectory references.
  3. Removing the extensions gives the ATIF export. Delete every extra.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. 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.

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

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.

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"] }.

To save a trajectory in the app:

  1. Open a conversation.
  2. Select Share or export in the conversation header.
  3. Choose ATIF or EATIF, then save the file.

To write one from the CLI, run hansard export with --format atif or --format eatif:

Terminal window
hansard export <session-id> --format eatif
hansard 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.

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:

Terminal window
python -m harbor.utils.trajectory_validator conversation.eatif.json