# Trajectory formats

> EATIF is Hansard's extension of ATIF, the agent trajectory format Harbor reads. See what EATIF adds, when to use each format, and every EATIF field.

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

EATIF, the Enhanced Agent Trajectory Interchange Format, is Hansard's
extension of
[ATIF](https://github.com/harbor-framework/harbor/blob/main/rfcs/0001-trajectory-format.md),
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

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](https://jqlang.org/), run:

```sh
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
```

## ATIF export

Hansard writes `schema_version` `ATIF-v1.7`, the version that adds embedded
subagent trajectories.

### Steps

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

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

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

`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

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

## EATIF v1.0 specification

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.

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

`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

`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

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.

```json
{
  "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

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`:

```sh
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](https://www.hansard.dev/guides/share-conversations/) for every option.

## 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:

```sh
python -m harbor.utils.trajectory_validator conversation.eatif.json
```

> **Paths and private text:** An EATIF file includes the working directory, the files the conversation
> touched, memory file paths, and the text of harness records. Both formats
> include tool arguments and output. Review a trajectory before you publish
> it.
