Skip to content

feat(ai): sliding-window context — compaction, prompt caching, tool-result eliding, agent memory - #1637

Merged
2witstudios merged 30 commits into
masterfrom
pu/sliding-window-compacted-context
Jun 12, 2026
Merged

2witstudios merged 30 commits into
masterfrom
pu/sliding-window-compacted-context

Conversation

@2witstudios

@2witstudios 2witstudios commented Jun 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

Agents can never run out of context, and users never experience compaction. This PR ships a sliding-window context system plus the cache-aware message assembly it builds on, in four phases (one commit each, built by parallel worktree agents and verified independently before each merge).

PR 1 — Cache-aware prompt assembly (`apps/web/src/lib/ai/core/prompt-assembly.ts`)

  • The system prompt is now byte-stable across turns: volatile per-turn content (timestamp, @mention, /command prompts) moves out of the system string and rides as a trailing text part on the last user message (never persisted).
  • Anthropic prompt-cache breakpoints via `providerOptions.openrouter.cacheControl` on the final message (re-marked per loop step via `prepareStep`) and on the stable boundary message. No-op-safe for non-Anthropic and local providers; OpenAI/Gemini benefit from the stable prefix implicitly.
  • Tool arrays made byte-deterministic (sorted mcpTools + integration tool merges, with determinism tests).
  • Expected: ~75–85% input-cost reduction on multi-step agent turns (`AGENT_MAX_STEPS = 100` re-sends the full prefix every step today, uncached). Measurable from day one via existing `cachedInputTokens` tracking.

PR 2 — Core sliding-window compaction

  • New `conversation_compactions` table (migration `0161`): per-conversation `{summary, pointer, version}`, keyed by conversationId string with no FK (covers legacy page chats, ask_agent, and v1 threads that have no conversations row).
  • Pure core `packages/lib/src/ai/context-window.ts`: model context = stable system + tools → synthetic summary user-message (`<conversation_summary>`, never persisted) → verbatim tail starting at a user turn (structurally cannot orphan tool_use/tool_result pairs). Soft threshold 0.75 of the model's context window emits an async compaction plan; hard 0.95 does inline emergency truncation (never blocks, never errors).
  • Nine-section structured summarizer prompt (intent, facts, rules verbatim, people, restorable page-ID pointers, errors, decisions, pending tasks, current state); progressive updates; `COMPACTION_MODEL` env override, else the conversation's own model; version-guarded upsert against races; usage tracked under new source `compaction`.
  • Wired into all four entry points: page chat, global assistant (replaces the old 10-message hard slice), v1 server-managed threads, and ask_agent. Invalidation hooks on message edit/delete endpoints. UI always renders full history from Postgres — compaction only changes what the model sees.
  • Admin-gated via one `canUseCompaction` predicate (same pattern as `canUseCommands`); non-admins get exact legacy behavior.

PR 3 — Cross-request tool-result eliding (`packages/lib/src/ai/tool-result-eliding.ts`)

  • Stale tool outputs (read_page bodies, search results — routinely 2–10k tokens each, replayed forever) are replaced with restorable stubs; tool inputs stay, so everything is re-fetchable and the pair structure survives.
  • Chunk-aligned boundaries (advance at most once per 8 assistant turns) keep replayed bytes stable between crossings, so eliding composes with the prefix cache instead of fighting it. The compaction pointer overrides the chunk boundary so the two mechanisms never double-elide.
  • New `context-assembly.ts` seam composes sanitize → compaction cut → elide in one place; its boundary feeds the cache breakpoint so they coincide by construction.

PR 4 — Agent memory + efficiency

  • Per-agent durable memory: a child page titled "Agent Memory" is injected into the stable system section and the agent is instructed to maintain it — memory that survives compaction, in PageSpace's own filesystem, no schema change.
  • Provider factory accepts the already-loaded user row, eliminating up to two redundant user queries per chat request.

Post-review fixes (bot review — all addressed)

  • `renderTranscript` handles UIMessage SDK format parts (`type: 'tool-{name}'` with `input`/`output` fields) so real conversation histories don't silently omit tool invocations from compaction summaries.
  • Removed `execute_tool` from `DEFAULT_ELIDABLE_TOOLS` — in search-exposure mode it dispatches write operations whose results must not be re-played.
  • Message edit/delete handlers in all three route files now await compaction invalidation (not fire-and-forget), so a concurrent request cannot read a stale summary.
  • Both `softDeleteConversation` implementations likewise await invalidation.
  • `runCompaction` accumulates `totalInputTokens`/`totalOutputTokens` across both `summarize()` calls when a re-condense pass runs; previously only the second call's usage was billed.
  • Security: `maskIdentifier()` applied to `conversationId` in the compaction debug log (CodeQL log-injection fix).
  • `elideStaleToolOutputs`: `toolName` now derived from `part.type.replace(/^tool-/, '')` when `part.toolName` is absent (UIMessage SDK format), so UIMessage parts are correctly guarded against elision.
  • Soft compaction guard: `cutIdx < tail.length` added so the plan never summarizes the entire tail with nothing left verbatim.
  • `summaryTokens` now estimated from the formatted summary message (post-`<conversation_summary>` wrapper), correcting a ~30-token undercount in context-window budget calculations.
  • `hasSummary` detection in `context-assembly.ts` also requires `firstText.startsWith('<conversation_summary>')`, preventing id-less client-supplied UIMessages from being misclassified as synthetic summaries.
  • Global assistant route: visual-content injection now preserves the full prepared tail; earlier messages are no longer silently dropped (was slicing to only last `MAX_MESSAGES_WITH_IMAGES` before conversion).
  • `prepareHistoryForModel` in the page-chat route now receives the resolved provider/model (post-catalog substitution) so context-window budget calculations use the correct model.
  • `ask_agent`: synthetic summary and tail are now passed together to `convertToModelMessages` instead of being manually split — eliminates the future-proofing risk of multi-part summary messages.
  • `WRITE_TOOLS` exported from `tool-filtering.ts` and used directly in `context-assembly.ts`, replacing a stale copy missing `find_and_replace`, `insert_content`, trigger, role, and command write-tools.
  • `CompactionStateRow` replaced with `SelectConversationCompaction` from the DB schema, eliminating type drift (`source` was widened to `string` in the old interface).
  • Compaction-aware test mocks corrected to use `inputTokens`/`outputTokens` (Vercel AI SDK field names); test fixture timestamps fixed for IDs > 9 (`m10` → `:10` not `:00`).
  • Added 2 regression tests for `ask_agent` compaction path: full `prepared.messages` passed to `convertToModelMessages`; `runCompaction` fired when `pendingCompaction` is set.

Test plan

  • 156+ new tests across the four phases plus review fixes (pure cores zero-mock; service tests with mocked repo/generateText; route-level tests for gating, persistence-leak, and determinism). Full ai/core area: 535+ passing.
  • Root `bun run typecheck` (drives web build + lint): 13/13 green.
  • The 18 failures in 4 files on `bun run test:unit` (`admin-role-version`, `activity-tools`, `grouping`, v1 `route.test`) reproduce identically on a detached `origin/master` checkout in the same environment — pre-existing upstream failures, consistent with master's currently red Test Suite CI; zero new failures introduced by this branch.
  • Migration regenerated twice via `bun run db:generate` after master-side migration collisions (0159→0160→0161), byte-identical table SQL each time; never hand-edited.
  • Manual verification path: as an admin, hold a long agent conversation past the soft threshold → `conversation_compactions` row appears with the structured summary; next request sends summary + tail while the UI shows full history; editing a pre-pointer message deletes the row; consecutive turns on Anthropic models report `cachedInputTokens > 0`.

🤖 Generated with Claude Code

Review rounds (post-open)

Two full review cycles are incorporated on top of the original four phases:

Round 1 — bot reviews + 16 human threads (commits 0cf8e2ca2…f4109e4d5, b46361169): summarizer handles AI SDK tool-{name} parts; execute_tool excluded from elision (write-dispatch risk); invalidation awaited in all edit/delete hooks; usage accumulated across re-condense; CodeQL log-injection masked; admin payload viewer mirrors production volatile-context placement; validateTailIntegrity wired into buildModelContext; shared isSyntheticSummaryMessage sentinel; ask_agent upgraded to the full sanitize→compact→elide seam; global-route const refactor + post-compaction context tracking; four new test suites (context-assembly, compaction-repository, prepare-context, compaction-gating). All 22 threads resolved with replies.

Round 2 — context-sizing/billing/security findings (commit 852aabef5): token estimator counts AI SDK v5 tool parts (compaction thresholds now see real sizes); compaction spend tracked even on lost version races; agent memory framed as untrusted quoted data (<agent_memory_data> envelope); empty summaries never persisted; compaction reads scoped by source/pageId; invalidation is now a version-bumping tombstone (closes the edit-during-first-compaction stale-summary race); maxOutputTokens + final clamp prevent paid summary-over-cap loops; global conversation delete only invalidates rows the caller actually deleted.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added AI conversation history compaction to optimize context windows with automatic summarization (admin-only).
    • Introduced agent memory system for page agents to store and retrieve persistent memory across conversations.
  • Improvements

    • Enhanced conversation history preparation with improved caching and cache breakpoint management.
    • Added intelligent elision of stale tool outputs while preserving side-effectful operations.
    • Message edits and deletions now properly invalidate compaction state to prevent stale summaries.

2witstudios and others added 12 commits June 11, 2026 19:53
Implements PR 1 of the Sliding-Window Context epic. Moves volatile per-turn
data (timestamp/mention/command) out of the system prompt and onto the last
user message so the stable system prefix stays byte-identical across turns,
enabling provider prefix caching (~75–85% input-cost reduction on multi-step
agent runs). Applies explicit OpenRouter/Anthropic cache breakpoints at the
message level via providerOptions, and re-applies them per step via
prepareStep so mid-loop tool results are cached. Sorts dynamically merged
MCP and integration tool keys for deterministic tool-array order.

- New `prompt-assembly.ts`: buildVolatileTurnContext, appendTurnContextToLastUserMessage, withCacheBreakpoints
- Chat route: stable system = base+pageTree+toolDiscovery; volatile block on last user message; prepareStep breakpoints
- Global route: same surgery; stable order base→TOOL_DISCOVERY→instructions→drive→awareness→pageTree→nonCore
- integration-tool-resolver.ts: sorted keys on both page-agent and global-assistant resolvers
- Chat/global routes: Object.keys(mcpToolSchemas).sort() before merge
- complete-request-builder.ts (admin viewer): mirrors new assembly order, removes volatile sections
- 28 new/updated tests; typecheck + lint green; pre-existing v1-route AbortSignal failures unrelated

prepareStep finding: ai@^5.0.54 fully supports messages?: Array<ModelMessage> in
PrepareStepResult — re-applying withCacheBreakpoints(stepMessages, 0) per step is solid.
@openrouter/ai-sdk-provider@1.5.4 reads providerOptions.openrouter.cacheControl at
message-level; no-op for non-Anthropic models and local providers (ollama/lmstudio/azure/glm).
v1 completions route has no volatile sections — no changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `conversation_compactions` table (no FK; covers all 4 AI entry
  points including row-less conversationIds). Migration 0159.
- `packages/lib/src/ai/context-window.ts`: pure buildModelContext
  (soft/hard threshold, pointer cut, emergency truncation, summary-over-
  cap), plus exported internals applyPointerCut, findUserTurnCut,
  validateTailIntegrity, formatSummaryMessage, stripNonTextForSummarizer.
- `packages/lib/src/ai/summarization-prompt.ts`: buildSummarizationPrompt
  with 9 mandatory sections, VERBATIM rule preservation, progressive
  framing on previousSummary, explicit token-cap instruction.
- Extend USAGE_SOURCES with 'compaction' + USAGE_SOURCE_LABELS entry.
- Service glue in apps/web/src/lib/ai/core/compaction/:
    - compaction-gating.ts: canUseCompaction (admin-only, mirroring
      canUseCommands — widen by changing this predicate only)
    - compaction-repository.ts: getState/upsertState/invalidate with
      optimistic versioning (onConflictDoNothing on first insert, version-
      guarded UPDATE for subsequent writes)
    - compaction-service.ts: runCompaction — 60s gap check, generateText
      with COMPACTION_MODEL env fallback, one re-condense pass if over
      cap, version-guarded upsert, loser discards; never throws; tracks
      usage with source='compaction'
    - prepare-context.ts: prepareConversationContext — gate check,
      buildModelContext, scheduleCompaction wrapping after()
- 40 new tests: 32 pure-core (zero mocks), 8 service (mocked repo +
  generateText). All green; 4 pre-existing failures unchanged.
- New @pagespace/lib subpath exports: ai/context-window,
  ai/summarization-prompt (+ typesVersions entries).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n hooks

Wire prepareConversationContext into all four AI entry points so admin users
get sliding-window compaction invisibly. Non-admin users get exact legacy
behavior (early gate in canUseCompaction). Summary message is never persisted.

Entry points wired (Task 7–8):
- api/ai/chat/route.ts: originalMessages = full sanitized history for UI,
  baseMessages = compacted tail for model; stableBoundaryIndex = hasSummary ? 1 : 0
  (was hardcoded 0); scheduleCompaction() called in onFinish
- api/ai/global/[id]/messages/route.ts: same pattern; injectVisualContent helper
  factored out; compaction inserted after finalTools+finalSystemPrompt are known
- api/v1/chat/completions/route.ts: isThreadMode && !clientManagesHistory branch only;
  v1ScheduleCompaction() called after settle() on clean exit
- agent-communication-tools.ts (ask_agent): user role looked up best-effort (try-catch);
  void runCompaction(pendingCompaction) fire-and-forget because after() from
  next/server is unavailable inside tool execution mid-stream

PreparedContext extended (Task 7):
- Added pendingCompaction field for callers where after() is unavailable
- Non-admin early-return now includes pendingCompaction: null

Invalidation hooks (Task 9):
- api/ai/chat/messages/[messageId]/route.ts: PATCH + DELETE check message.createdAt
  <= compactedUpToCreatedAt before invalidating (precise, no false positives)
- api/ai/page-agents/.../messages/[messageId]/route.ts: same createdAt check
- api/ai/global/[id]/messages/[messageId]/route.ts: always invalidates (Message
  type lacks createdAt; conservative invalidation is safe)
- conversation-repository.softDeleteConversation: fire-and-forget invalidate
- global-conversation-repository.softDeleteConversation: same

DM messages route (api/messages/[conversationId]/[messageId]) intentionally skipped:
DM conversations are not AI chat — compaction never applies there.

Tests: agent-communication-tools test file mocks prepareConversationContext and
runCompaction; try-catch on the role DB query handles test mock chain shape.
All compaction unit tests pass; 2 pre-existing AbortSignal v1 test failures
are environment-specific (Bun/Request incompatibility), not regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both this branch and master independently generated migration 0159.
Removing ours so it can be regenerated as 0160 on top of master's
snapshot lineage (migrations must never be hand-edited).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds chunk-aligned tool-output elision that cuts 40–70% of replayed
history tokens in long conversations with no orphan-pair risk.

- packages/lib/src/ai/tool-result-eliding.ts: pure module with
  computeElisionBoundary (chunk-aligned, compactionPointer override)
  and elideStaleToolOutputs (replaces large read-tool outputs with
  deterministic stubs; never touches write-tool results or small outputs)
- packages/lib/src/ai/__tests__/tool-result-eliding.test.ts: 19 tests
  covering boundary determinism, stub shape, chunk-alignment invariant,
  non-elidable/write-tool guards
- apps/web/src/lib/ai/core/context-assembly.ts: unified seam
  (prepareHistoryForModel) composing sanitize → compaction → elide;
  both chat and global routes now use it instead of bare
  prepareConversationContext; stableBoundaryIndex and elision boundary
  coincide by construction
- Recall polish: regex_search, list_conversations, read_conversation
  descriptions updated to mention recovering condensed/elided history

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ng (PR 4)

- New agent-memory.ts: getAgentMemoryContext() finds the non-trashed "Agent Memory"
  child page of an AI_CHAT agent, caps content at ~2k tokens, and returns '' on any
  error or permission denial. buildAgentMemorySection() wraps content + standing
  instruction into the stable system-prompt block.
- chat route: injects agentMemoryPrompt into the STABLE system section (AI_CHAT only,
  positioned between pageTree and toolDiscovery) — cache invalidates once per write,
  not per turn.
- provider-factory: adds optional UserProviderRow + ProviderFactoryOptions so callers
  can supply a pre-loaded user row and skip redundant DB selects. chat route threads
  its already-loaded user through createAIProvider and updateUserProviderSettings,
  eliminating two extra round-trips per request.
- 14 new agent-memory tests + 6 new provider-factory threading tests, all green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rge (round 2)

Master gained its own 0160 (calendar multi-drive sharing). Regenerating
ours as 0161 on master's lineage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@2witstudios, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 16 minutes. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4138f5df-f849-46ec-a48c-8fb8c4faa0bd

📥 Commits

Reviewing files that changed from the base of the PR and between 852aabe and 63449be.

📒 Files selected for processing (11)
  • apps/web/src/app/api/ai/global/[id]/messages/[messageId]/__tests__/route.test.ts
  • apps/web/src/app/api/ai/global/[id]/messages/[messageId]/route.ts
  • apps/web/src/lib/ai/core/compaction/__tests__/compaction-repository.test.ts
  • apps/web/src/lib/ai/core/compaction/compaction-repository.ts
  • apps/web/src/lib/ai/tools/agent-communication-tools.ts
  • apps/web/src/lib/logging/mask.ts
  • apps/web/src/lib/repositories/global-conversation-repository.ts
  • packages/db/drizzle/0163_colossal_slyde.sql
  • packages/db/drizzle/meta/0163_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/lib/package.json
📝 Walkthrough

Walkthrough

This PR implements a production-grade sliding-window conversation compaction system for AI chats. It adds persisted compaction state tracking, summarization orchestration, unified history preparation with cache-aware streaming, deterministic tool ordering, and invalidation flows when edited or deleted messages affect compacted history.

Changes

AI Conversation Compaction

Layer / File(s) Summary
Database schema and persistence
packages/db/drizzle/0162_watery_jean_grey.sql, packages/db/src/schema/ai-compaction.ts, packages/db/package.json, packages/db/src/schema.ts, packages/db/drizzle/meta/_journal.json
New conversation_compactions table stores summary text, token estimates, compaction progress markers, versioning, model attribution, and timestamps; scoped by source (page/global) and optional pageId for agent conversations.
Compaction repository and gating
apps/web/src/lib/ai/core/compaction/compaction-repository.ts, apps/web/src/lib/ai/core/compaction/compaction-gating.ts, apps/web/src/lib/ai/core/compaction/__tests__/compaction-repository.test.ts, apps/web/src/lib/ai/core/compaction/__tests__/compaction-gating.test.ts
Repository implements scope-aware state reads, CAS-style upserted with version guarding, and tombstone invalidation. Gating enforces admin-only access at launch.
Context-window cutting and summary formatting
packages/lib/src/ai/context-window.ts, packages/lib/src/ai/__tests__/context-window.test.ts, apps/web/src/lib/ai/core/tool-filtering.ts
Utilities compute tail start from persisted pointers or timestamp fallback, align cuts to user-turn boundaries, validate tool-result integrity, format summaries with synthetic sentinel markup, and strip non-text content for summarization. WRITE_TOOLS set is exported for reuse.
Summarization prompt and tool-result elision
packages/lib/src/ai/summarization-prompt.ts, packages/lib/src/ai/tool-result-eliding.ts, packages/lib/src/ai/__tests__/summarization-prompt.test.ts, packages/lib/src/ai/__tests__/tool-result-eliding.test.ts
Prompt builder renders transcripts with tool markers, enforces token caps, and supports progressive framing from prior summaries. Elision module chunks assistant turns and replaces old tool outputs with refetch stubs, excluding write-side-effect tools.
Compaction service and scheduling
apps/web/src/lib/ai/core/compaction/compaction-service.ts, apps/web/src/lib/ai/core/compaction/prepare-context.ts, apps/web/src/lib/ai/core/context-assembly.ts, apps/web/src/lib/ai/core/compaction/__tests__/compaction-service.test.ts, apps/web/src/lib/ai/core/compaction/__tests__/prepare-context.test.ts, apps/web/src/lib/ai/core/__tests__/context-assembly.test.ts
Service orchestrates LLM summarization with time-gap enforcement, re-condensing on token overflow, and version-guarded persistence. Context preparation gates compaction eligibility, loads state, builds model context, and defers execution via after(). History assembly sanitizes, compacts, and elides in a unified pipeline.
Prompt assembly and cache breakpoints
apps/web/src/lib/ai/core/prompt-assembly.ts, apps/web/src/lib/ai/core/__tests__/prompt-assembly.test.ts
Builders deterministically concatenate volatile per-turn context (timestamp/mentions/command), append to last user message without mutating, and apply ephemeral cache-control markers to stable boundaries and final messages.
Provider factory user-row threading
apps/web/src/lib/ai/core/provider-factory.ts, apps/web/src/lib/ai/core/__tests__/provider-factory.test.ts
Provider factory optionally accepts pre-loaded user rows to skip redundant database selects during provider/model resolution.
Agent memory system
apps/web/src/lib/ai/core/agent-memory.ts, apps/web/src/lib/ai/core/__tests__/agent-memory.test.ts
Loads durable "Agent Memory" child page, enforces view permissions, caps by token count, and provides standing instructions for memory lifecycle.
Deterministic tool ordering
apps/web/src/lib/ai/core/integration-tool-resolver.ts, apps/web/src/lib/ai/core/__tests__/integration-tool-resolver.test.ts
Integration tool resolvers now sort tool keys alphabetically before returning, ensuring stable output across requests.
Page chat route integration
apps/web/src/app/api/ai/chat/route.ts
Route imports agent-memory helpers, threads user rows to provider factory, loads system prompt early, builds agent-memory section for AI_CHAT pages, funnels history through prepareHistoryForModel seam, generates volatile per-turn context at stream time, applies cache breakpoints, sorts MCP tools deterministically, and schedules post-response compaction.
Global chat route integration
apps/web/src/app/api/ai/global/[id]/messages/route.ts
Route omits volatile sections from system prefix, uses unified history-preparation seam, builds per-turn context and cache breakpoints at stream time, sorts MCP tools deterministically, uses sanitized full history for UI streaming, and schedules post-response compaction.
v1 API thread-mode compaction
apps/web/src/app/api/v1/chat/completions/route.ts
Thread-mode requests invoke prepareConversationContext to enable compaction when server manages history, prepend synthetic summaries as user messages, and schedule post-stream compaction execution.
Agent communication tools
apps/web/src/lib/ai/tools/agent-communication-tools.ts, apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts
ask_agent looks up caller role, uses prepareHistoryForModel to enable compaction, converts prepared tail to model messages, prepends synthetic summaries, and triggers fire-and-forget compaction after generation.
Tool recovery guidance
apps/web/src/lib/ai/tools/page-read-tools.ts, apps/web/src/lib/ai/tools/search-tools.ts
Tool descriptions updated to document how condensed or elided history can be recovered via dedicated read and search tools.
Message edit/delete invalidation
apps/web/src/app/api/ai/chat/messages/[messageId]/route.ts, apps/web/src/app/api/ai/global/[id]/messages/[messageId]/route.ts, apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/messages/[messageId]/route.ts
PATCH and DELETE handlers now invalidate compaction state when edited or deleted messages fall within or before the compacted range, guarding against stale summaries. Failures are logged but do not affect the main request.
Conversation deletion invalidation
apps/web/src/lib/repositories/conversation-repository.ts, apps/web/src/lib/repositories/global-conversation-repository.ts
softDeleteConversation now invalidates compaction state after marking messages inactive, with scope-specific identifiers (page vs. global).
Usage attribution and token estimation
packages/lib/src/monitoring/usage-source.ts, packages/lib/src/monitoring/ai-context-calculator.ts, packages/lib/src/monitoring/__tests__/ai-context-calculator.test.ts
Adds 'compaction' as a canonical usage source and extends token estimation to account for AI SDK v5 tool parts with input/output fields.
Complete request builder
apps/web/src/lib/ai/core/complete-request-builder.ts
System prompt assembly now separates stable and volatile sections, includes TOOL_DISCOVERY_PROMPT, and reports volatile-turn token estimates separately.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • 2witstudios/PageSpace#1198: Both PRs extend PATCH/DELETE message route handlers to add post-update/delete side effects—main PR adds compaction-state invalidation while retrieved PR adds realtime websocket broadcasts.
  • 2witstudios/PageSpace#633: Both PRs modify message PATCH/DELETE handlers to perform an awaited invalidation step after editing or deleting a message, with overlapping implementations in the same route files.
  • 2witstudios/PageSpace#1605: Both PRs reorganize the chat route's system-prompt and streaming assembly flow around cache boundaries and volatile context injection.

🐰 Compaction brings order to the chaos,
Summaries distilled from lengthy paths,
Cache breakpoints mark the seams,
History trimmed to fit the dreams!
✨📝

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/sliding-window-compacted-context

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0397ab88ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/lib/src/ai/summarization-prompt.ts
Comment thread packages/lib/src/ai/tool-result-eliding.ts
Comment thread apps/web/src/app/api/ai/chat/messages/[messageId]/route.ts Outdated
Comment thread apps/web/src/lib/ai/core/compaction/compaction-service.ts
Comment thread apps/web/src/lib/ai/core/compaction/compaction-service.ts Fixed
2witstudios and others added 2 commits June 11, 2026 23:19
…ute_tool elision, await invalidation, accumulate usage

- summarization-prompt.ts: renderTranscript now handles UIMessage-format parts
  (type='tool-{name}', input/output fields) so real persisted conversations
  don't silently drop tool invocations from compaction summaries (P1)
- tool-result-eliding.ts: remove execute_tool from DEFAULT_ELIDABLE_TOOLS;
  in search-exposure mode it dispatches write operations whose results must
  not be replaced with a re-call stub (P1)
- chat, global, page-agent message routes: await compaction invalidation on
  edit/delete instead of fire-and-forget so stale summaries can't be read
  by a concurrent request before the DELETE/PATCH returns (P2)
- compaction-service.ts: accumulate inputTokens/outputTokens across both
  summarize() calls when a re-condense pass runs; previously the first
  call's usage was silently discarded (P2)
- new test coverage: UIMessage tool part rendering, execute_tool exclusion,
  output truncation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…njection

CodeQL flagged console.debug at line 149 where user-provided conversationId
was logged verbatim. Add maskIdentifier import and wrap the id so it is
truncated to first4...last4 format before reaching the log sink.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (3)
apps/web/src/lib/ai/core/compaction/compaction-repository.ts (1)

5-18: ⚡ Quick win

Reuse the schema-inferred row type instead of duplicating it.

CompactionStateRow is already drifting from the table contract: Line 7 widens source to string, while conversation_compactions.source is constrained to 'page' | 'global'. Keeping a hand-maintained copy here makes future schema changes easy to miss; import SelectConversationCompaction from @pagespace/db/schema/ai-compaction and alias CompactionStateRow to it.

Suggested refactor
-import { conversationCompactions } from '`@pagespace/db/schema/ai-compaction`';
+import {
+  conversationCompactions,
+  type SelectConversationCompaction,
+} from '`@pagespace/db/schema/ai-compaction`';
 
-export interface CompactionStateRow {
-  conversationId: string;
-  source: string;
-  pageId: string | null;
-  summary: string;
-  summaryTokens: number;
-  compactedUpToMessageId: string | null;
-  compactedUpToCreatedAt: Date | null;
-  summaryVersion: number;
-  summarizerModel: string | null;
-  lastCompactedAt: Date | null;
-  createdAt: Date;
-  updatedAt: Date;
-}
+export type CompactionStateRow = SelectConversationCompaction;

As per coding guidelines, TypeScript code should use proper types rather than a widened duplicate contract here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/ai/core/compaction/compaction-repository.ts` around lines 5
- 18, Replace the hand-maintained CompactionStateRow with the schema-inferred
type: import SelectConversationCompaction from the
`@pagespace/db/schema/ai-compaction` module and alias or re-export it as
CompactionStateRow (i.e., make CompactionStateRow =
SelectConversationCompaction) so the in-code type exactly matches the DB table
contract and avoids widening fields like source; update any local references to
CompactionStateRow to use the imported/aliased type.

Source: Coding guidelines

apps/web/src/lib/ai/core/context-assembly.ts (1)

37-49: ⚡ Quick win

Stop mirroring the write-tool allowlist in two places.

WRITE_TOOLS_FOR_ELISION is explicitly copied from tool-filtering.ts. The next write tool added there can be elided here by accident, which hides a side-effectful tool result from the model and breaks follow-up reasoning. Export the canonical set once and consume it from both paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/ai/core/context-assembly.ts` around lines 37 - 49, The
duplicated WRITE_TOOLS_FOR_ELISION set causes drift; stop mirroring it by
exporting the canonical write-tool allowlist from its original module (the
symbol that currently defines the list in tool-filtering) and import/use that
exported constant here instead of defining WRITE_TOOLS_FOR_ELISION locally;
update references in context-assembly.ts to consume the imported symbol (remove
the local new Set([...]) and any duplicate entries) so both consumers share the
single source of truth.
apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts (1)

99-106: ⚡ Quick win

Add one regression test that actually exercises the compaction path.

The new mock always passes messages through unchanged and forces pendingCompaction: null, so this suite never covers the new { synthetic summary + tail } branch or the follow-up runCompaction() dispatch. That leaves the compaction wiring effectively untested even though ask_agent now depends on it. Add a case where prepareConversationContext() returns a synthetic first message plus pendingCompaction, then assert the exact generateText({ messages }) payload and that compaction is triggered.

Based on PR objectives, ask_agent now "build[s] compaction-aware model messages and trigger[s] follow-up compaction."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts` around
lines 99 - 106, Add a regression test that mocks prepareConversationContext to
return a synthetic summary message followed by the tail messages and a non-null
pendingCompaction, then call ask_agent and assert that generateText was invoked
with the exact messages array (synthetic summary first + tail) and that
runCompaction was subsequently dispatched; specifically override vi.mock for
prepareConversationContext to return { messages: [syntheticSummary, ...tail],
pendingCompaction: { /* id or payload */ }, scheduleCompaction: vi.fn() }, spy
on generateText and runCompaction, call ask_agent with the conversation that
triggers compaction, and assert generateText received the composed messages and
runCompaction was called with the pendingCompaction payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/api/ai/chat/route.ts`:
- Around line 915-923: The call to prepareHistoryForModel is using pre-factory
preference variables currentProvider/currentModel; change it to use the actual
computed runtime pair (resolvedProvider and resolvedModelName) so compaction
budgets reflect the real provider/model. Locate the prepareHistoryForModel
invocation and replace currentProvider with resolvedProvider and currentModel
with resolvedModelName (or the variable that holds the resolved model name)
while keeping the rest of the arguments (systemPrompt, tools, conversationId,
etc.) unchanged.

In `@apps/web/src/app/api/ai/global/`[id]/messages/route.ts:
- Around line 885-891: prepared.messages is being truncated by using
slice(-MAX_MESSAGES_WITH_IMAGES) so earlier tail messages chosen by
prepareHistoryForModel() are dropped; instead preserve the full
prepared.messages array and only apply injectVisualContent to its last
MAX_MESSAGES_WITH_IMAGES items. Change the logic around prepared.messages,
calling injectVisualContent on
prepared.messages.slice(-MAX_MESSAGES_WITH_IMAGES) to produce processedTail,
then merge that processedTail back into the full messages sequence before
calling convertToModelMessages (and keep using prepared.summaryText when
constructing modelMessages) so only visual-content injection is limited while
the rest of the context remains intact.

In `@apps/web/src/lib/ai/core/compaction/__tests__/compaction-service.test.ts`:
- Around line 65-68: The mocks for mockGenerateText currently return
usage.promptTokens/completionTokens while runCompaction reads usage.inputTokens
and usage.outputTokens; update the mock fixtures (the mockGenerateText responses
at the three locations) to include usage.inputTokens and usage.outputTokens with
the same numeric values used previously, and then change the test assertions
that verify recorded token totals to assert on the aggregated input/output token
sums produced by runCompaction (referencing runCompaction, mockGenerateText,
usage.inputTokens and usage.outputTokens to locate the relevant code).

In `@apps/web/src/lib/ai/core/compaction/compaction-gating.ts`:
- Around line 7-10: The canUseCompaction function currently does a raw role
check (user?.role === 'admin'); replace that with the centralized permission
helper from the shared permissions module so compaction uses the canonical
authorization logic: import the appropriate exported function from the shared
permissions package (for example isAdmin or hasPermission) and call it inside
canUseCompaction to determine access instead of comparing role strings directly;
keep the function signature and return a boolean based on that helper.

In `@apps/web/src/lib/ai/core/complete-request-builder.ts`:
- Around line 164-171: buildCompleteRequest() currently builds systemPrompt by
concatenating baseSystemPrompt, TOOL_DISCOVERY_PROMPT, inlineInstructions and
nonCoreNamesSection but omits the volatile suffix (timestamp/mention/command)
that production appends to the last user message; update buildCompleteRequest()
so the admin preview mirrors production by taking the volatile suffix (the same
logic used on the route side) and appending it to the final user message instead
of dropping it, referencing symbols systemPrompt, baseSystemPrompt,
TOOL_DISCOVERY_PROMPT, inlineInstructions, nonCoreNamesSection and the
buildCompleteRequest() assembly path; alternatively, if you decide not to append
volatile data, split or rename this output to make it explicit that it is a
stable-prefix-only preview (e.g., stableSystemPromptPreview) so callers and
token estimates aren’t misled.

In `@apps/web/src/lib/ai/core/context-assembly.ts`:
- Around line 116-119: The code currently infers a synthetic summary by checking
compacted[0].id (variables hasSummary, summaryText, tailUIMessages), which
misclassifies client-sent UIMessage objects that legitimately lack DB ids;
change prepareConversationContext to return an explicit flag or summary message
alongside the compacted array (e.g., { compacted, syntheticSummary } or
syntheticSummary?: UIMessage), then update this file to stop using
!compacted[0].id — compute hasSummary from the explicit return value and set
summaryText and tailUIMessages based on that flag (use compacted.slice(1) only
when the explicit syntheticSummary flag/message is present).

In `@apps/web/src/lib/ai/core/prompt-assembly.ts`:
- Around line 143-154: The addCacheBreakpoint function currently only sets
providerOptions.openrouter.cacheControl; update it to also set
providerOptions.anthropic with the Vercel AI SDK shape (e.g.,
providerOptions.anthropic = { cacheControl: { type: 'ephemeral' } } and
optionally include ttl) while preserving any existing providerOptions and
existing.openrouter values (keep EPHEMERAL_CACHE for openrouter). Modify
addCacheBreakpoint to merge both openrouter and anthropic entries (or branch by
provider if preferred) and add/adjust tests to assert that both
providerOptions.openrouter.cacheControl and
providerOptions.anthropic.cacheControl are set correctly.

In `@apps/web/src/lib/ai/tools/agent-communication-tools.ts`:
- Around line 598-603: The summary branch currently flattens
prepared.messages[0] to a single string via agentSummaryText, losing additional
parts and tool-rendered content; instead keep the full parts payload by
converting prepared.messages[0] with the same convertToModelMessages flow used
for the tail. Update the logic around
agentHasSummary/agentSummaryText/agentTailUIMessages so that when
agentHasSummary is true you call convertToModelMessages on prepared.messages[0]
(preserving its parts array) and prepend those resulting ModelMessage(s) to
agentTailModelMessages when building agentModelMessages, ensuring generateText()
receives the full parts structure rather than only parts[0].text.

In `@apps/web/src/lib/repositories/conversation-repository.ts`:
- Around line 279-280: Change both softDeleteConversation implementations to
await the compaction invalidation instead of fire-and-forget: ensure the
conversation-state write and the call to invalidateCompaction(conversationId)
are performed as a single awaited flow (ideally in the same helper/transaction)
so the delete does not return until invalidateCompaction completes, and
propagate any errors up to the caller so the route layer can log or handle
failures; update the helper used by
apps/web/src/lib/repositories/conversation-repository.ts and
apps/web/src/lib/repositories/global-conversation-repository.ts to perform and
await both the state delete and invalidateCompaction and surface exceptions
rather than swallowing them.

In `@packages/lib/src/ai/__tests__/context-window.test.ts`:
- Around line 14-20: The default timestamp generation in makeUser (and the other
fixture helpers on lines referenced) uses id.slice(-1) which wraps multi-digit
IDs like "m10" back to 0; change it to extract and parse the full trailing
numeric suffix (e.g. const n = Number((id.match(/\d+$/) || ['0'])[0]) || 0) and
use that n in the Date construction so createdAt increases monotonically for
TEN_MSGS; update the same logic in the other fixture functions referenced (lines
23-29 and 32-40) to use the parsed numeric suffix instead of slice(-1).

In `@packages/lib/src/ai/context-window.ts`:
- Around line 218-223: The code currently uses compaction.summaryTokens which
undercounts because prepare-context actually prepends
formatSummaryMessage(compaction.summary); update the budget calculation to count
the tokens of the formatted summary string instead: if summaryMessage (from
formatSummaryMessage(compaction.summary)) is non-null, call the token estimator
used elsewhere (e.g., the existing estimateTokens/estimateTailTokens utility) to
compute formattedSummaryTokens and replace compaction.summaryTokens with that
value when computing totalTokens/usageRatio (and mirror this change in the other
occurrences referenced); keep using compaction.summaryTokens only as a fallback
if no formatted summary string is produced.
- Around line 255-274: The soft-threshold compaction branch can choose cutIdx ==
tail.length and thus summarize the entire tail; change the guard so the plan
never covers the whole tail. Update the condition around creating compactionPlan
(the block using findUserTurnCut, hasFullUserTurnPreCut, cutIdx, tail,
tailStart, estimateTailTokens, compaction) to require cutIdx < tail.length
(e.g., if (hasFullUserTurnPreCut && cutIdx > 0 && cutIdx < tail.length)) so at
least one live tail message remains verbatim after soft compaction.

In `@packages/lib/src/ai/tool-result-eliding.ts`:
- Around line 195-201: The code only uses part.toolName and falls back to '' so
messages with part.type like 'tool-<name>' are missed; update the logic where
toolName is computed (the toolName variable in tool-result-eliding.ts / the code
handling part) to derive the canonical tool identifier when part.toolName is
undefined by parsing part.type for the 'tool-' prefix (e.g., if typeof part.type
=== 'string' and part.type.startsWith('tool-') then set toolName to the
substring after 'tool-'), then continue using writeTools.has(toolName) and
elidableTools.has(toolName) as before so both write-tools and elidable-tools are
correctly detected.

---

Nitpick comments:
In `@apps/web/src/lib/ai/core/compaction/compaction-repository.ts`:
- Around line 5-18: Replace the hand-maintained CompactionStateRow with the
schema-inferred type: import SelectConversationCompaction from the
`@pagespace/db/schema/ai-compaction` module and alias or re-export it as
CompactionStateRow (i.e., make CompactionStateRow =
SelectConversationCompaction) so the in-code type exactly matches the DB table
contract and avoids widening fields like source; update any local references to
CompactionStateRow to use the imported/aliased type.

In `@apps/web/src/lib/ai/core/context-assembly.ts`:
- Around line 37-49: The duplicated WRITE_TOOLS_FOR_ELISION set causes drift;
stop mirroring it by exporting the canonical write-tool allowlist from its
original module (the symbol that currently defines the list in tool-filtering)
and import/use that exported constant here instead of defining
WRITE_TOOLS_FOR_ELISION locally; update references in context-assembly.ts to
consume the imported symbol (remove the local new Set([...]) and any duplicate
entries) so both consumers share the single source of truth.

In `@apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts`:
- Around line 99-106: Add a regression test that mocks
prepareConversationContext to return a synthetic summary message followed by the
tail messages and a non-null pendingCompaction, then call ask_agent and assert
that generateText was invoked with the exact messages array (synthetic summary
first + tail) and that runCompaction was subsequently dispatched; specifically
override vi.mock for prepareConversationContext to return { messages:
[syntheticSummary, ...tail], pendingCompaction: { /* id or payload */ },
scheduleCompaction: vi.fn() }, spy on generateText and runCompaction, call
ask_agent with the conversation that triggers compaction, and assert
generateText received the composed messages and runCompaction was called with
the pendingCompaction payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96f7815c-e006-4137-a76f-10fb4b50fd33

📥 Commits

Reviewing files that changed from the base of the PR and between 885a54f and 0cf8e2c.

📒 Files selected for processing (41)
  • apps/web/src/app/api/ai/chat/messages/[messageId]/route.ts
  • apps/web/src/app/api/ai/chat/route.ts
  • apps/web/src/app/api/ai/global/[id]/messages/[messageId]/route.ts
  • apps/web/src/app/api/ai/global/[id]/messages/route.ts
  • apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/messages/[messageId]/route.ts
  • apps/web/src/app/api/v1/chat/completions/route.ts
  • apps/web/src/lib/ai/core/__tests__/agent-memory.test.ts
  • apps/web/src/lib/ai/core/__tests__/integration-tool-resolver.test.ts
  • apps/web/src/lib/ai/core/__tests__/prompt-assembly.test.ts
  • apps/web/src/lib/ai/core/__tests__/provider-factory.test.ts
  • apps/web/src/lib/ai/core/agent-memory.ts
  • apps/web/src/lib/ai/core/compaction/__tests__/compaction-service.test.ts
  • apps/web/src/lib/ai/core/compaction/compaction-gating.ts
  • apps/web/src/lib/ai/core/compaction/compaction-repository.ts
  • apps/web/src/lib/ai/core/compaction/compaction-service.ts
  • apps/web/src/lib/ai/core/compaction/prepare-context.ts
  • apps/web/src/lib/ai/core/complete-request-builder.ts
  • apps/web/src/lib/ai/core/context-assembly.ts
  • apps/web/src/lib/ai/core/integration-tool-resolver.ts
  • apps/web/src/lib/ai/core/prompt-assembly.ts
  • apps/web/src/lib/ai/core/provider-factory.ts
  • apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts
  • apps/web/src/lib/ai/tools/agent-communication-tools.ts
  • apps/web/src/lib/ai/tools/page-read-tools.ts
  • apps/web/src/lib/ai/tools/search-tools.ts
  • apps/web/src/lib/repositories/conversation-repository.ts
  • apps/web/src/lib/repositories/global-conversation-repository.ts
  • packages/db/drizzle/0161_easy_mockingbird.sql
  • packages/db/drizzle/meta/0161_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/package.json
  • packages/db/src/schema.ts
  • packages/db/src/schema/ai-compaction.ts
  • packages/lib/package.json
  • packages/lib/src/ai/__tests__/context-window.test.ts
  • packages/lib/src/ai/__tests__/summarization-prompt.test.ts
  • packages/lib/src/ai/__tests__/tool-result-eliding.test.ts
  • packages/lib/src/ai/context-window.ts
  • packages/lib/src/ai/summarization-prompt.ts
  • packages/lib/src/ai/tool-result-eliding.ts
  • packages/lib/src/monitoring/usage-source.ts

Comment thread apps/web/src/app/api/ai/chat/route.ts
Comment thread apps/web/src/app/api/ai/global/[id]/messages/route.ts Outdated
Comment thread apps/web/src/lib/ai/core/compaction/compaction-gating.ts
Comment thread apps/web/src/lib/ai/core/complete-request-builder.ts
Comment thread apps/web/src/lib/repositories/conversation-repository.ts Outdated
Comment thread packages/lib/src/ai/__tests__/context-window.test.ts
Comment thread packages/lib/src/ai/context-window.ts
Comment thread packages/lib/src/ai/context-window.ts
Comment thread packages/lib/src/ai/tool-result-eliding.ts Outdated
Comment thread apps/web/src/lib/ai/core/compaction/compaction-service.ts
2witstudios and others added 5 commits June 11, 2026 23:36
Adds a test that verifies both summarize() calls' inputTokens/outputTokens
are summed when a re-condense pass runs, not just the second call's counts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion toolName, context fixes

- await invalidateCompaction in conversation-repository & global-conversation-repository
  so soft-deletes don't leave stale summaries visible to concurrent requests
- fix elideStaleToolOutputs to derive toolName from type field ('tool-{name}')
  when toolName is absent (UIMessage SDK format)
- fix context-assembly hasSummary guard to also require <conversation_summary> prefix
  preventing v1 client-supplied id-less messages from being misclassified
- fix global/messages/route visual content injection to preserve full prepared tail
  (was silently dropping earlier messages, keeping only last MAX_MESSAGES_WITH_IMAGES)
- fix chat/route to pass resolved provider/model to prepareHistoryForModel
- fix context-window soft compaction guard: cutIdx < tail.length prevents absorbing
  entire tail into messagesToSummarize with nothing left as the new tail
- fix compaction-service test mocks to use inputTokens/outputTokens (SDK field names)
- fix context-window test makeUser/makeAssistant timestamps for IDs >9 (m10 → :10 not :00)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…en budget accuracy

- agent-communication-tools: use convertToModelMessages for the full
  prepared.messages array (including synthetic summary) instead of
  manually splitting out parts[0].text, which would silently drop
  multi-part content if the summary format ever changes
- context-window: estimate formatted summary tokens (post-wrapper) rather
  than stored raw summaryTokens, fixing ~30-token undercount from the
  <conversation_summary> wrapper and footer text added by formatSummaryMessage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nitpicks)

- Export WRITE_TOOLS from tool-filtering.ts; context-assembly.ts now
  imports it directly instead of maintaining a stale copy — prevents
  newly added write tools from having their results elided by accident
  (find_and_replace, insert_content, trigger/role/command tools were all
  missing from the old WRITE_TOOLS_FOR_ELISION)
- compaction-repository: replace hand-maintained CompactionStateRow
  interface with SelectConversationCompaction from the DB schema so the
  source field stays 'page' | 'global' and schema changes propagate
  automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ask_agent

- Verifies that when prepareConversationContext returns a synthetic summary
  message, convertToModelMessages is called with the full prepared.messages
  array (summary + tail) rather than just the tail after manual splitting
- Verifies that runCompaction is fired when pendingCompaction is non-null
  (covers the fire-and-forget compaction dispatch path in ask_agent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

All bot review items addressed. Summary of changes across commits 0cf93e6, cf31929, 3e6a8d7, 13a4ac2:

The two nitpick items from the main review are also fixed in 3e6a8d7:

  • CompactionStateRow → replaced with SelectConversationCompaction from @pagespace/db/schema/ai-compaction (schema-inferred; source is now 'page' | 'global' instead of string)
  • WRITE_TOOLS_FOR_ELISION duplication → eliminated; WRITE_TOOLS exported from tool-filtering.ts and imported directly, picking up find_and_replace, insert_content, trigger/role/command tools that were missing from the stale copy

The third nitpick (agent-communication-tools regression test) is in 13a4ac2.

2witstudios and others added 2 commits June 12, 2026 00:13
…mary detection

Consistent with context-assembly.ts: require firstText.startsWith('<conversation_summary>')
so that id-less client-supplied UIMessages in thread mode cannot be misclassified
as synthetic summaries and silently stripped from the model context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@2witstudios 2witstudios left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔬 Code Review

Thorough review of the sliding-window compaction, tool-result eliding, stable system prompts / prompt caching, agent memory, and find_and_replace removal (~21k lines, 63 files).

Inline comments are attached to specific lines below. Summary of findings by priority:

High

  • context-assembly.ts has no unit tests (summary detection, stableBoundaryIndex, boundary coincidence)
  • compaction-repository.ts has no unit tests (version-guarded CAS upsert, first-insert race)
  • hasSummary sentinel check duplicated in 3 places — needs a shared helper; as never casts need a real adapter

Medium

  • contextCalculation in global route uses full sanitizedMessages not the compacted tail — wrong token count in admin context viewer
  • agent-communication-tools.ts skips elision — intentional? If so, document why
  • prepare-context.ts and compaction-gating.ts need minimal unit tests

Low

  • validateTailIntegrity exported but never called in production — call it or unexport
  • Omit<RunCompactionParams, never> is a no-op type
  • allAgentTools not sorted in agent-communication-tools (cache stability inconsistency)
  • Refactor mutable-let block in global route
  • Comment explaining why JSON.stringify(tools) token estimate is intentionally function-free
  • Drop self-describing comment in compaction-service.ts

Comment thread packages/lib/src/ai/context-window.ts
Comment thread apps/web/src/lib/ai/core/compaction/prepare-context.ts Outdated
@2witstudios

Copy link
Copy Markdown
Owner Author

Review findings

The patch introduces context-sizing, billing, persistent-memory security, and consistency failures. In particular, common AI SDK tool outputs are omitted from threshold calculations and concurrent compaction calls can incur untracked provider spend.

P1

  • Count AI SDK tool outputs before applying thresholds (packages/lib/src/ai/context-window.ts:196)
    Persisted AI SDK v5 messages use tool-* parts with input/output, but the estimator only counts legacy tool-call/tool-result parts. Large tool results are therefore estimated as message overhead only, so compaction and emergency truncation may not trigger before the provider rejects an oversized context.

  • Record usage when compaction loses the version race (apps/web/src/lib/ai/core/compaction/compaction-service.ts:149)
    Concurrent requests can both invoke the paid model, but the losing request returns before trackUsage. Record provider usage regardless of whether the generated summary wins persistence.

  • Keep writable agent memory out of the system tier (apps/web/src/lib/ai/core/agent-memory.ts:86)
    Raw persistent memory can contain prompt-injected page or web content and is currently concatenated into future system prompts. Treat memory as untrusted quoted data or require review before promotion to the system tier.

P2

  • Reject empty summaries before advancing the pointer (apps/web/src/lib/ai/core/compaction/compaction-service.ts:136)
    An empty or whitespace-only summary can be persisted while advancing the compacted pointer. Later requests omit the falsy summary but still discard all pre-pointer messages, silently losing history.

  • Scope compaction reads to the source and page (apps/web/src/lib/ai/core/compaction/prepare-context.ts:60)
    The lookup loads compaction solely by conversation ID even though source and pageId are available. Reused IDs can mix or expose unrelated conversation context; include the scope fields in the lookup.

  • Prevent invalidation races from restoring stale summaries (apps/web/src/lib/ai/core/compaction/compaction-repository.ts:42)
    If a message is edited or deleted while the first compaction is pending, invalidation sees no row and does nothing. The pending null-version write can then insert a stale summary. Revalidate a conversation revision or retain an invalidation tombstone before first insertion.

  • Enforce the summary token cap in generation (apps/web/src/lib/ai/core/compaction/compaction-service.ts:45)
    generateText has no output limit, and the single re-condense pass can remain oversized. Set maxOutputTokens and validate the final result before persistence to prevent repeated paid summary-over-cap compactions.

  • Invalidate only conversations the caller deleted (apps/web/src/lib/repositories/global-conversation-repository.ts:366)
    Invalidation currently runs even when the user-scoped update matches no row. A caller who obtains another conversation ID can disrupt its compaction state. Only invalidate when deletedConversation exists.

Comment thread apps/web/src/app/api/ai/chat/route.ts
Comment thread apps/web/src/lib/ai/core/compaction/compaction-service.ts Outdated
Comment thread apps/web/src/lib/ai/core/compaction/prepare-context.ts
Comment thread apps/web/src/app/api/ai/global/[id]/messages/route.ts Outdated
Comment thread apps/web/src/lib/ai/tools/agent-communication-tools.ts Outdated
Comment thread apps/web/src/app/api/ai/global/[id]/messages/route.ts Outdated
Comment thread apps/web/src/lib/ai/tools/agent-communication-tools.ts Outdated
Comment thread apps/web/src/app/api/v1/chat/completions/route.ts
Comment thread apps/web/src/lib/ai/core/context-assembly.ts
Comment thread apps/web/src/lib/ai/core/compaction/compaction-repository.ts
Comment thread apps/web/src/lib/ai/core/compaction/prepare-context.ts
Comment thread apps/web/src/lib/ai/core/compaction/compaction-gating.ts
Comment thread apps/web/src/lib/ai/core/compaction/compaction-service.ts
2witstudios and others added 5 commits June 12, 2026 10:23
…el, viewer parity, sub-agent elision, compaction test coverage

- context-window: buildModelContext now calls validateTailIntegrity and
  advances mid-turn tails to the next user turn (S1); new shared
  isSyntheticSummaryMessage sentinel helper (S3/C1)
- context-assembly + v1 route: use the shared sentinel; replace 'as never'
  with an explicit CompactionMessage[] cast (C1)
- complete-request-builder: mirror production by appending the volatile
  turn context to the example last user message; tokenEstimates gains
  volatileTurnContext (CodeRabbit viewer-parity)
- global route: placeholder lets → const IIFE destructure (C6);
  contextCalculation now measures the post-compaction message set (A1)
- ask_agent: switched to the full prepareHistoryForModel seam so sub-agent
  histories get chunk-aligned elision too (A2); tool-merge determinism
  documented (C5)
- prepare-context: drop no-op Omit type (S2); stringify-estimation comment (C4)
- compaction-service: prompt-injection accepted-risk surface documented
- chat route: stronger intentional-double-sanitize comment (C2)
- tests: new suites for context-assembly (summary split, boundary
  coincidence, false-positive guard), compaction-repository (CAS upsert,
  insert race, invalidate), prepare-context (admin gate passthrough,
  summary prepend, after() scheduling), compaction-gating; lib tests for
  the integrity-gate recovery paths

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rge (round 3)

Master gained its own 0161 (contact_resolved_at). Regenerating ours as
0162 on master's lineage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… race loss, untrusted memory framing, scoped + tombstone invalidation

P1:
- ai-context-calculator: estimateMessageTokens now counts AI SDK v5
  'tool-{name}' parts (input/output) — large persisted tool results were
  previously counted as overhead only, so compaction/emergency truncation
  could fail to fire before the provider rejected the context
- compaction-service: provider usage is tracked immediately after
  generation, so a lost version race or failed validation can no longer
  incur untracked spend
- agent-memory: memory content is framed as untrusted quoted data inside
  an <agent_memory_data> envelope with an explicit non-instruction
  preamble — page/web-derived text cannot gain system-tier authority

P2:
- compaction-service: empty/whitespace summaries are never persisted (the
  pointer must not advance over silently-lost history); generateText is
  bounded with maxOutputTokens and the final result is clamped at the cap
  so a paid summary-over-cap loop cannot recur
- compaction-repository: getState is scoped by source (and pageId for
  page-source) so reused/colliding conversation IDs can never attach
  another conversation's summary; invalidate() now writes an empty-state
  TOMBSTONE (insert + onConflictDoUpdate with version bump) instead of
  deleting, closing the edit-during-first-compaction race where a pending
  null-version insert could land a stale summary
- edit/delete hooks tombstone when no row exists yet; global conversation
  delete only invalidates when the user-scoped delete matched a row

Tests: estimator SDK-part coverage, untrusted-framing assertion, race-loss
billing, empty-summary refusal, maxOutputTokens bound, scoped reads,
tombstone semantics (page + global scopes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

Review findings — all 8 addressed in 852aabef5

P1

  • Count AI SDK tool outputs before thresholds — estimateMessageTokens now has a branch for AI SDK v5 tool-{name} parts, counting input and output payloads (string or JSON) plus call overhead. Regression test: an 8k-char tool output must estimate >2000 tokens (previously ~15). Compaction and emergency truncation now see real sizes.
  • Record usage when compaction loses the version race — trackUsage moved to immediately after generation (including the re-condense pass accumulation), before any validation or persistence. Lost race, empty-summary refusal, and clamping all happen after the spend is recorded. Test: race loss still tracks usage exactly once.
  • Keep writable agent memory out of the system tier — memory content is now wrapped in an <agent_memory_data> envelope with an explicit preamble: it is DATA the agent recorded, NOT system instructions; directives inside it that conflict with the prompt, grant permissions, or request secrets must not be followed. It stays inside the system prompt for cache stability but is denied instruction authority. Test asserts injected directive text lands inside the envelope with the non-instruction framing present.

P2

  • Reject empty summaries — whitespace-only summaries are never persisted; the run logs and returns with state unchanged (spend still tracked). Test added.
  • Scope compaction reads — getState(conversationId, { source, pageId }): reads are constrained to the same source, and to the owning page for source: 'page'. All callers (prepare-context, service re-check, all edit/delete hooks) pass their scope. Tests assert source/pageId constraints and that global scope doesn't filter by page.
  • Invalidation tombstone — invalidate() no longer deletes: it upserts an empty-state tombstone (insert with onConflictDoUpdate that clears state AND bumps summaryVersion). A pending first-compaction insert now hits the PK conflict and loses; a pending CAS update misses the bumped version. The edit/delete hooks also tombstone when no row exists yet (the exact race window). A tombstone reads as "no compaction" — full tail is sent — and the next over-threshold compaction overwrites it through the normal version-guarded path.
  • Enforce the summary token cap in generation — generateText now sets maxOutputTokens at the cap on both passes, and the final result is hard-clamped (with a truncation marker) before persistence, so stored summaryTokens can never re-trigger a paid summary-over-cap loop.
  • Invalidate only conversations the caller deleted — the global repository now invalidates only when the user-scoped delete matched a row.

Validation: lib 513/513 (incl. new estimator test), web ai/core + repositories 641/641, AI route tests 400/400, root bun run typecheck green.

🤖 Generated with Claude Code

Comment thread apps/web/src/lib/ai/core/compaction/compaction-service.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/src/lib/ai/core/complete-request-builder.ts (1)

269-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Only count volatile turn context when the example message is actually present.

volatileTurnContext is always added into tokenEstimates.total, but request.messages is empty when includeExampleMessage is false. In that mode the builder over-reports the payload size and the message-format label advertises tokens that are not in the serialized request.

💡 Suggested fix
-  const volatileTurnContext = buildVolatileTurnContext({
-    timestampPrompt: buildTimestampSystemPrompt(),
-    mentionPrompt: buildMentionSystemPrompt([
-      { id: 'example-page-id', label: 'Example Document', type: 'page' },
-    ]),
-    commandPrompt: '',
-  });
+  const volatileTurnContext = includeExampleMessage
+    ? buildVolatileTurnContext({
+        timestampPrompt: buildTimestampSystemPrompt(),
+        mentionPrompt: buildMentionSystemPrompt([
+          { id: 'example-page-id', label: 'Example Document', type: 'page' },
+        ]),
+        commandPrompt: '',
+      })
+    : '';

   const messages: CompleteAIRequest['messages'] = includeExampleMessage
     ? [
         {
           role: 'user' as const,
           content:
             'What documents are in this drive?' +
             (volatileTurnContext ? `\n\n${volatileTurnContext}` : ''),
         },
       ]
     : [];

-  const volatileTokens = estimateSystemPromptTokens(volatileTurnContext);
+  const volatileTokens =
+    includeExampleMessage && volatileTurnContext
+      ? estimateSystemPromptTokens(volatileTurnContext)
+      : 0;

Based on PR objectives, this builder is supposed to show the payload “exactly as it would be sent to the LLM.”

Also applies to: 378-378

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/ai/core/complete-request-builder.ts` around lines 269 - 315,
The token estimates always include volatileTurnContext even when
request.messages is empty; update the calculation so volatileTurnContext
(volatileTokens) is only added to tokenEstimates and tokenEstimates.total when
includeExampleMessage is true (i.e., when the example user message containing
volatileTurnContext is actually included in request.messages). Locate the
volatileTurnContext / volatileTokens variables and the tokenEstimates object in
complete-request-builder (look for volatileTurnContext, includeExampleMessage,
volatileTokens, and tokenEstimates.total) and conditionally add volatileTokens
to the token breakdown and total only when includeExampleMessage is true.
apps/web/src/lib/ai/tools/agent-communication-tools.ts (1)

643-645: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle rejected background compactions explicitly.

void runCompaction(prepared.pendingCompaction) drops the promise on the floor. If compaction throws, this path produces an unhandled rejection even though the comment describes it as detached/best-effort.

💡 Minimal fix
         if (prepared.pendingCompaction) {
-          void runCompaction(prepared.pendingCompaction);
+          void runCompaction(prepared.pendingCompaction).catch((error) => {
+            loggers.ai.error('ask_agent: deferred compaction failed', error as Error, {
+              agentId,
+              conversationId: activeConversationId,
+            });
+          });
         }

Based on PR objectives, this post-generation compaction path is intended to be detached and non-fatal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/ai/tools/agent-communication-tools.ts` around lines 643 -
645, The detached call to runCompaction(prepared.pendingCompaction) currently
drops the promise and can cause unhandled rejections; update that invocation to
explicitly handle rejections (e.g.,
runCompaction(prepared.pendingCompaction).catch(err => { /* swallow or log */
})) so the best-effort background compaction remains non-fatal but any thrown
error is caught; reference the runCompaction(...) call and
prepared.pendingCompaction variable when making this change.
🧹 Nitpick comments (1)
packages/lib/src/monitoring/ai-context-calculator.ts (1)

124-145: ⚡ Quick win

Teach the exported message-part type about input/output instead of casting around them.

This new tool-* branch only works because it locally casts each part to { input?: unknown; output?: unknown }. That leaves estimateMessageTokens() supporting shapes its own exported UIMessage type cannot represent, which is why the new test needs an unsafe assertion too.

♻️ Suggested refactor
 export interface UIMessage {
   id?: string;
   role: 'user' | 'assistant' | 'system';
   parts?: Array<{
     type: string;
     text?: string;
     toolCallId?: string;
     toolName?: string;
     args?: unknown;
     result?: unknown;
+    input?: unknown;
+    output?: unknown;
   }>;
 }
-        const sdkPart = part as { input?: unknown; output?: unknown };
-        if (sdkPart.input !== undefined && sdkPart.input !== null) {
-          tokens += estimateTokens(JSON.stringify(sdkPart.input));
+        if (part.input !== undefined && part.input !== null) {
+          tokens += estimateTokens(JSON.stringify(part.input));
         }
-        if (sdkPart.output !== undefined && sdkPart.output !== null) {
-          const outStr = typeof sdkPart.output === 'string'
-            ? sdkPart.output
-            : JSON.stringify(sdkPart.output);
+        if (part.output !== undefined && part.output !== null) {
+          const outStr = typeof part.output === 'string'
+            ? part.output
+            : JSON.stringify(part.output);
           tokens += estimateTokens(outStr);
         }

As per coding guidelines, **/*.{ts,tsx}: Always use proper TypeScript types. Never use any types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/src/monitoring/ai-context-calculator.ts` around lines 124 - 145,
The branch handling 'tool-*' message parts is using a local cast (sdkPart as {
input?: unknown; output?: unknown }) instead of the library's exported
message-part type, so update the shared exported UIMessage/message-part type to
include optional input?: unknown and output?: unknown for tool-style messages
(or add a discriminated union variant for types starting with "tool-"); then
remove the unsafe cast in estimateMessageTokens (the branch in
ai-context-calculator.ts that reads sdkPart.input/sdkPart.output) and rely on
the strengthened type so tests no longer need unsafe assertions, and ensure any
call sites or tests importing the message type are updated to the new union
variant.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/api/ai/global/`[id]/messages/[messageId]/route.ts:
- Around line 70-73: Only tombstone the global summary when the edited/deleted
message falls inside the compacted prefix: fetch state via
getState(conversationId) and call invalidate(conversationId, { source: 'global'
}) only if !state OR message.createdAt <= state.compactedUpToCreatedAt; replace
the unconditional invalidate(...) at the shown try block and apply the same
guard to the other occurrence around lines 184-187 so we only clear compaction
when the touched message is within the compacted range.

In `@apps/web/src/lib/ai/core/compaction/compaction-repository.ts`:
- Around line 31-45: The storage key lacks scope fields so reads in getState
(and related functions) can diverge from writes; add a composite key/unique
constraint in the ai-compaction schema for (conversation_id, source, page_id)
and then update all write and conflict/update predicates to include source and
pageId (e.g., in the insert/update/CAS code paths referenced near getState, the
invalidate function, and the other blocks you flagged at 63-81, 84-101, 118-147)
so that inserts, upserts, updates, and tombstones use the same scoped WHERE
(conversationId + source [+ pageId when source==='page']) and the same insert
columns; ensure conversationCompactions table definition and all db.where/eq
calls are changed to match the new composite uniqueness to prevent cross-scope
collisions.

In `@apps/web/src/lib/ai/tools/agent-communication-tools.ts`:
- Around line 595-605: prepareHistoryForModel is currently passed allAgentTools
but generateText may send { ...allAgentTools, ...finishTool }, so the compaction
budget undercounts schema bytes and can overflow at generation time; fix by
making the history prep use the exact toolset that will be sent to generateText
(e.g., compute a toolsForRequest variable combining allAgentTools and finishTool
and pass that to prepareHistoryForModel) and ensure the same toolsForRequest is
used when calling generateText so schema sizing matches; update references to
allAgentTools in the prepareHistoryForModel call to use the combined tools
variable.

---

Outside diff comments:
In `@apps/web/src/lib/ai/core/complete-request-builder.ts`:
- Around line 269-315: The token estimates always include volatileTurnContext
even when request.messages is empty; update the calculation so
volatileTurnContext (volatileTokens) is only added to tokenEstimates and
tokenEstimates.total when includeExampleMessage is true (i.e., when the example
user message containing volatileTurnContext is actually included in
request.messages). Locate the volatileTurnContext / volatileTokens variables and
the tokenEstimates object in complete-request-builder (look for
volatileTurnContext, includeExampleMessage, volatileTokens, and
tokenEstimates.total) and conditionally add volatileTokens to the token
breakdown and total only when includeExampleMessage is true.

In `@apps/web/src/lib/ai/tools/agent-communication-tools.ts`:
- Around line 643-645: The detached call to
runCompaction(prepared.pendingCompaction) currently drops the promise and can
cause unhandled rejections; update that invocation to explicitly handle
rejections (e.g., runCompaction(prepared.pendingCompaction).catch(err => { /*
swallow or log */ })) so the best-effort background compaction remains non-fatal
but any thrown error is caught; reference the runCompaction(...) call and
prepared.pendingCompaction variable when making this change.

---

Nitpick comments:
In `@packages/lib/src/monitoring/ai-context-calculator.ts`:
- Around line 124-145: The branch handling 'tool-*' message parts is using a
local cast (sdkPart as { input?: unknown; output?: unknown }) instead of the
library's exported message-part type, so update the shared exported
UIMessage/message-part type to include optional input?: unknown and output?:
unknown for tool-style messages (or add a discriminated union variant for types
starting with "tool-"); then remove the unsafe cast in estimateMessageTokens
(the branch in ai-context-calculator.ts that reads sdkPart.input/sdkPart.output)
and rely on the strengthened type so tests no longer need unsafe assertions, and
ensure any call sites or tests importing the message type are updated to the new
union variant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df2572d1-78a8-4e57-9124-fade197f6ba3

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf8e2c and 852aabe.

📒 Files selected for processing (31)
  • apps/web/src/app/api/ai/chat/messages/[messageId]/route.ts
  • apps/web/src/app/api/ai/chat/route.ts
  • apps/web/src/app/api/ai/global/[id]/messages/[messageId]/route.ts
  • apps/web/src/app/api/ai/global/[id]/messages/route.ts
  • apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/messages/[messageId]/route.ts
  • apps/web/src/app/api/v1/chat/completions/route.ts
  • apps/web/src/lib/ai/core/__tests__/agent-memory.test.ts
  • apps/web/src/lib/ai/core/__tests__/context-assembly.test.ts
  • apps/web/src/lib/ai/core/agent-memory.ts
  • apps/web/src/lib/ai/core/compaction/__tests__/compaction-gating.test.ts
  • apps/web/src/lib/ai/core/compaction/__tests__/compaction-repository.test.ts
  • apps/web/src/lib/ai/core/compaction/__tests__/compaction-service.test.ts
  • apps/web/src/lib/ai/core/compaction/__tests__/prepare-context.test.ts
  • apps/web/src/lib/ai/core/compaction/compaction-repository.ts
  • apps/web/src/lib/ai/core/compaction/compaction-service.ts
  • apps/web/src/lib/ai/core/compaction/prepare-context.ts
  • apps/web/src/lib/ai/core/complete-request-builder.ts
  • apps/web/src/lib/ai/core/context-assembly.ts
  • apps/web/src/lib/ai/core/tool-filtering.ts
  • apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts
  • apps/web/src/lib/ai/tools/agent-communication-tools.ts
  • apps/web/src/lib/repositories/conversation-repository.ts
  • apps/web/src/lib/repositories/global-conversation-repository.ts
  • packages/db/drizzle/0162_watery_jean_grey.sql
  • packages/db/drizzle/meta/0162_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/lib/src/ai/__tests__/context-window.test.ts
  • packages/lib/src/ai/context-window.ts
  • packages/lib/src/ai/tool-result-eliding.ts
  • packages/lib/src/monitoring/__tests__/ai-context-calculator.test.ts
  • packages/lib/src/monitoring/ai-context-calculator.ts
💤 Files with no reviewable changes (1)
  • packages/db/drizzle/0162_watery_jean_grey.sql
✅ Files skipped from review due to trivial changes (2)
  • apps/web/src/lib/ai/core/compaction/tests/compaction-gating.test.ts
  • packages/db/drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • apps/web/src/app/api/v1/chat/completions/route.ts
  • apps/web/src/app/api/ai/chat/messages/[messageId]/route.ts
  • packages/lib/src/ai/tool-result-eliding.ts
  • apps/web/src/lib/ai/core/tests/agent-memory.test.ts
  • apps/web/src/lib/ai/core/compaction/prepare-context.ts
  • apps/web/src/lib/ai/core/context-assembly.ts
  • apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/messages/[messageId]/route.ts
  • apps/web/src/lib/ai/core/agent-memory.ts
  • apps/web/src/app/api/ai/global/[id]/messages/route.ts
  • apps/web/src/lib/ai/core/compaction/compaction-service.ts

Comment thread apps/web/src/app/api/ai/global/[id]/messages/[messageId]/route.ts Outdated
Comment thread apps/web/src/lib/ai/core/compaction/compaction-repository.ts
Comment thread apps/web/src/lib/ai/tools/agent-communication-tools.ts
2witstudios and others added 4 commits June 12, 2026 11:06
…al invalidation, scoped writes, accurate sub-agent tool budget

- maskIdentifier now strips non-id characters BEFORE masking (CodeQL log
  injection: slicing alone keeps the taint, and <=8-char ids passed
  through raw) — fixes every call site at once
- global message edit/delete now mirror the page-chat guard: tombstone
  when no compaction row exists, otherwise invalidate only when the
  touched message is inside the compacted range (Message interface gains
  createdAt — the select already returned it)
- compaction writes are scope-guarded at the storage layer: the CAS
  update and the tombstone clear include source/pageId predicates so a
  colliding conversationId can never overwrite or clear another scope's
  row; tombstone is now a two-step PK-claim insert + scoped clear+bump
  (all interleavings with a concurrent compaction stay safe; same-id
  cross-scope coexistence is intentionally blocked, not corrupted)
- ask_agent budgets compaction against the exact tool set generateText
  receives (finishTool included)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rge (round 4)

Master gained its own 0162 (Home drive). Regenerating ours as 0163.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@2witstudios
2witstudios merged commit eb53bf7 into master Jun 12, 2026
10 checks passed
@2witstudios
2witstudios deleted the pu/sliding-window-compacted-context branch June 22, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants