Files
openclaw/docs/reference/session-management-compaction.md
Ayaan Zaidi bd5f760aec fix(agents): honor exact NO_REPLY and drop fabricated turn-ending replies (#170534)
Fixes the source-chat forms of #170147 (fabricated and forced replies after deliberate silence); the Anthropic runtime-context form is handled by #170438. Related: #150539, #85714.

## Problem

OpenClaw conflated deliberate silence, missing output, and an undelivered answer. Canned assistant replies masked missing results, required turns overrode explicit `NO_REPLY`, and consumers used different silence heuristics.

## Fix and user impact

- A final is silent only when its trimmed raw text is exactly `NO_REPLY`. A leading `NO_REPLY` followed by a newline and text delivers that text. Punctuation, lowercase, quotes, JSON, reasoning wrappers, and prose remain visible.
- `prepareAgentRunTerminalReply` classifies once and publishes `meta.terminalReply`. Consumers use that fact; stored-row readers use one exact helper.
- External chats honor `NO_REPLY`, including required turns. Subagents, private completions, and WebChat/Control UI require a result by default. Trusted input handling can explicitly mark result consumption optional, avoiding a duplicate answer to a late peer result.
- When a result is owed, an empty final goes through recovery, then fallback, then one system notice: **"No answer was returned. Ask again if you still need one."** The notice is never an assistant row. External recovery permits an answer or `NO_REPLY`; result-required recovery requires an answer.
- A required, undelivered final in message-tool-only mode gets one hidden retry (send it or choose `NO_REPLY`), then the notice. Optional turns keep private finals quiet.
- Optional turns owe no result: an empty final ends quietly without recovery or notice. Native and CLI results retain the reply obligation through fallback; a clean empty result without an explicitly optional policy still remains fallback-eligible.
- A yielded CLI turn returns its paused continuation metadata even when a result is required. Merely spawning a child without yielding still requires a real private result.
- Canned stand-ins are removed. Authored acknowledgments, classified failure guidance, restart/session-recovery notices, tool warnings, uncertain-delivery notices, media no-ack behavior, and retention warnings remain.

This reverses #168571's suppression of punctuated reasoning placeholders. Replies rejected by narrowed permissions report failure rather than completion. Yields without an authored acknowledgment stay quiet.

Out of scope: run-wait full-result hydration, child-result retention and the `truncated` snapshot field, and the broader waiting-status redesign.

## Evidence

[Before/after Control UI screenshots and synthetic QA-channel receipts](https://github.com/openclaw/openclaw/pull/170534#issuecomment-6198030745).

The [published-driver upgrade proof from v2026.10.3](https://github.com/openclaw/openclaw/pull/170534#issuecomment-6198687389) remains applicable: this PR adds no schema or stored-row changes since that proof; its later repairs change runtime reply policy and test fixtures only.

| Check | Observed result |
|---|---|
| Fleet replay: 90 days, 4 production installs, 21,344 terminal finals, 29 provider/model pairs | 0 silent/visible classification differences; 0 newly visible control text |
| Mutation checks on 15 guarantees | All 15 regressions fail when their fix is removed |
| Live Telegram matrix: 352 turns, 6 models | 205 of 212 non-empty silent-scenario finals were exact `NO_REPLY`; no punctuated, wrapped, lowercase, or trailing near-misses; cross-chat sends 48/48; reactions 48/48; required answers 79/80 (one pre-existing steering overlap) |
| Targeted live re-check at `cc0a8c8` | Cross-chat 16/16 (was 3/8); yields 16/16 without canned status; empty-first recovery 20/20 real answers with no wrong `NO_REPLY`; Control UI 5/5 results |
| Forgotten message tool (#85714) | Scripted 6/6; real required turns: OpenAI 10/10, Anthropic 10/10 |
| Earlier integration at `068b53b8ca2` | 13/13 focused files, 833 tests pass; direct format/lint and diff checks pass |
| Main integration at `063436cbde5` | Merged `8d4e374169b`; conflict owners and terminal/retry/replay/CLI/followup boundaries: 12/12 focused files, 580 tests pass; direct format/lint and diff checks pass |
| Integration repairs at `16b7f78ef93` | 11 repaired files pass (236 tests); core and affected test type lanes, direct lint, and mock-export ratchet pass. The local trusted-obligation Gateway test stopped before startup on stale runtime-artifact publication; fresh-build CI covers that entry point. |
| Optional completion at `9041ced336c` | 11 focused assertions across 4 files pass: optional empty completion, no recovery/fallback, unchanged exact silence, and preserved genuine heartbeat failure; core types and direct lint pass. |
| Empty fallback repair at `3ccbec7ea0b` | 12 targeted files pass (709 tests, including focused reruns of corrected fixtures); required/default clean-empty fallback, optional no-fallback, native/CLI obligation metadata, Codex finalization, and dispatch boundaries covered. Direct format/lint and diff checks pass. |
| Conflict integration and collector proof at `4841cf797c5` | Integrated `20b5969cd83` only after GitHub reported a merge conflict. Collector lifecycle: 230 tests pass, checking captured text in the durable row rather than the lightweight runtime projection. Unchanged plugin-reload suite: 5 tests pass locally. Direct format/lint and diff checks pass. |
| CLI and final CI repairs at `0479f1981de` | CLI reliability 62/62, Gateway agent 394/394, announce fencing 9/9, targeted Slack progress 1/1, versions UI 1/1, and plugin borrowing 5/5 pass. Optional empty CLI output now settles quietly. Announcement fixtures use current run owners; geometry is measured atomically; the plugin fixture joins existing startup catalog work before measuring run custody. |
| Final integration at `a6d4bc7ae0c` | Five focused files pass (71 tests): configured model picker, exact Codex terminal routing, one-shot cleanup, and test-type shard ownership. The UI chat type lane, direct type-aware lint, formatting, and diff checks pass. Updated fixtures follow raw native terminal facts, await accepted startup before inspecting auto-answered requests, and track the renamed Solid avatar test. |
| Yielded CLI handoff at `7e6b53d1c1c` | The WebChat yield regression failed before repair and passes afterward; CLI reliability passes 64/64, including non-yielded child-result rejection, terminal empty/silent rejection, and optional-empty quiet settlement. Agent-root test types, direct type-aware lint, formatting, and diff checks pass. |
| Published v2026.10.3 to `068b53b8ca2` upgrade | [Upgrade proof](https://github.com/openclaw/openclaw/pull/170534#issuecomment-6198687389): settings and 32 transcript events retained; exact `NO_REPLY` hidden and `NO_REPLY.` visible; retained fallback-notice state does not block a new turn; Gateway readiness HTTP 200 |

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-10-15 20:01:04 -07:00

6.8 KiB

summary, read_when, title
summary read_when title
Deep dive: session store + transcripts, lifecycle, and (auto)compaction internals
You need to debug session ids, transcript events, or session row fields
You are changing auto-compaction behavior or adding "pre-compaction" housekeeping
You want to implement memory flushes or silent system turns
Session management deep dive

A single Gateway process owns session state end-to-end. UIs (macOS app, web Control UI, TUI) query the Gateway for session lists and token counts. In remote mode, the per-agent SQLite database lives on the remote host, so checking your local Mac's state will not reflect what the Gateway is using.

Overview docs first: Session management, Compaction, Memory overview, Memory search, Session pruning, Transcript hygiene, full config reference at Agent config.

This page is an index. The deep dive is documented on six pages, one per reader job. Open the page that matches your task and stay there.

Page Read it when
Session state on disk The two persistence layers and the per-agent paths on the Gateway host.
Store maintenance and retention session.maintenance keys, disk-budget cleanup, cron retention, and the SQLite downgrade path.
Session keys, ids, and transcript events sessionKey patterns, sessionId lifecycle, SessionEntry fields, and transcript entry types.
Compaction behavior and settings What compaction does, when it runs, its settings and providers, and where it surfaces.
Silent turns and the memory flush The NO_REPLY contract and agents.defaults.compaction.memoryFlush.
Session transcript working sets Proposed worker-owned acquisition, resident budgets, and history-reader migrations.

Where each section moved

Every section heading from the previous single-page version keeps its anchor here, so an existing link such as /reference/session-management-compaction#when-auto-compaction-happens still resolves. Each entry points at the page that now holds the content.

Troubleshooting checklist

  • Session key wrong? Start with /concepts/session and confirm the sessionKey in /status.
  • Store vs transcript mismatch? Confirm the Gateway host and the store path from openclaw status.
  • Compaction spam? Check the model's context window (too small forces frequent compaction) and tool-result bloat (tune session pruning).
  • Every prompt seems to overflow on a small local model? Confirm the provider reports the correct model context window. OpenClaw can cap the effective reserve only when that window is known.
  • Silent turns leaking? Confirm the entire completed final trims to uppercase NO_REPLY. Lowercase, punctuation, quotes, JSON, and trailing tokens are visible text. A literal leading NO_REPLY\n removes only that control line and delivers the remaining text; see Silent housekeeping.