Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CANONICAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fields it must compute from stored cross-upload state.
| `source_identity_kind` | library | `native` \| `location` \| `content` \| `synthetic` — how identity was derived (see below). **Requires a `source_identity_kind LowCardinality(String)` column in ClickHouse.** |
| `source_order_id` | library | fixed-width, lexicographically sortable logical order key within the group |
| `component_index` | library | index within one source record; worker sort input, **not** a ClickHouse column |
| `record_type` | library | `meta` \| `user` \| `reasoning` \| `assistant` \| `assistant-tool-call` \| `tool` |
| `record_type` | library | `meta` \| `system` \| `user` \| `reasoning` \| `assistant` \| `assistant-tool-call` \| `tool` |
| `record_id` | library | per-canonical-record dedup identity (64-hex sha256) |
| `record_hash` | library | sha256 of `record_json` (64-hex) |
| `content_hash` | library | sha256 of canonical semantic content, excluding transport metadata/timestamps (64-hex) |
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ A trajectory is an ordered array containing:

- One leading `meta` record identifying the source and available session
metadata.
- Optional system message records when `filters.systemMessages` is explicitly
set to `"include"`; system messages are omitted by default.
- `user` and assistant prose records.
- Optional `reasoning` records when the source exposes reasoning.
- Assistant tool-call records with stable IDs and stringified JSON-object
Expand Down
5 changes: 3 additions & 2 deletions fixtures/canonical/claude-code__tool-call.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -162,7 +162,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/codex__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
}
],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -231,7 +231,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/hermes__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -141,7 +141,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/letta-code__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -162,7 +162,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/omp__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -183,7 +183,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/openclaw__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -141,7 +141,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/openhands__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -183,7 +183,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
5 changes: 3 additions & 2 deletions fixtures/canonical/pi__tool-calls.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
],
"diagnostics": [],
"normalizer_version": "0.2.0",
"canonical_schema_version": 2,
"canonical_schema_version": 3,
"config": {
"bounds": {
"toolArguments": {
Expand All @@ -183,7 +183,8 @@
}
},
"filters": {
"toolResults": "include"
"toolResults": "include",
"systemMessages": "omit"
}
}
}
4 changes: 4 additions & 0 deletions python/src/trajectory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
NormalizeRequest,
NormalizeResult,
SourceContext,
SystemMessagePolicy,
SystemRecord,
TrajectoryListing,
ReasoningRecord,
ToolCall,
Expand Down Expand Up @@ -60,6 +62,8 @@
"NormalizeResult",
"ReasoningRecord",
"SourceContext",
"SystemMessagePolicy",
"SystemRecord",
"ToolCall",
"ToolArgumentBounds",
"ToolResultBounds",
Expand Down
9 changes: 9 additions & 0 deletions python/src/trajectory/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
]
ToolResultTruncationStrategy = Literal["head", "head-tail"]
ToolResultPolicy = Literal["include", "omit"]
SystemMessagePolicy = Literal["include", "omit"]
DiagnosticCode = Literal[
"invalid_json_line",
"non_object_json_line",
Expand Down Expand Up @@ -83,6 +84,7 @@ class NormalizationBounds(TypedDict, total=False):

class NormalizationFilters(TypedDict, total=False):
toolResults: ToolResultPolicy
systemMessages: SystemMessagePolicy


class SourceContext(TypedDict, total=False):
Expand Down Expand Up @@ -152,6 +154,12 @@ class UserRecord(TypedDict):
timestamp: str


class SystemRecord(TypedDict):
role: Literal["system"]
content: str
timestamp: str


class ReasoningRecord(TypedDict):
role: Literal["reasoning"]
content: str
Expand Down Expand Up @@ -190,6 +198,7 @@ class ToolResultRecord(_ToolResultOptional):

NormalizedRecord = Union[
MetaRecord,
SystemRecord,
UserRecord,
ReasoningRecord,
AssistantMessageRecord,
Expand Down
31 changes: 26 additions & 5 deletions python/src/trajectory/_vendor/trajectory-cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,15 @@ var codexAdapter = {
if (payloadType === "message") {
const role = payload.role;
const content = blocksText(payload.content);
if (role === "user") {
if (role === "system") {
emit({
type: "message",
role: "system",
content,
inputLine: line,
...timestamp ? { timestamp } : {}
});
} else if (role === "user") {
const head = content.trimStart();
if (INJECTED_PREFIXES.some((prefix) => head.startsWith(prefix))) {
diagnostics.push({
Expand Down Expand Up @@ -1924,20 +1932,24 @@ function invalidBounds(message) {
}

// src/filters.ts
var DEFAULT_NORMALIZATION_FILTERS = Object.freeze({ toolResults: "include" });
var DEFAULT_NORMALIZATION_FILTERS = Object.freeze({ toolResults: "include", systemMessages: "omit" });
function resolveFilters(filters) {
if (filters === undefined)
return { ...DEFAULT_NORMALIZATION_FILTERS };
assertObject2(filters, "filters");
const unknown = Object.keys(filters).find((key) => key !== "toolResults");
const unknown = Object.keys(filters).find((key) => key !== "toolResults" && key !== "systemMessages");
if (unknown !== undefined) {
throw invalidFilters(`filters contains unknown option ${JSON.stringify(unknown)}.`);
}
const toolResults = filters.toolResults ?? DEFAULT_NORMALIZATION_FILTERS.toolResults;
if (toolResults !== "include" && toolResults !== "omit") {
throw invalidFilters('filters.toolResults must be either "include" or "omit".');
}
return { toolResults };
const systemMessages = filters.systemMessages ?? DEFAULT_NORMALIZATION_FILTERS.systemMessages;
if (systemMessages !== "include" && systemMessages !== "omit") {
throw invalidFilters('filters.systemMessages must be either "include" or "omit".');
}
return { toolResults, systemMessages };
}
function assertObject2(value, path) {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
Expand Down Expand Up @@ -1983,7 +1995,7 @@ function validateTranscript(value, options) {
continue;
}
validateTimestamp(record.timestamp, index);
if (record.role === "user" || record.role === "reasoning") {
if (record.role === "system" || record.role === "user" || record.role === "reasoning") {
exactKeys(record, CONTENT_KEYS, index);
if (typeof record.content !== "string") {
fail(`Record ${index}: ${record.role} content must be a string.`);
Expand Down Expand Up @@ -2250,6 +2262,15 @@ function normalizeEvent(event, eventIndex, recordIndex, plan, diagnostics, bound
if (!event.content.trim()) {
return;
}
if (event.role === "system") {
if (filters.systemMessages === "omit")
return;
const record3 = {
role: "system",
content: event.content
};
return record3;
}
if (event.role === "user" && NOISE_PREFIXES.some((prefix) => event.content.trimStart().startsWith(prefix))) {
diagnostics.push({
code: "noise_record_dropped",
Expand Down
21 changes: 20 additions & 1 deletion python/tests/test_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def codex_message(role: str, text: str) -> str:
"role": role,
"content": [
{
"type": "input_text" if role == "user" else "output_text",
"type": "output_text" if role == "assistant" else "input_text",
"text": text,
}
],
Expand Down Expand Up @@ -197,6 +197,25 @@ def test_omits_tool_results_while_retaining_calls(self) -> None:
)
)

def test_system_messages_are_explicitly_opt_in(self) -> None:
transcript = "\n".join(
[
codex_message("system", "Follow the project instructions."),
codex_message("user", "Inspect the project."),
codex_message("assistant", "I inspected it."),
]
)

default_result = normalize_transcript(source="codex", transcript=transcript)
self.assertNotIn("system", [record["role"] for record in default_result["records"]])

included = normalize_transcript(
source="codex",
transcript=transcript,
filters={"systemMessages": "include"},
)
self.assertIn("system", [record["role"] for record in included["records"]])

def test_partial_fragment_is_opt_in(self) -> None:
with self.assertRaises(NormalizationError) as raised:
normalize_transcript(
Expand Down
1 change: 1 addition & 0 deletions schema/trajectory-canonical-v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"type": "string",
"enum": [
"meta",
"system",
"user",
"reasoning",
"assistant",
Expand Down
11 changes: 11 additions & 0 deletions schema/trajectory-v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"record": {
"oneOf": [
{ "$ref": "#/$defs/meta" },
{ "$ref": "#/$defs/system" },
{ "$ref": "#/$defs/user" },
{ "$ref": "#/$defs/reasoning" },
{ "$ref": "#/$defs/assistant" },
Expand Down Expand Up @@ -41,6 +42,16 @@
"timestamp": { "$ref": "#/$defs/timestamp" }
}
},
"system": {
"type": "object",
"required": ["role", "content", "timestamp"],
"additionalProperties": false,
"properties": {
"role": { "const": "system" },
"content": { "type": "string" },
"timestamp": { "$ref": "#/$defs/timestamp" }
}
},
"reasoning": {
"type": "object",
"required": ["role", "content", "timestamp"],
Expand Down
10 changes: 9 additions & 1 deletion src/adapters/codex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,15 @@ export const codexAdapter: SourceAdapter = {
if (payloadType === "message") {
const role = payload.role;
const content = blocksText(payload.content);
if (role === "user") {
if (role === "system") {
emit({
type: "message",
role: "system",
content,
inputLine: line,
...(timestamp ? { timestamp } : {}),
});
} else if (role === "user") {
const head = content.trimStart();
if (INJECTED_PREFIXES.some((prefix) => head.startsWith(prefix))) {
diagnostics.push({
Expand Down
Loading