fix #97625: Qwen3:14b compaction fails with Already compacted#99136
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 11:40 AM ET / 15:40 UTC. Summary PR surface: Source +6, Tests +68. Total +74 across 2 files. Reproducibility: yes. at source level, but not as a live current-main Ollama run. Current main classifies Review metrics: none identified. Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow CLI compaction lifecycle fix after required CI remains clean, while keeping broader recently-compacted overflow recovery under #98982. Do we have a high-confidence way to reproduce the issue? Yes at source level, but not as a live current-main Ollama run. Current main classifies Is this the best way to solve the issue? Yes, this is the best narrow fix. It reuses the existing compaction reason classifier at the CLI lifecycle decision boundary instead of adding provider-specific Qwen/Ollama handling or a new config knob. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 3020f7817eb3. Label changesLabel justifications:
Evidence reviewedPR surface: Source +6, Tests +68. Total +74 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
806397f
into
openclaw:main
* origin/main: (1287 commits) fix(android): block loopback canvas navigation (openclaw#99874) fix(hooks): suppress unhandled stdout/stderr stream errors in gmail watcher (openclaw#100519) fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout (openclaw#98381) fix(build): fall back to tsx for build TypeScript scripts (openclaw#91262) feat(skills): suggest saving detected reusable workflows by default (openclaw#95477) (openclaw#100692) docs(changelog): remove generated release-note entries feat(telegram): offer BotFather web app flow in setup help and docs (openclaw#100540) fix(ios): unify Talk and Settings row typography on one branded detail row (openclaw#100515) feat(gateway): add persisted crash-loop breaker and fatal-config exit contract refactor(macos): lock and unify PortGuardian tunnel record persistence so concurrent app instances cannot lose orphan records (openclaw#100601) fix: stop reconnecting on protocol mismatch (openclaw#98414) fix(maint): reuse recent hosted gates after rebase (openclaw#100663) fix(ui): reopen web terminals without stale content (openclaw#100665) fix(browser): diagnose empty WSL2 Chrome replies (openclaw#100590) fix(ios): chat snaps back to bottom when scrolling to top via status-bar tap (openclaw#100502) Treat already-compacted CLI compaction as no-op (openclaw#99136) docs(changelog): remove direct main fix entry fix(feishu): strip internal tool-trace banners from outbound text (openclaw#98705) fix(message): thread --limit through to CLI formatter and surface provider pagination hints (openclaw#99089) fix(voyage): close response body stream when batch output JSONL parsing throws (openclaw#98840) ... # Conflicts: # extensions/memory-wiki/package.json
Closes #97625
Summary
Already compacted/already_compactedCLI turn compaction outcomes as safe no-ops instead of fatal pre-turn failures.ollama/qwen3:14bfailure message.What Problem This Solves
Users running local Ollama-backed CLI sessions can hit a fatal turn abort after the runtime has already compacted the transcript. In the reported Qwen3:14b workflow, this surfaced as
CLI transcript compaction failed for ollama/qwen3:14b: Already compactedbefore the MCP tool result could reach the user, while another Ollama model completed the same flow.Why This Change Was Made
OpenClaw already classifies
Already compactedasalready_compacted_recently, and adjacent manual/compactplus memory-flush preflight paths already treat that class as a skipped compaction. The CLI turn compaction lifecycle only skippedbelow_threshold, so the same harmless already-compacted state became a fatalfailureReasonfor CLI-backed turns.User Impact
A CLI-backed agent turn can now continue when the selected context engine reports that the transcript was already compacted. Real compaction failures still fail closed, so timeouts, guard failures, provider errors, and unclassified compaction errors remain visible instead of being hidden.
Why it matters / User impact
The failure happens on the path that decides whether a user turn may proceed after transcript persistence. When it fails closed on a harmless already-compacted transcript, the user loses the expected assistant/tool response even though the transcript source of truth is already in a compacted state. This is especially visible for small-context local models such as
ollama/qwen3:14b, where compaction can happen before the MCP tool workflow completes.Maintainer-ready checklist
Already compactedmeans the compact boundary found the current branch already ends at a compaction state, so the correct lifecycle contract is to skip without mutating transcript contents, session state, or compaction counters.ollama/qwen3:14b, reaches context-engine compaction, and receivesAlready compacted; the lifecycle must return the existing session entry instead of throwingCLI transcript compaction failed.Scope boundary
This patch is limited to the CLI turn compaction lifecycle in
src/agents/command/cli-compaction.tsand its regression coverage. It does not change MCP execution, Ollama routing, model catalog/config behavior, prompt assembly, transcript rewriting, context-engine plugin registration, or the lazy compaction-count reconcile persistence path.Source-of-truth / architecture boundary
The model-consumed source of truth is the active session transcript and the context-engine compaction result for that transcript.
Already compactedmeans the compact boundary found the current branch already ends at a compaction state, so there is no additional transcript rewrite to perform. The fix changes only the decision that maps that known terminal no-op reason into turn control flow; it does not mutate transcript contents, session store entries, or compaction counters for the no-op path.Related PR scan
The issue evidence scan found no related open PRs for the public-contract search terms or direct issue linkage. The
config.plugins.slots.contextEnginereference appears only in the local proof command to select a registered context-engine slot; this patch does not add or modify any public config/schema/API/protocol surface.Root Cause
compactCliTranscriptconverted every thrown context-engine compact error intofailureReason, andrunCliTurnCompactionLifecyclethrows on thatfailureReason. AlthoughclassifyCompactionReason()already mapsAlready compactedtoalready_compacted_recently, the CLI lifecycle only treatedbelow_thresholdas a non-fatal no-op.reconcile is not a functionwarning, which also appeared in the reporter's successful Llama3.1 run and was not the fatal abort path.Real behavior proof
Already compactedforollama/qwen3:14b.runCliTurnCompactionLifecycleproduction path with a registered public context-engine slot selected throughconfig.plugins.slots.contextEngine. The slot's compact boundary throws the reporter's exactAlready compactedmessage forollama/qwen3:14b; no direct helper call bypasses the lifecycle.pnpm --dir /media/vdb/code/ai/aispace/openclaw-worktrees/issue-97625 exec node --import tsx -e "import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { CURRENT_SESSION_VERSION } from 'openclaw/plugin-sdk/agent-sessions'; import { registerContextEngine } from './src/context-engine/registry.ts'; import { runCliTurnCompactionLifecycle } from './src/agents/command/cli-compaction.ts'; const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-97625-slot-proof-')); try { const engineId = 'already-compacted-proof'; const compactCalls = []; registerContextEngine(engineId, () => ({ info: { id: engineId, name: 'Already Compacted Proof Engine' }, async ingest() { return { ingested: false }; }, async assemble(params) { return { messages: params.messages, estimatedTokens: 0 }; }, async compact(params) { compactCalls.push({ force: params.force, currentTokenCount: params.currentTokenCount, compactionTarget: params.compactionTarget }); throw new Error('Already compacted'); } })); const sessionKey = 'agent:main:qwen-proof'; const sessionId = 'session-qwen-proof'; const sessionFile = path.join(dir, 'session.jsonl'); const storePath = path.join(dir, 'sessions.json'); await fs.writeFile(sessionFile, [JSON.stringify({ type: 'session', version: CURRENT_SESSION_VERSION, id: sessionId, timestamp: new Date(0).toISOString(), cwd: dir }), JSON.stringify({ type: 'message', message: { role: 'user', content: 'old ask', timestamp: 1 } }), JSON.stringify({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: 'old answer' }], timestamp: 2 } }), ''].join('\n'), 'utf-8'); const sessionEntry = { sessionId, updatedAt: Date.now(), sessionFile, contextTokens: 1000, totalTokens: 950, totalTokensFresh: true }; const sessionStore = { [sessionKey]: sessionEntry }; await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2), 'utf-8'); const updatedEntry = await runCliTurnCompactionLifecycle({ cfg: { plugins: { slots: { contextEngine: engineId } } }, sessionId, sessionKey, sessionEntry, sessionStore, storePath, sessionAgentId: 'main', workspaceDir: dir, agentDir: dir, provider: 'ollama', model: 'qwen3:14b' }); console.log(JSON.stringify({ completedWithoutThrow: true, sameEntryReturned: updatedEntry === sessionEntry, compactCalls, persistedCompactionCount: sessionStore[sessionKey]?.compactionCount ?? null }, null, 2)); } finally { await fs.rm(dir, { recursive: true, force: true }); }"Already compactedfailure text, logs it as skipped forollama/qwen3:14b, and returns the existing session entry without throwing or recording a new compaction.qwen3:14bmodel, the reporter's Python MCP stdio server, or the exactjarvis__expandtool workflow in this environment. The reported late reconcileTypeErrorwas checked throughsrc/agents/embedded-agent-subscribe.handlers.compaction.test.tsbut was not reproduced locally, so this patch does not change the lazy reconcile import/persistence path.Regression Test Plan
src/agents/command/cli-compaction.test.tsollama/qwen3:14b, reaches context-engine compaction, and receives the reportedAlready compactedreason. Before this patch, the lifecycle threwCLI transcript compaction failed for ollama/qwen3:14b: Already compacted; after this patch, it returns the existing session entry as a no-op.pnpm --dir /media/vdb/code/ai/aispace/openclaw-worktrees/issue-97625 test src/agents/command/cli-compaction.test.tspnpm --dir /media/vdb/code/ai/aispace/openclaw-worktrees/issue-97625 test src/agents/embedded-agent-subscribe.handlers.compaction.test.tsMerge risk
impact:session-state,impact:message-loss,impact:auth-provider,merge-risk:agent-runtime,merge-risk:session-state.below_thresholdandalready_compacted_recently. Existing timeout/provider/guard/unknown failures still producefailureReason, and the regression asserts that no maintenance or compaction-count write is performed for the already-compacted no-op.Patch quality notes