Trajectory and message format
For AI Agents: the Markdown version of this page is at https://ankole.agentbull.com/en-US/docs/trajectory-format/index.md. The documentation index is at https://ankole.agentbull.com/en-US/llms.txt.
Ankole records what an agent did in two places, for two kinds of work: AIGateway stores conversation messages (the live transcript a stateful Responses conversation produces), and Background Agent Jobs store turn trajectories (the per-turn record of a durable job’s execution). This page documents both storage shapes, the canonical ChatML format, and the model-visible projection that strips protocol detail. It builds on AIGateway and Background Agent Jobs.
The decisive property, stated up front: the model never sees raw storage rows. Both shapes project to a model-visible form that strips protocol identities and replaces them with turn-local call aliases — the model sees “tool call 1 → tool result 1,” not internal UUIDs or wire protocol fields. The stored form is for durability and audit; the projected form is for the model.
AIGateway conversation messages
AIGateway owns the live conversation transcript. Each message is a row in ai_gateway_messages:
| Field | Meaning |
|---|---|
subject_uid |
the Principal the conversation belongs to |
conversation_id |
the conversation this message is part of |
type |
the message type (assistant, tool result, and so on) |
role |
the role the message plays in the transcript |
status |
lifecycle status |
previous_message_id |
a self-reference continuation anchor — renders as previous_response_id on the API, so the conversation chains |
content |
the message content (a JSON value, not a single string) |
metadata |
opaque caller metadata plus AIGateway-owned response facts (model, provider, usage, provider raw ids) |
The previous_message_id is the continuation anchor: each message points at its predecessor, producing a linked chain. On the API, this renders as previous_response_id, so a caller or a compaction can resume from any anchor. The metadata field carries facts AIGateway owns — the model and provider used, the token usage, the provider’s raw response id — alongside opaque caller metadata. It must not carry a second item list.
A compaction (see Context compression) replaces the old messages with a summary message that becomes the new anchor. The old messages are no longer in the model’s visible context; the summary is the new starting point.
Background Agent Job trajectories
A background job stores its per-turn execution as an append-only stream of sanitized semantic thread items in background_agent_job_turn_items. Each item belongs to a turn, has a position, a revision, an item key, and the semantic item itself:
| Field | Meaning |
|---|---|
turn_id |
the job turn this item belongs to |
position |
the item’s order within the turn |
revision |
the turn revision that accepted the item |
item_key |
a stable key for the item (a client: key marks a caller message) |
item |
one typed semantic thread item |
The rows are append-only: a steer or nudge appends new items instead of rewriting stored ones. Every reader projects each stored item into canonical ChatML messages at read time, and an item that projects no messages stays stored for thread replay. A turn recorded before the item stream existed has no item rows, so its trajectory renders empty.
Tool-result message metadata records execution_mechanism as provider_hosted when the model Provider executes the tool, or local_dynamic when Codex invokes a dynamic tool implemented by Ankole. This stable fact distinguishes tools that use the same display name.
This is a separate storage shape from the AIGateway conversation messages, because a background job’s trajectory belongs to the job, not to a conversation. The job’s turns are their own thread; the conversation it reports back to receives the result, not the trajectory.
The model-visible projection
The model does not see the stored rows. modelVisibleTrajectory in the worker projects a trajectory to what the model should see:
- Strips stored protocol identities — internal message ids, wire-protocol fields, anything the model should not act on.
- Replaces tool-call ids with turn-local aliases —
call_1,call_2, and so on. The model sees which tool result belongs to which tool call (the only useful relationship), without the internal UUIDs that wire them. - Preserves content and role — the actual messages, the tool calls and their results, in the order they happened.
- Preserves bounded-content facts — trajectory-level
metadata.redactedandmetadata.content_truncatedstay visible. The control-plane projection setscontent_truncatedwhen it must reduce selected message content to meet its page limit.
The moduledoc is explicit: “Turn-local call aliases preserve the only useful relationship: which tool result belongs to which tool call.” Everything else the storage row carries is for the system, not the model.
How the two shapes relate
| AIGateway messages | Background job trajectories | |
|---|---|---|
| What it stores | live conversation transcript | per-turn job execution record |
| Who owns it | AIGateway | Background Agent Jobs |
| Canonical format | AIGateway’s message schema | semantic items projected to ChatML |
| Model sees it through | the stateful Responses API | modelVisibleTrajectory projection |
| Compaction | AIGateway compaction replaces old messages | not compacted (jobs are bounded by retry budget) |
The two do not mix. A conversation’s messages are AIGateway’s; a job’s trajectory is the job’s. The job reports its result back to the owning conversation through a wakeup event (see Background Agent Jobs), not by writing into the conversation’s message store.
What this guide is not
It is not a ChatML specification — the canonical ChatML format is a standard, and Ankole’s read-time projection keeps that shape, not redefines it. It is not a consumer-facing API for reading trajectories — the Console routes (/ai-gateway/conversations/:id/messages, /background-agent-jobs/:id) are the operator surface, documented in the Console API reference. And it is not a substitute for the storage pages; this is the format-level view across both.
Next steps
- For the conversation message store, read AIGateway.
- For the job trajectory store, read Background Agent Jobs.
- For compaction (which replaces old messages), read Context compression.
- For the Console routes that read these, read the Console API reference.