|
| 1 | +--- |
| 2 | +summary: "Opt-in runtime training data export that produces episode-level JSONL from compaction, session reset, and trajectory export triggers" |
| 3 | +read_when: |
| 4 | + - Enabling or configuring the training export feature |
| 5 | + - Understanding the trigger architecture and episode format |
| 6 | + - Debugging training export output or missing episodes |
| 7 | + - Reviewing the privacy and retention implications of unredacted training data |
| 8 | +title: "Training Export" |
| 9 | +--- |
| 10 | + |
| 11 | +# Training Export |
| 12 | + |
| 13 | +## Overview |
| 14 | + |
| 15 | +The training export system produces episode-level JSONL training data from the OpenClaw runtime trajectory. Each line in the output file is a self-contained training sample — either a **task episode** capturing an agent turn, or a **compact-summary episode** capturing how the agent compresses conversation context. |
| 16 | + |
| 17 | +**Output:** `~/.openclaw/training-export/episodes.jsonl` |
| 18 | + |
| 19 | +### Design Principles |
| 20 | + |
| 21 | +- **Trajectory-first.** All training-required fields (system prompt, messages, tools, model metadata) are sourced from runtime trajectory events, not reconstructed offline. |
| 22 | +- **Trigger-driven.** Export is invoked at well-defined trigger points (compaction hooks, session reset, manual export command). No separate offline pipeline. |
| 23 | +- **Provider-owned conversion.** Message and tool conversion delegates to the Pi SDK / provider layer wherever possible, minimizing duplicated conversion logic. |
| 24 | +- **Unified compaction hook.** Training export for all compaction modes streams through a single pair of Pi SDK hooks (`session_before_compact` + `session_compact`), rather than being called from individual compaction paths. |
| 25 | +- **Pair-export guarantee.** For compaction-triggered exports, a task episode and a compact-summary episode must appear as a complete pair. If either is filtered by quality checks, the entire batch is discarded. |
| 26 | +- **Config-gated at call sites.** Callers check `trainingExport.enabled` before invoking export, so the intent is visible at every entry point without digging into implementation details. |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## Trigger Architecture |
| 31 | + |
| 32 | +### Compaction Hooks (Primary Trigger) |
| 33 | + |
| 34 | +The training export extension registers two Pi SDK hooks that fire for **all** compaction modes: |
| 35 | + |
| 36 | +| Hook | When | Action | Coverage | |
| 37 | +| ------------------------ | -------------------------- | --------------------------------------------------------------- | --------------------------------------------- | |
| 38 | +| `session_before_compact` | Before compaction executes | Stash pre-compaction snapshot + Pi SDK `preparation` (no write) | default, safeguard, manual | |
| 39 | +| `session_compact` | After compaction completes | Validate summary, build task + summary pair, write | default, safeguard, manual, overflow, timeout | |
| 40 | + |
| 41 | +**Pair-export flow:** |
| 42 | + |
| 43 | +1. `session_before_compact` — stash the current runtime snapshot and Pi SDK's `preparation` object (which provides `messagesToSummarize`, `previousSummary`, and `customInstructions`) |
| 44 | +2. `session_compact`: |
| 45 | + - If the compaction summary is empty (boundary-only compaction where `keepRecentTokens` covers all messages) → discard stash, no export |
| 46 | + - If the summary is valid → build both episodes; if **either** is filtered by quality checks, discard the entire batch |
| 47 | + - On success → atomically write both episodes |
| 48 | + |
| 49 | +### Non-Compaction Triggers |
| 50 | + |
| 51 | +| Trigger | Call Site | Exports | |
| 52 | +| ------------------- | ---------------------------------------------------- | ------------ | |
| 53 | +| `before_reset` | `src/gateway/session-reset-service.ts` | task episode | |
| 54 | +| `trajectory_export` | `src/auto-reply/reply/commands-export-trajectory.ts` | task episode | |
| 55 | + |
| 56 | +Both call sites guard on `getTrainingExportConfig(cfg)?.enabled === true` before calling `runTrainingExport`. |
| 57 | + |
| 58 | +### Extension Registration |
| 59 | + |
| 60 | +The extension is registered in `src/agents/pi-embedded-runner/extensions.ts`, gated on `trainingExport.enabled`: |
| 61 | + |
| 62 | +```typescript |
| 63 | +if (getTrainingExportConfig(params.cfg)?.enabled === true) { |
| 64 | + setCompactionTrainingExportRuntime(params.sessionManager, params.cfg ?? null); |
| 65 | + factories.push(compactionTrainingExportExtension); |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## Episode Types |
| 72 | + |
| 73 | +### Task Episode |
| 74 | + |
| 75 | +Triggered by `on_compaction` (without `compactionEntry`), `before_reset`, or `trajectory_export`. |
| 76 | + |
| 77 | +Built from the runtime snapshot collected from the latest `context.compiled` trajectory event: |
| 78 | + |
| 79 | +- System prompt |
| 80 | +- Runtime messages (with trailing non-assistant messages trimmed for `on_compaction` — see below) |
| 81 | +- Runtime tools |
| 82 | +- Model metadata and trace info |
| 83 | + |
| 84 | +**Trailing trim.** For all trigger types, if the snapshot ends mid-turn at a non-`assistant` message (e.g. `toolResult`), trailing non-`assistant` messages are removed. The `trainExampleMessagesAreUsable` check requires ≥1 user + ≥1 assistant; if trimming leaves the episode unusable, it is discarded. This is a training-data quality requirement, not a trigger-specific behavior. |
| 85 | + |
| 86 | +### Compact-Summary Episode |
| 87 | + |
| 88 | +Triggered by `on_compaction` (with `compactionEntry`). |
| 89 | + |
| 90 | +Payload is built from the Pi SDK `preparation` object stashed during `session_before_compact`: |
| 91 | + |
| 92 | +| Field | Source | |
| 93 | +| -------------- | ----------------------------------------------------------------------------------------- | |
| 94 | +| `systemPrompt` | `COMPACT_SUMMARIZATION_SYSTEM_PROMPT` (local constant) | |
| 95 | +| `promptText` | `buildCompactSummaryPrompt({ messagesToSummarize, previousSummary, customInstructions })` | |
| 96 | +| `responseText` | `compactionEntry.summary` | |
| 97 | +| `compaction` | `tokensBefore`, `firstKeptEntryId`, `fromExtension` | |
| 98 | + |
| 99 | +**Empty-summary guard.** When `messagesToSummarize` is empty (short conversations where `keepRecentTokens` covers all messages), `serializeCompactSummaryConversation` returns an empty string, producing `<conversation>\n\n</conversation>`. The `compactConversationTextIsNonEmpty` regex (`/<conversation>\s*[\s\S]*\S[\s\S]*<\/conversation>/`) requires at least one non-whitespace character between the tags, so the summary episode fails validation and is filtered. Combined with pair-export, both task and summary episodes are correctly discarded for this boundary case. |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Message Conversion Pipeline |
| 104 | + |
| 105 | +Messages are converted via the `chat_completions` format pipeline: |
| 106 | + |
| 107 | +``` |
| 108 | +runtime messages (Pi SDK format) |
| 109 | + ↓ |
| 110 | +1. Pre-process (single map over messages) |
| 111 | + a. Strip thinking blocks from assistant messages |
| 112 | + b. Convert compactionSummary → user message |
| 113 | + ↓ |
| 114 | +2. Upstream convertMessages() from @mariozechner/pi-ai/openai-completions |
| 115 | + ↓ |
| 116 | +3. adaptChatCompletionsMessagesToExportMessages() |
| 117 | + ↓ |
| 118 | +4. Append reasoning_content (scanned from original runtimeMessages) |
| 119 | + ↓ |
| 120 | +5. developer role → system role (training format compatibility) |
| 121 | +``` |
| 122 | + |
| 123 | +### Why CompactionSummary Conversion is Needed |
| 124 | + |
| 125 | +Pi SDK's `convertToLlm` (`@mariozechner/pi-coding-agent/dist/core/messages.js:103-108`) converts `compactionSummary` messages to user messages with wrapper text. However, the upstream `convertMessages` from `@mariozechner/pi-ai/openai-completions` does **not** handle the `compactionSummary` role. Without pre-processing, compaction summary messages are silently dropped from task episodes, losing critical context. |
| 126 | + |
| 127 | +The pre-processing step mirrors Pi SDK's conversion format: |
| 128 | + |
| 129 | +``` |
| 130 | +The conversation history before this point was compacted into the following summary: |
| 131 | +
|
| 132 | +<summary> |
| 133 | +{summary text} |
| 134 | +</summary> |
| 135 | +``` |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Configuration |
| 140 | + |
| 141 | +```typescript |
| 142 | +interface TrainingExportConfig { |
| 143 | + enabled?: boolean; // default: false (opt-in) |
| 144 | + compat?: ModelCompatConfig; // model compatibility overrides for export |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +The `enabled` check is applied at every entry point (call sites + extension registration). |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Trigger Types |
| 153 | + |
| 154 | +| Kind | Scenario | Distinction | |
| 155 | +| ------------------- | --------------------- | ----------------------------------------------------------------- | |
| 156 | +| `on_compaction` | Compaction event | Has `compactionEntry` → summary episode; otherwise → task episode | |
| 157 | +| `before_reset` | Session reset | task episode | |
| 158 | +| `trajectory_export` | Manual export command | task episode | |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## Key Files |
| 163 | + |
| 164 | +| File | Responsibility | |
| 165 | +| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 166 | +| `src/training-export.ts` | Core: snapshot collection, episode construction, JSONL I/O, prompt constants, compaction extension (merged from `compaction-summary-prompt.ts` and `compaction-training-export.ts`) | |
| 167 | +| `src/agents/pi-embedded-runner/extensions.ts` | Extension registration (config-gated) | |
| 168 | +| `src/agents/pi-hooks/compaction-safeguard.ts` | Safeguard compaction logic (no longer contains training export calls) | |
| 169 | +| `src/agents/pi-embedded-runner/compact.ts` | Manual compaction (no longer contains training export calls) | |
| 170 | +| `src/gateway/session-reset-service.ts` | `before_reset` trigger → `runTrainingExport` | |
| 171 | +| `src/auto-reply/reply/commands-export-trajectory.ts` | `trajectory_export` trigger → `runTrainingExport` | |
| 172 | + |
| 173 | +--- |
0 commit comments