Skip to content

fix: filter delivery-mirror from all consumer paths (LLM context, webchat, API)#40716

Closed
kiyoakii wants to merge 3 commits into
openclaw:mainfrom
kiyoakii:fix/filter-delivery-mirror-from-context
Closed

fix: filter delivery-mirror from all consumer paths (LLM context, webchat, API)#40716
kiyoakii wants to merge 3 commits into
openclaw:mainfrom
kiyoakii:fix/filter-delivery-mirror-from-context

Conversation

@kiyoakii

@kiyoakii kiyoakii commented Mar 9, 2026

Copy link
Copy Markdown

Problem

Internal delivery-mirror audit entries (provider=openclaw, model=delivery-mirror) leak into all three consumer paths, causing escalating duplicate assistant messages that degrade over the life of a session:

Fixes #33263, #38061, #39469.
Related: #30316, #39795.

Note: Other open PRs (#38075 etc.) only filter chat.history. This PR covers all three consumer paths — including LLM context (the most impactful) and the sessions API.

Changes

Path File What
LLM context tool-result-context-guard.ts Filter via shared predicate in transformContext pipeline
Webchat UI chat.ts Filter before slice/byte-budget so audit entries don't consume the bounded window
API sessions.ts Filter in sessions.get handler
Predicate transcript.ts New isDeliveryMirrorMessage() using in narrowing (zero type assertions)

The write path is intentionally unchanged — delivery-mirror entries remain in session JSONL as an audit trail. appendCustomEntry() was investigated but SessionManager._persist() defers writes until a type:"message" + role:"assistant" entry exists, making it unreliable for standalone entries. Existing session files already contain old-format entries, so consumer-side filters are needed regardless.

Tests

7 new tests across 2 files covering the predicate (positive match, role/provider/model mismatches, non-object input) and the transformContext integration (strips delivery-mirror; preserves array identity when none present).

Unrelated

Commit f5c618eb fixes a pre-existing oxfmt formatting issue in src/cli/daemon-cli/lifecycle.test.ts.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime agents Agent runtime and tooling size: S labels Mar 9, 2026
@greptile-apps

greptile-apps Bot commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes internal delivery-mirror audit entries (written by appendAssistantMessageToSessionTranscript with provider=openclaw, model=delivery-mirror) from leaking into all three consumer paths — LLM context window, webchat history (chat.history), and the sessions API (sessions.get) — which was causing escalating duplicate assistant messages and Anthropic API rejections.

Key changes:

  • Introduces a shared isDeliveryMirrorMessage(msg: unknown): boolean predicate in transcript.ts using proper in-operator narrowing (zero type assertions), making it safe to call on raw unknown objects from JSONL reads.
  • Filters delivery-mirror entries in tool-result-context-guard.ts before enforceToolResultContextBudgetInPlace, so these internal audit entries no longer inflate the context budget estimate (a secondary correctness improvement beyond de-duplication).
  • A .some() pre-check in tool-result-context-guard.ts preserves array identity for the common case where no delivery-mirror messages are present, avoiding unnecessary allocations.
  • The limit/max comparisons in sessions.ts and chat.ts are correctly applied to the filtered length so the pagination window accounts only for visible messages.
  • The JSONL write path is intentionally untouched — delivery-mirror entries remain on disk as an audit trail.
  • 7 new tests cover the predicate exhaustively (positive match, role/provider/model mismatches, null/undefined/non-object) and two integration tests validate the transformContext behaviour (strips delivery-mirror; preserves array identity when none present).

Confidence Score: 5/5

  • This PR is safe to merge — it is a targeted consumer-side filter with no write-path changes, correct predicate logic, and good test coverage.
  • The implementation is minimal and correct: the isDeliveryMirrorMessage predicate is robustly written with proper type-narrowing, all three identified consumer paths are covered, the ordering of filter-before-budget-enforcement is right, and pagination limits are applied to filtered lengths. Tests exhaustively cover the predicate contract and the transformContext integration. No production data is mutated or lost — delivery-mirror entries remain in JSONL. No significant logic gaps or edge-case risks were found.
  • No files require special attention.

Last reviewed commit: f5c618e

@byungsker

This comment was marked as spam.

@kiyoakii

kiyoakii commented Mar 9, 2026

Copy link
Copy Markdown
Author

@byungsker Thanks! All CI checks succeeded. Good to merge?

@byungsker

This comment was marked as spam.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 25, 2026
@steipete

Copy link
Copy Markdown
Contributor

Dedupe pass: keeping this as the main PR for the Track B assistant-message-shape slice under #69208.

Why this one stays canonical:

  • focused file set around delivery-mirror filtering and consumer paths
  • narrower than Hide transcript-only OpenClaw history artifacts #69217, which carries the same relevant idea mixed with unrelated Microsoft/channel/fetch changes
  • maps cleanly to the delivery-mirror/gateway-injected assistant-artifact branch without trying to solve the broader replay/idempotency family

This does not imply the full umbrella is fixed; it just makes this PR the clean candidate for the assistant-artifact consumer-path work.

@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Summary
The PR adds a shared delivery-mirror predicate/filter, applies it to LLM context, WebChat chat.history, and sessions.get, and updates docs, changelog, and regression tests.

Reproducibility: yes. from source: current main writes provider: "openclaw" / model: "delivery-mirror" assistant rows and the chat.history and sessions.get paths return/project recent transcript messages without this filter. I did not run a live gateway reproduction in this read-only sweep.

Next step before merge
A narrow repair can update the canonical PR/replacement to current main and fix the visible-window edge case without a product decision.

Security
Cleared: The diff is limited to TypeScript filtering, tests, docs, and changelog; it does not add dependencies, workflows, package scripts, secret handling, or new code-execution surfaces.

Review findings

  • [P3] Keep mirror rows out of the bounded history window — src/gateway/server-methods/chat.ts:1670
Review details

Best possible solution:

Refresh the canonical PR on current main, keep delivery-mirror rows as persisted audit data, and filter internal transcript artifacts from model-, WebChat-, and API-facing consumers without letting hidden rows consume visible history limits.

Do we have a high-confidence way to reproduce the issue?

Yes from source: current main writes provider: "openclaw" / model: "delivery-mirror" assistant rows and the chat.history and sessions.get paths return/project recent transcript messages without this filter. I did not run a live gateway reproduction in this read-only sweep.

Is this the best way to solve the issue?

Yes, with a rebase caveat: consumer-boundary filtering is the narrow maintainable fix because it preserves the audit write path while hiding internal artifacts. The updated branch should account for current main's bounded session-history readers so filtering happens before visible limits are finalized.

Full review comments:

  • [P3] Keep mirror rows out of the bounded history window — src/gateway/server-methods/chat.ts:1670
    This filters after chat.history has already requested only max recent rows from the transcript. If those tail rows include delivery-mirror artifacts, hidden rows still consume the requested window and clients can receive fewer visible messages than requested; pull enough rows or make the recent reader count only visible messages before applying the limit.
    Confidence: 0.78

Overall correctness: patch is correct
Overall confidence: 0.76

Acceptance criteria:

  • pnpm test src/agents/pi-embedded-runner/tool-result-context-guard.test.ts src/config/sessions/sessions.test.ts src/gateway/server.chat.gateway-server-chat-b.test.ts
  • pnpm test src/gateway/session-utils.fs.test.ts src/gateway/server-methods/server-methods.test.ts
  • pnpm check:changed

What I checked:

Likely related people:

  • vincentkoc: The provided timeline shows a narrow repair pushed by vincentkoc to keep this PR canonical, and current main also shows recent maintenance by Vincent Koc. (role: recent maintainer and branch repair owner; confidence: medium; commits: 3b99a1a159da, 89a15fddaf84; files: src/gateway/server-methods/chat.ts, src/gateway/server-methods/sessions.ts, src/config/sessions/transcript.ts)
  • steipete: A maintainer comment explicitly scoped this PR as the canonical Track B assistant-artifact consumer-path candidate under the duplicate transcript umbrella. (role: maintainer triage owner; confidence: medium; commits: 585ce38015ef; files: src/gateway/server-methods/chat.ts, src/gateway/server-methods/sessions.ts, src/agents/pi-embedded-runner/replay-history.ts)
  • andyylin: The current changelog records adjacent work on redundant delivery-mirror transcript appends, and the PR timeline later mentions/subscribes this person. (role: adjacent delivery-mirror maintainer; confidence: low; files: CHANGELOG.md, src/config/sessions/transcript.ts)

Remaining risk / open question:

  • The PR is reported mergeable: false, and current main's bounded async history readers changed the integration point this branch must adapt to.
  • Tests were not executed in this read-only review; the discussion reports prior targeted validation and pnpm check:changed, but not proof against the exact current main SHA.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 89a15fddaf84.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 27, 2026
kiyoakii and others added 3 commits April 28, 2026 18:53
…PI responses

delivery-mirror entries (provider=openclaw, model=delivery-mirror) are internal
cross-channel delivery audit records written by
appendAssistantMessageToSessionTranscript(). They use appendMessage() which
creates type:"message" entries indistinguishable from real LLM responses,
causing them to leak into buildSessionContext() and API responses.

This causes duplicate assistant messages in the LLM context window (escalating
from 2x to 6-8x over a session), duplicate renders in webchat UI, and raw
audit entries in API responses.

Fix: filter delivery-mirror messages at the three consumer points:
- transformContext pipeline (LLM context)
- chat.history handler (webchat UI)
- sessions.get handler (API)

Add isDeliveryMirrorMessage() predicate co-located with the write path in
transcript.ts. Add regression tests for all filter paths.

The write path (appendMessage) is intentionally unchanged - delivery-mirror
entries remain in the JSONL as an audit trail. appendCustomEntry() was
considered but _persist() in SessionManager defers writes until an assistant
message exists, making it unreliable for standalone audit entries.

Fixes #33263, #38061, #39469
Related: #30316, #39795
- Move delivery-mirror filter before slice/byte-budget in chat.history
  so internal entries do not consume bounded window slots
- Drop unnecessary .toLowerCase() on role check for consistent
  strict equality across all three predicate fields
- Add symmetric test case for model match with provider mismatch
@vincentkoc

Copy link
Copy Markdown
Member

ProjectClownfish pushed a narrow repair to this branch so the original contributor path can stay canonical.

Source PR: #40716
Validation: pnpm -s vitest run src/agents/pi-embedded-runner/tool-result-context-guard.test.ts src/config/sessions/sessions.test.ts; pnpm check:changed
Contributor credit is preserved in the branch history and PR context.

@vincentkoc vincentkoc force-pushed the fix/filter-delivery-mirror-from-context branch from a237940 to 3b99a1a Compare April 28, 2026 18:53
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: M and removed size: S labels Apr 28, 2026
@vincentkoc vincentkoc added clownfish:human-review clawsweeper Tracked by ClawSweeper automation and removed clownfish:merge-ready labels Apr 28, 2026
@kiyoakii kiyoakii closed this by deleting the head repository May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui clawsweeper Tracked by ClawSweeper automation docs Improvements or additions to documentation gateway Gateway runtime size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Webchat duplicates assistant replies via delivery-mirror transcript entry

4 participants