feat(auto-reply): persist ambient room events as transcript rows#99306
Merged
Conversation
Persist room-event observations as durable bare user transcript rows and carry an ambient transcript watermark through session state so Telegram chat windows only include the unpersisted gap. Fixes #99257
obviyus
force-pushed
the
codex/persist-ambient-rows
branch
from
July 3, 2026 02:01
11cb4f2 to
f6817fd
Compare
This was referenced Jul 3, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Problem This Solves
Fixes #99257. In ambient group mode (
room_eventturns) the agent evaluates every unmentioned message but is forced to forget it:get-reply-run.tssuppressed all room-event persistence, so group knowledge lived only in the bounded per-turn chat window (default 50 messages, rebuilt every turn in the uncached prompt tail). Messages older than the window were permanently invisible, compaction could not preserve ambient chat (nothing was persisted to summarize), and every turn re-paid the full window in fresh tokens.Why This Change Was Made
The session transcript becomes the durable group chat log:
#id sender: bodyshape as the Current event display line (byte-identical). No metadata prefix, no window snapshot, no assistant row when the turn stays silent. Coalesced room events persist one row with each observed message as its own attributed line.SessionEntry.ambientTranscriptWatermarks, core-owned insrc/config/sessions/ambient-transcript-watermark.ts) advances only after the row durably persists (onMessagePersistedhook on the user-turn transcript recorder), monotonically (timestamp-first, message-id tiebreak). Every prompt-facing consumer of the raw group history — the group-history entries, the cache-derived chat window (includeNodehook), room-eventInboundHistory(no raw fallback;undefinedwhen empty), and the recovered-thread paths — excludes everything at or before the watermark, so the per-turn window shrinks to the un-persisted gap (normally empty; covers races, coalesced bursts, and pre-feature history). The one intentional exemption is explicit reply targets (a quoted message stays visible even if transcript-owned, so the model never sees a reply to invisible text). All 14 raw-window consumer sites were enumerated as filtered-or-exempt during review.plugin-sdk/session-store-runtimebarrel (narrow channel hot-path helpers).Net effect: append-only rows mean each group message costs its tokens once and is prompt-cache-hit on every later turn; compaction natively summarizes old ambient chat, so the agent retains the gist of messages far beyond any window.
User Impact
Ambient-mode deployments get durable full-chat awareness: the agent remembers group conversation across turns, restarts, and compaction, with strictly better cache economics than widening
historyLimit. Reply gating is unchanged — the default-silent room-event contract (#99144) and the single-canonical-history payload shape (#99256) are preserved and test-pinned.Evidence
src/auto-reply/reply/get-reply-run.media-only.test.ts; dedupe boundary (persisted rows absent from the window, only the gap remains) inextensions/telegram/src/bot-message-context.prompt-context.test.ts.InboundHistory+ recovered threads. Each fix carries a regression test confirmed to fail without it (assertions on the final assembled prompt text, so future fallback paths also fail).src/agents/embedded-agent-runner/replay-history.test.ts), CLI session reseed (src/agents/cli-runner/session-history.test.ts), andstrip-inbound-metapass-through.node scripts/run-vitest.mjsacross all touched test files plus the affected-format sweep;pnpm tsgo,pnpm check:test-types, scoped oxlint on every touched file,pnpm prompt:snapshots:check— all green.node scripts/run-vitest.mjs test/extension-test-boundary.test.ts src/scripts/test-projects.test.ts— green.check:changedgreen on the final HEAD (exit 0) and on the two prior review-round HEADs.InboundHistory+ recovered-thread filtering) was instead manually verified against the enumeration.