mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* fix(agents): describe unrecorded tool results as unknown outcomes
Use shared unknown-outcome guidance while preserving the Responses-family aborted convention. Retain synthetic provenance in model context so late real results can replace repair placeholders without changing existing detection or stored transcript rows.
* improve(agents): assert provider request history stays append-only
Track converted message prefixes and record declared compaction, pruning, runtime-context, and image rewrites. Notify every cache-affinity baseline for the same session identity. Memoize message/content and schema fingerprints, with strict assertions enabled only by an opt-in environment flag.
* fix(agents): pin the shared tool-result text for the request wrapper
Include packages/llm-core/src/types.ts in the PR wrapper's extracted source inventory. Main commit 93625aa3d1 pulled tool-result-pairing.ts into that graph, so its existing shared tool-result text import must be included for standalone wrapper execution.
326 lines
14 KiB
Markdown
326 lines
14 KiB
Markdown
---
|
|
summary: "Reference: provider-specific transcript sanitization and repair rules"
|
|
read_when:
|
|
- You are debugging provider request rejections tied to transcript shape
|
|
- You are changing transcript sanitization or tool-call repair logic
|
|
- You are investigating tool-call id mismatches across providers
|
|
title: "Transcript hygiene"
|
|
---
|
|
|
|
OpenClaw applies **provider-specific fixes** to transcripts before a run
|
|
(building model context). These are **in-memory** adjustments used to satisfy
|
|
strict provider requirements. Runtime transcript state stays in SQLite;
|
|
provider-specific
|
|
assistant-prefill stripping happens only while constructing outbound
|
|
payloads.
|
|
|
|
Scope includes:
|
|
|
|
- Runtime-only prompt context staying out of user-visible transcript turns
|
|
- Tool call id sanitization
|
|
- Tool call input validation
|
|
- Tool result pairing repair
|
|
- Turn validation / ordering
|
|
- Thought signature cleanup
|
|
- Thinking signature cleanup
|
|
- Image payload sanitization
|
|
- Blank text-block cleanup before provider replay
|
|
- Incomplete reasoning-only length-turn cleanup before provider replay
|
|
- User-input provenance tagging (for inter-session routed prompts)
|
|
- Empty assistant error-turn removal for provider replay
|
|
|
|
If you need transcript storage details, see
|
|
[Session management deep dive](/reference/session-management-compaction).
|
|
|
|
---
|
|
|
|
## Failed attempts and recovery
|
|
|
|
Text-only assistant errors are buffered until the logical run settles. Recovery
|
|
discards their partial text because the recovered reply supersedes it. Terminal
|
|
failure persists the last attempt's partial text and error.
|
|
|
|
Tool calls, displayable non-text content, and attachment facts are persisted
|
|
immediately, before dependent tool results or the recovered reply. These fact
|
|
rows omit the error and use a replayable stop reason so provider replay retains
|
|
the calls. For mixed text/fact messages, partial text and the error remain
|
|
buffered separately; terminal settlement does not duplicate facts or usage.
|
|
This uses existing assistant-row shapes and requires no database migration.
|
|
|
|
## Global rule: runtime context is not user transcript
|
|
|
|
Runtime/system context can be added to the model prompt for a turn, but it is
|
|
not end-user-authored content. OpenClaw keeps a separate transcript-facing
|
|
prompt body for Gateway replies, queued followups, ACP, CLI, and embedded
|
|
OpenClaw runs. Stored visible user turns use that transcript body instead of
|
|
the runtime-enriched prompt.
|
|
|
|
For legacy sessions that already persisted runtime wrappers, Gateway history
|
|
surfaces apply a display projection before returning messages to WebChat,
|
|
TUI, REST, or SSE clients.
|
|
|
|
---
|
|
|
|
## Where this runs
|
|
|
|
The embedded runner selects and applies transcript policy:
|
|
|
|
- Policy selection: `src/agents/transcript-policy.ts`
|
|
(`resolveTranscriptPolicy`, keyed on `provider`, `modelApi`, and `modelId`)
|
|
- Sanitization/repair application: `sanitizeSessionHistory` in
|
|
`src/agents/embedded-agent-runner/replay-history.ts`
|
|
|
|
Legacy JSONL validation and import belong to `openclaw doctor --fix`; the
|
|
embedded runner does not repair or reopen file-backed runtime transcripts.
|
|
|
|
---
|
|
|
|
## Global rule: image sanitization
|
|
|
|
Image payloads are always sanitized to prevent provider-side rejection due to
|
|
size limits (downscale/recompress oversized base64 images). This also helps
|
|
control image-driven token pressure for vision-capable models: lower max
|
|
dimensions reduce token usage, higher dimensions preserve detail.
|
|
|
|
Implementation:
|
|
|
|
- `sanitizeSessionMessagesImages` in
|
|
`src/agents/embedded-agent-helpers/images.ts`
|
|
- `sanitizeContentBlocksImages` in `src/agents/tool-images.ts`
|
|
- Max image side is configurable via `agents.defaults.imageMaxDimensionPx`
|
|
(default: `1200`)
|
|
- Blank text blocks are removed while this pass walks replay content.
|
|
Assistant turns that become empty are dropped unless they own opaque
|
|
provider replay state; user and tool-result turns that become empty receive
|
|
a non-empty omitted-content placeholder.
|
|
|
|
---
|
|
|
|
## Global rule: malformed tool calls
|
|
|
|
Assistant tool-call blocks missing both `input` and `arguments` are dropped
|
|
before model context is built. This prevents provider rejections from
|
|
partially persisted tool calls (for example, after a rate limit failure).
|
|
|
|
Completed call/result pairs remain history when their tool is disabled, removed,
|
|
or unavailable in the current catalog. Their names still require valid syntax;
|
|
malformed calls, ambiguous pairing, and synthetic missing-result repairs do not
|
|
grant this exception. Replaying a completed pair does not advertise or authorize
|
|
the tool for a new call.
|
|
|
|
Implementation:
|
|
|
|
- `sanitizeToolCallInputs` in `src/agents/session-transcript-repair.ts`
|
|
- Applied in `sanitizeSessionHistory`
|
|
(`src/agents/embedded-agent-runner/replay-history.ts`)
|
|
|
|
---
|
|
|
|
## Global rule: tool result pairing
|
|
|
|
Tool results are paired to tool-call occurrences within each assistant turn before
|
|
provider-specific call IDs are rewritten. Provider-generated IDs may repeat on later
|
|
turns, so a result adjacent to a repeated call stays with that occurrence. A displaced
|
|
result is moved only when exactly one unresolved occurrence can own it; ambiguous
|
|
extras are dropped and missing occurrences receive synthetic error results.
|
|
|
|
Synthetic missing results tell the model that the outcome is unknown: retry only
|
|
read-only or idempotent operations, and verify current state before repeating an
|
|
operation that may have had side effects. Responses-family transports retain
|
|
their `aborted` placeholder. Neither placeholder proves that the tool did not run.
|
|
|
|
Implementation: `sanitizeToolUseResultPairing` in
|
|
`src/agents/session-transcript-repair.ts`
|
|
|
|
When switching models, provider replay moves delayed asynchronous tool results
|
|
next to their originating call before removing the source model's async metadata.
|
|
Call and result IDs are trimmed before matching, so surrounding whitespace does
|
|
not turn a real result into a synthetic missing-result error. This projection
|
|
runs in `packages/ai/src/transcript-transform.ts` and leaves stored history intact.
|
|
|
|
---
|
|
|
|
## Global rule: incomplete or silent reasoning-only turns
|
|
|
|
Assistant turns are omitted from the in-memory replay copy when they contain
|
|
only thinking or redacted-thinking content after either of these events:
|
|
|
|
- The provider output limit ends the turn with incomplete reasoning state.
|
|
- Silent-reply cleanup removes the turn's only visible `NO_REPLY` text.
|
|
|
|
The silent-reply cleanup prevents hidden reasoning from merging into a later
|
|
assistant tool-use turn when strict providers rebuild the conversation.
|
|
|
|
Empty length turns remain unchanged, as do length turns with visible text,
|
|
tool calls, or unknown content blocks. Silent-reply turns with tool calls or
|
|
unknown content blocks also remain unchanged. Stored transcripts are not
|
|
rewritten.
|
|
|
|
Implementation: `normalizeAssistantReplayContent` in
|
|
`src/agents/embedded-agent-runner/replay-history.ts`
|
|
|
|
---
|
|
|
|
## Global rule: inter-session input provenance
|
|
|
|
When an agent sends a prompt into another session via `sessions_send`
|
|
(including a delayed reply delivered to the requester), OpenClaw persists the
|
|
created user turn with `message.provenance.kind = "inter_session"`.
|
|
|
|
OpenClaw also prepends a same-turn `[Inter-session message] ... isUser=false`
|
|
marker before the routed prompt text so the active model call can
|
|
distinguish foreign session output from external end-user instructions. This
|
|
marker includes the source session, channel, and tool when available. The
|
|
transcript still uses `role: "user"` for provider compatibility, but the
|
|
visible text and provenance metadata both mark the turn as inter-session
|
|
data.
|
|
|
|
During context rebuild, OpenClaw applies the same marker to older persisted
|
|
inter-session user turns that only have provenance metadata.
|
|
|
|
---
|
|
|
|
## Provider matrix (current behavior)
|
|
|
|
**OpenAI / OpenAI Codex**
|
|
|
|
- Image sanitization only.
|
|
- Drop orphaned reasoning signatures (standalone reasoning items without a
|
|
following content block) for OpenAI Responses/Codex transcripts, and drop
|
|
replayable OpenAI reasoning after a model route switch.
|
|
- Preserve replayable OpenAI Responses reasoning item payloads, including
|
|
encrypted empty-summary items, so manual/WebSocket replay keeps required
|
|
`rs_*` state paired with assistant output items.
|
|
- Native ChatGPT Codex Responses follows Codex wire parity by replaying
|
|
prior Responses reasoning/message/function payloads without prior item
|
|
IDs while preserving session `prompt_cache_key`.
|
|
- OpenAI Responses-family replay preserves canonical `call_*|fc_*`
|
|
same-model reasoning pairs, but deterministically normalizes malformed or
|
|
overlong `call_id`/function-call item ids before pi-ai payload conversion.
|
|
- Tool result pairing repair may move real matched outputs and synthesize
|
|
Codex-style `aborted` outputs for missing tool calls.
|
|
- No turn validation or reordering; no thought signature stripping.
|
|
|
|
**OpenAI-compatible Chat Completions**
|
|
|
|
- Historical assistant thinking/reasoning blocks are stripped before replay
|
|
so local and proxy-style OpenAI-compatible servers do not receive
|
|
prior-turn reasoning fields such as `reasoning` or `reasoning_content`.
|
|
- Current same-turn tool-call continuations keep the assistant reasoning
|
|
block attached to the tool call until the tool result has been replayed.
|
|
- Custom/self-hosted model entries with `reasoning: true` preserve replayed
|
|
reasoning metadata.
|
|
- Provider-owned exceptions can opt out when their wire protocol requires
|
|
replayed reasoning metadata.
|
|
|
|
**Google (Generative AI / Gemini CLI / Antigravity)**
|
|
|
|
- Tool call id sanitization: strict alphanumeric.
|
|
- Tool result pairing repair and synthetic tool results.
|
|
- Turn validation (Gemini-style turn alternation).
|
|
- Google turn ordering fixup (prepend a tiny user bootstrap if history
|
|
starts with assistant).
|
|
- Antigravity Claude: normalize thinking signatures; drop unsigned thinking
|
|
blocks.
|
|
|
|
**Anthropic / Minimax (Anthropic-compatible)**
|
|
|
|
- Prefix-binding Claude models, such as Fable 5.1, persist runtime-context carriers
|
|
as hidden custom messages immediately after their user turn and replay them in
|
|
place. Inline inbound metadata on older user turns is also retained. This
|
|
model-scoped append-only policy includes Bedrock, Vertex, and Foundry routes.
|
|
Carriers contain only the delimited context body; the shared instruction lives
|
|
once in the stable system prompt. Carriers remain user-role context and
|
|
are excluded from chat history and compaction summarization. Other Claude
|
|
models and Anthropic-compatible models keep transient carriers, avoiding
|
|
repeated cache-read charges and context use for old carriers when nothing
|
|
binds the prefix.
|
|
- Tool result pairing repair and synthetic tool results.
|
|
- Turn validation (merge consecutive user turns to satisfy strict
|
|
alternation). For prefix-binding models on the Messages API, append-only replay keeps
|
|
consecutive user turns separate instead, so a command turn followed by a
|
|
prompt replays with the same per-turn timestamp stamps the active turn was
|
|
signed over; Bedrock Converse still merges them.
|
|
- Trailing assistant prefill turns are stripped from outgoing Anthropic
|
|
Messages payloads when thinking is enabled, including Cloudflare AI
|
|
Gateway routes.
|
|
- Pre-compaction assistant thinking signatures are stripped before provider
|
|
replay when a session has been compacted. On prefix-binding models,
|
|
compaction changes the signed prefix (summarized content replaces the
|
|
original), so replaying the original signatures can cause Anthropic to
|
|
reject the request with "Invalid signature in thinking block". The
|
|
thinking text is preserved as an unsigned block and then handled by the
|
|
rule below.
|
|
- Thinking blocks with missing, empty, or blank replay signatures are
|
|
stripped before provider conversion. If that empties an assistant turn,
|
|
OpenClaw keeps turn shape with non-empty omitted-reasoning text.
|
|
- Older thinking-only assistant turns that must be stripped are replaced
|
|
with non-empty omitted-reasoning text so provider adapters do not drop
|
|
the replay turn.
|
|
|
|
**Amazon Bedrock (Converse API)**
|
|
|
|
- Empty assistant stream-error turns and legacy fallback placeholders are dropped
|
|
from the in-memory replay copy. This avoids invalid empty ContentBlocks and
|
|
synthetic assistant prefill without rewriting the stored transcript.
|
|
- Zero-usage empty stop turns are dropped too; billed silent replies and errors
|
|
with real assistant content retain their existing replay handling.
|
|
- Pre-compaction assistant thinking signatures are stripped before Converse
|
|
replay when a session has been compacted, for the same reason as
|
|
Anthropic above.
|
|
- Claude thinking blocks with missing, empty, or blank replay signatures
|
|
are stripped before Converse replay. If that empties an assistant turn,
|
|
OpenClaw keeps turn shape with non-empty omitted-reasoning text.
|
|
- Older thinking-only assistant turns that must be stripped are replaced
|
|
with non-empty omitted-reasoning text so the Converse replay keeps
|
|
strict turn shape.
|
|
- Replay filters OpenClaw delivery-mirror and gateway-injected assistant
|
|
turns.
|
|
- Image sanitization applies through the global rule.
|
|
|
|
**Mistral (including model-id based detection)**
|
|
|
|
- Tool call id sanitization: strict9 (alphanumeric, length 9).
|
|
|
|
**OpenRouter Gemini**
|
|
|
|
- Thought signature cleanup: strip non-base64 `thought_signature` values
|
|
(keep base64).
|
|
|
|
**OpenRouter Anthropic**
|
|
|
|
- Trailing assistant prefill turns are stripped from verified OpenRouter
|
|
OpenAI-compatible Anthropic model payloads when reasoning is enabled,
|
|
matching direct Anthropic and Cloudflare Anthropic replay behavior.
|
|
|
|
**Everything else**
|
|
|
|
- Image sanitization only.
|
|
|
|
---
|
|
|
|
## Historical behavior (pre-2026.1.22)
|
|
|
|
Before the 2026.1.22 release, OpenClaw applied multiple layers of transcript
|
|
hygiene:
|
|
|
|
- A **transcript-sanitize extension** ran on every context build and could:
|
|
- Repair tool use/result pairing.
|
|
- Sanitize tool call ids (including a non-strict mode that preserved
|
|
`_`/`-`).
|
|
- The runner also performed provider-specific sanitization, which
|
|
duplicated work.
|
|
- Additional mutations occurred outside the provider policy, including
|
|
stripping `<final>` tags from assistant text before persistence, dropping
|
|
empty assistant error turns, and trimming assistant content after tool
|
|
calls.
|
|
|
|
This complexity caused cross-provider regressions (notably
|
|
`openai-responses` `call_id|fc_id` pairing). The 2026.1.22 cleanup removed
|
|
the extension, centralized logic in the runner, and made OpenAI **no-touch**
|
|
beyond image sanitization.
|
|
|
|
## Related
|
|
|
|
- [Session management](/concepts/session)
|
|
- [Session pruning](/concepts/session-pruning)
|