ATIF export¶
glossogen export-atif writes a run's agents as
ATIF (Agent Trajectory
Interchange Format) trajectories: one JSON document per agent, holding its
prompts, responses, tool calls and token usage in a schema other agent tooling
reads.
It logs one line per trajectory written, with its step count.
| Flag | Effect |
|---|---|
--run-dir DIR |
the run to export (required) |
--out-dir DIR |
where to write <agent_id>.json, created if missing (required) |
--agent-id ID |
only this agent; omit for every agent |
--round R |
exclusive cutoff, as for export-thread: keeps rounds 1..R-1 |
Trajectories are generated from the JSONL event log on each call. Nothing is written to the run directory.
Other ways to get it¶
| Surface | Call |
|---|---|
| REST | GET /api/g/{slug}/runs/{scenario}/{run_dir_name}/agents/{agent_id}/atif?round=R |
| MCP | export_agent_atif with run_id, agent_id, cutoff_round |
| Raw zip | the export modal's raw tab (on by default), glossogen export --raw --include-atif, or include_atif on POST .../runs/export/raw |
| Single-run zip | always included in a run's zip download |
REST and MCP return {"trajectories": [...]} for one agent. The raw zip writes
every agent of every selected run under atif/ in that run's folder, as described
in exporting runs.
How events map to steps¶
| ATIF | Comes from |
|---|---|
session_id |
the run id, <scenario>/<run_dir_name> |
agent |
the agent's agent_registered event: role and model, tool definitions; agent_id, provider, channels and the agent's model_settings (output cap, thinking level) under extra |
system step |
the registered system prompt, then each context compaction summary |
user step |
the prompt the runner opens each agent cycle with: the initial prompt on a fresh agent, the continue prompt after that |
agent step |
each LLM response: text, reasoning_content, tool_calls |
observation |
the tool results, matched to their call by tool_call_id |
root extra |
scenario name, scenario_config, cutoff round, generation |
Scenario injections, world events and the other agents' channel messages are not
steps of their own. The model receives them as the results of its
read_notifications and read_channel calls, and that is where a trajectory
holds them. Round verdicts and postmortem phases are not part of any trajectory;
read the event log or the CSV tables for those.
An agent step from the run above, with the tool result cut short:
{
"step_id": 6,
"timestamp": "2026-06-25T13:57:19.992503+00:00",
"source": "agent",
"message": "Let me check the comm link to see what happened, then engage the engineer in discussion.",
"model_name": "claude-opus-4-7",
"tool_calls": [
{
"tool_call_id": "toolu_01GwfhcK6SBU7CBmaENzoJG3",
"function_name": "read_channel",
"arguments": {"channel_id": "link", "last_n": 20}
}
],
"observation": {
"results": [
{
"source_call_id": "toolu_01GwfhcK6SBU7CBmaENzoJG3",
"content": "{\"current_round\": 1, \"messages\": [{\"round\": 1, \"sender\": \"Field Observer\", \"text\": \"Vri al"
}
]
},
"extra": {"round_number": 1, "stop_reason": "tool_use"}
}
An agent step's timestamp is when its first tool call was invoked. The response
event itself is logged after the calls return, so for a slow tool that is after
events the agent had not yet seen. A runner-prompt step has no timestamp: the log
records when a cycle's turns happened, not when its prompt was sent.
A cycle that raised before producing a response is recorded on the next agent
step as extra.failed_cycles. Cycles that fail after the last response are
recorded on the trajectory's root extra as failed_cycles_after_last_response.
Token usage is per cycle¶
The runner logs token usage once per agent cycle, on that cycle's last response.
Only those steps carry metrics, and their llm_call_count is the number of
responses in the cycle they close. A cycle can span many rounds: in the run
above, the stabilization engineer's 109 steps hold one with metrics, closing a
cycle of 107 responses:
{
"llm_call_count": 107,
"metrics": {
"prompt_tokens": 34352270,
"completion_tokens": 92070,
"cached_tokens": 34164983,
"cost_usd": 20.5544015,
"extra": {"cache_creation_input_tokens": 186980}
}
}
prompt_tokens includes cached tokens, as ATIF defines it. cost_usd uses the
platform's pricing table, and it is
absent for a model that table does not price.
final_metrics sums the steps played in this run. A copied step
keeps its metrics, but its source run already counts that spend, so the totals
leave it out. When no played step carries metrics, the token and cost totals are
absent rather than zero: the same run's field observer never closed a cycle, so
its 176 steps carry no metrics and its final_metrics holds only total_steps.
Tool definitions¶
agent.tool_definitions lists each tool the agent was offered, with its name,
description and JSON input_schema. agent.extra.tool_definitions_source says
where they came from:
| Source | Meaning |
|---|---|
recorded |
logged on the agent's agent_registered event when the run launched |
reconstructed |
the run predates that, so the scenario was rebuilt from its recorded config and its tools listed at export time |
names_only |
neither worked, for instance the scenario is no longer installed, so each entry has a name only |
A reconstructed schema describes the code installed at export time, not the
code the run used. Tool descriptions get edited, and some read knob values, which
is why the rebuild uses the run's recorded config rather than a preset.
Swapped and replaced seats¶
A seat produces one trajectory per generation. A new generation starts at an
in-run swap, at the first launch of a replace-agent or cross-run run's replaced
seat, and at any re-registration under a different model. Files are named
<agent_id>.gen1.json, <agent_id>.gen2.json and so on, with matching
trajectory_ids. A resume under the same model continues the same trajectory,
with a continue-prompt step where the relaunched runner picked the agent up.
A swapped-in generation's system step is the prompt the swap seeded its agent with: the swap's own when the scheduled event set one, otherwise the seat's registered prompt. The event records which, so a swap logged before that was recorded shows the registered prompt either way.
Copied context¶
A step with "is_copied_context": true is history the agent received rather than
played in this run. Which steps carry it depends on how the run was made:
| Run | Copied steps |
|---|---|
| fork-at-round | every step up to the boundary event its manifest names |
| replace-agent | the same for the agents that kept their seats, and for the predecessor's generation of the replaced seat, which is all copied. The replaced seat's new generation opens with its seed: the predecessor's tool calls on the channels it was allowed to see |
| cross-run replace-agent | the same, except the imported seat's seed is its history from the run it came from |
| in-run swap | the swapped-in trajectory opens with its seed, built at the swap round |
plain --resume |
none, since it continues the same run |
A seed is rebuilt with the filter the run used when it launched, and its steps
have no timestamp or metrics, since the history they come from records
neither. In the replace-agent run veyru/1780729770, the replaced field observer
has two files: gen1 holds the predecessor's 71 steps, all copied, and gen2
has 78 steps, 71 of them seed, followed by the continue prompt and the steps
played after the boundary.
Checking a file against the spec¶
The spec's Pydantic models ship in the harbor package. This validates an
exported file against them without adding that package to the project:
uv run --no-project --python 3.12 --prerelease=allow --with harbor python -c '
import json, sys
from harbor.models.trajectories import Trajectory
Trajectory.model_validate(json.load(open(sys.argv[1])))
print("valid")' ./atif/field_observer.json
Next¶
- Exporting runs for CSV tables and raw run folders
- Evaluation for an agent's thread as a provider request body
- MCP integration for the other run-browsing tools