Trajectory Format

Hermes Agent saves conversation trajectories in ShareGPT-compatible JSONL format for use as training data, debugging artifacts, and reinforcement learning datasets.

Source files:agent/trajectory.py,run_agent.py(search for_save_trajectory),batch_runner.py

agent/trajectory.py run_agent.py _save_trajectory batch_runner.py

File Naming Convention​

Trajectories are written to files in the current working directory:

File When
trajectory_samples.jsonl Conversations that completed successfully (completed=True)
failed_trajectories.jsonl Conversations that failed or were interrupted (completed=False)

trajectory_samples.jsonl completed=True failed_trajectories.jsonl completed=False

The batch runner (batch_runner.py) writes to a custom output file per batch (e.g.,batch_001_output.jsonl) with additional metadata fields.

batch_runner.py batch_001_output.jsonl

You can override the filename via thefilenameparameter insave_trajectory().

filename save_trajectory()

JSONL Entry Format​

Each line in the file is a self-contained JSON object. There are two variants:

CLI/Interactive Format (from_save_trajectory)​

_save_trajectory

{  "conversations": [ ... ],  "timestamp": "2026-03-30T14:22:31.456789",  "model": "anthropic/claude-sonnet-4.6",  "completed": true}

Batch Runner Format (frombatch_runner.py)​

batch_runner.py

{  "prompt_index": 42,  "conversations": [ ... ],  "metadata": { "prompt_source": "gsm8k", "difficulty": "hard" },  "completed": true,  "partial": false,  "api_calls": 7,  "toolsets_used": ["code_tools", "file_tools"],  "tool_stats": {    "terminal": {"count": 3, "success": 3, "failure": 0},    "read_file": {"count": 2, "success": 2, "failure": 0},    "write_file": {"count": 0, "success": 0, "failure": 0}  },  "tool_error_counts": {    "terminal": 0,    "read_file": 0,    "write_file": 0  }}

Thetool_statsandtool_error_countsdictionaries are normalized to include ALL possible tools (frommodel_tools.TOOL_TO_TOOLSET_MAP) with zero defaults, ensuring consistent schema across entries for HuggingFace dataset loading.

tool_stats tool_error_counts model_tools.TOOL_TO_TOOLSET_MAP

Conversations Array (ShareGPT Format)​

Theconversationsarray uses ShareGPT role conventions:

conversations | API Role | ShareGPTfrom | | — | — | | system | “system” | | user | “human” | | assistant | “gpt” | | tool | “tool” |

from "system" "human" "gpt" "tool"

Complete Example​

{  "conversations": [    {      "from": "system",      "value": "You are a function calling AI model. You are provided with function signatures within <tools> </tools> XML tags. You may call one or more functions to assist with the user query. If available tools are not relevant in assisting with user query, just respond in natural conversational language. Don't make assumptions about what values to plug into functions. After calling & executing the functions, you will be provided with function results within <tool_response> </tool_response> XML tags. Here are the available tools:\n<tools>\n[{\"name\": \"terminal\", \"description\": \"Execute shell commands\", \"parameters\": {\"type\": \"object\", \"properties\": {\"command\": {\"type\": \"string\"}}}, \"required\": null}]\n</tools>\nFor each function call return a JSON object, with the following pydantic model json schema for each:\n{'title': 'FunctionCall', 'type': 'object', 'properties': {'name': {'title': 'Name', 'type': 'string'}, 'arguments': {'title': 'Arguments', 'type': 'object'}}, 'required': ['name', 'arguments']}\nEach function call should be enclosed within <tool_call> </tool_call> XML tags.\nExample:\n<tool_call>\n{'name': <function-name>,'arguments': <args-dict>}\n</tool_call>"    },    {      "from": "human",      "value": "What Python version is installed?"    },    {      "from": "gpt",      "value": "<think>\nThe user wants to know the Python version. I should run python3 --version.\n</think>\n<tool_call>\n{\"name\": \"terminal\", \"arguments\": {\"command\": \"python3 --version\"}}\n</tool_call>"    },    {      "from": "tool",      "value": "<tool_response>\n{\"tool_call_id\": \"call_abc123\", \"name\": \"terminal\", \"content\": \"Python 3.11.6\"}\n</tool_response>"    },    {      "from": "gpt",      "value": "<think>\nGot the version. I can now answer the user.\n</think>\nPython 3.11.6 is installed on this system."    }  ],  "timestamp": "2026-03-30T14:22:31.456789",  "model": "anthropic/claude-sonnet-4.6",  "completed": true}

Normalization Rules​

Reasoning Content Markup​

The trajectory converter normalizes ALL reasoning intotags, regardless of how the model originally produced it:

<think>

  1. Native thinking tokens(msg[“reasoning”]field from providers like Anthropic, OpenAI o-series): Wrapped as\n{reasoning}\n\nand prepended before the content.
  2. REASONING_SCRATCHPAD XML(when native thinking is disabled and the model reasons via system-prompt-instructed XML):tags are converted toviaconvert_scratchpad_to_think().
  3. Empty think blocks: Everygptturn is guaranteed to have ablock. If no reasoning was produced, an empty block is inserted:\n\n— this ensures consistent format for training data.

Native thinking tokens(msg[“reasoning”]field from providers like Anthropic, OpenAI o-series): Wrapped as\n{reasoning}\n\nand prepended before the content.

msg["reasoning"] <think>\n{reasoning}\n</think>\n

REASONING_SCRATCHPAD XML(when native thinking is disabled and the model reasons via system-prompt-instructed XML):tags are converted toviaconvert_scratchpad_to_think().

<REASONING_SCRATCHPAD> <think> convert_scratchpad_to_think()

Empty think blocks: Everygptturn is guaranteed to have ablock. If no reasoning was produced, an empty block is inserted:\n\n— this ensures consistent format for training data.

gpt <think> <think>\n</think>\n

Tool Call Normalization​

Tool calls from the API format (withtool_call_id, function name, arguments as JSON string) are converted to XML-wrapped JSON:

tool_call_id

<tool_call>{"name": "terminal", "arguments": {"command": "ls -la"}}</tool_call>

{} <tool_call> gpt

Tool Response Normalization​

All tool results following an assistant message are grouped into a singletoolturn with XML-wrapped JSON responses:

tool

<tool_response>{"tool_call_id": "call_abc123", "name": "terminal", "content": "output here"}</tool_response>

{ [ tool_calls

System Message​

The system message is generated at save time (not taken from the conversation). It follows the Hermes function-calling prompt template with:

<tools> FunctionCall <tool_call>

Tool definitions includename,description,parameters, andrequired(set tonullto match the canonical format).

name description parameters required null

Loading Trajectories​

Trajectories are standard JSONL — load with any JSON-lines reader:

import jsondef load_trajectories(path: str):    """Load trajectory entries from a JSONL file."""    entries = []    with open(path, "r", encoding="utf-8") as f:        for line in f:            line = line.strip()            if line:                entries.append(json.loads(line))    return entries# Filter to successful completions onlysuccessful = [e for e in load_trajectories("trajectory_samples.jsonl")              if e.get("completed")]# Extract just the conversations for trainingtraining_data = [e["conversations"] for e in successful]

Loading for HuggingFace Datasets​

from datasets import load_datasetds = load_dataset("json", data_files="trajectory_samples.jsonl")

The normalizedtool_statsschema ensures all entries have the same columns, preventing Arrow schema mismatch errors during dataset loading.

tool_stats

Controlling Trajectory Saving​

In the CLI, trajectory saving is controlled by:

# config.yamlagent:  save_trajectories: true  # default: false

Or via the–save-trajectoriesflag. When the agent initializes withsave_trajectories=True, the_save_trajectory()method is called at the end of each conversation turn.

--save-trajectories save_trajectories=True _save_trajectory()

The batch runner always saves trajectories (that’s its primary purpose).

Samples with zero reasoning across all turns are automatically discarded by the batch runner to avoid polluting training data with non-reasoning examples.