Skip to content

Comments

🤖 refactor: move auto-compaction & auto-retry from frontend to backend#2469

Merged
ThomasK33 merged 78 commits intomainfrom
compaction-k3gj
Feb 20, 2026
Merged

🤖 refactor: move auto-compaction & auto-retry from frontend to backend#2469
ThomasK33 merged 78 commits intomainfrom
compaction-k3gj

Conversation

@ThomasK33
Copy link
Member

Summary

Moves three autonomous behaviors from the frontend (React hooks + localStorage) to the backend (AgentSession), making the backend the single source of truth for "what happens next in a turn":

  1. Auto-retryRetryManager in AgentSession handles exponential backoff retry of failed streams
  2. Idle compactionIdleCompactionService executes compaction directly instead of asking the frontend
  3. On-send & mid-stream compactionCompactionMonitor in AgentSession checks context usage before sending and during streaming, triggering compaction automatically

Background

The frontend previously acted as the orchestrator for auto-retry (via useResumeManager) and auto-compaction (via useForceCompaction, useIdleCompactionHandler, and pre-send checks in ChatInput). This created fragility: retries and compactions didn't survive page reloads, race conditions existed between frontend and backend state, and the frontend contained complex state machines that belong in the backend.

Implementation

Phase 1 — Auto-Retry → Backend:

  • Extracted retryEligibility.ts and retryState.ts from browser/ to common/
  • Created RetryManager class with backoff scheduling, non-retryable error detection, cancel/dispose
  • Integrated into AgentSession: stream-end → handleStreamSuccess(), stream-error → handleStreamFailure()
  • Added setAutoRetryEnabled IPC route
  • New chat events: auto-retry-scheduled, auto-retry-starting, auto-retry-abandoned
  • Removed useResumeManager hook and autoRetryPreference localStorage wrapper

Phase 2 — Idle Compaction → Backend:

  • Extracted buildCompactionMessageText() to common/utils/compaction/compactionPrompt.ts
  • IdleCompactionService now calls workspaceService.executeIdleCompaction() directly
  • Cross-workspace serialization preserved via internal queue
  • New chat event: idle-compaction-started (replaces idle-compaction-needed)
  • Removed useIdleCompactionHandler hook

Phase 3 — On-Send & Mid-Stream Compaction → Backend:

  • Extracted autoCompactionCheck.ts and contextLimit.ts from browser/ to common/
  • Created CompactionMonitor class with threshold checks and single-trigger-per-stream guard
  • Integrated into AgentSession: pre-send check synthesizes compaction request with follow-up, mid-stream check interrupts and chains compaction
  • Added setAutoCompactionThreshold IPC route
  • New chat events: auto-compaction-triggered, auto-compaction-completed
  • Removed useForceCompaction hook and shouldTriggerAutoCompaction pre-send check

Phase 4 — Cleanup:

  • Removed dead localStorage key helpers (getAutoRetryKey, getRetryStateKey)
  • Renamed MuxFrontendMetadataMuxMessageMetadata (no longer frontend-specific)
  • Fixed all lint and formatting issues

Risks

  • Medium: The backend now drives retry/compaction timing — if AgentSession crashes mid-retry, state is lost (acceptable: fresh start on restart, same as before)
  • Low: Provider config overrides for custom context windows are passed as null in session-side monitor checks (existing SendMessageOptions doesn't carry providers config map). A follow-up can thread this for full parity.
  • Low: Frontend CompactionWarning is now informational-only (backend handles the action)

📋 Implementation Plan

Move Auto-Compaction & Auto-Retry from Frontend to Backend

Context & Why

Today, the frontend (React hooks + localStorage) acts as the orchestrator for two critical autonomous behaviors:

  1. Auto-compaction — detecting when context usage is too high and synthesizing /compact turns (on-send, mid-stream, and idle).
  2. Auto-retry — detecting interrupted streams and resuming them with exponential backoff.

This creates fragility: retries and compactions don't survive page reloads, race conditions exist between frontend state and backend state, and the frontend contains complex state machines that belong in the backend. Moving this logic into AgentSession makes the backend the single source of truth for "what happens next in a turn."

Evidence

Source What it told us
src/browser/hooks/useResumeManager.ts Retry orchestrator: polls every 1s, exponential backoff (1s→60s), persists RetryState to localStorage
src/browser/utils/messages/retryEligibility.ts Classification: non-retryable errors list, grace period (15s), isEligibleForAutoRetry()
src/browser/utils/messages/retryState.ts Backoff math: min(1000 * 2^attempt, 60000)
src/browser/utils/compaction/autoCompactionCheck.ts Threshold check: warning at threshold−10%, force at threshold+5%
src/browser/hooks/useForceCompaction.ts Mid-stream: monitors shouldForceCompact, interrupts stream, triggers compaction
src/browser/hooks/useIdleCompactionHandler.ts Idle: listens for idle-compaction-needed, serializes queue, calls sendMessage
src/browser/utils/chatCommands.ts executeCompaction() builds metadata with type: "compaction-request" and calls sendMessage
src/node/services/agentSession.ts Already has TurnPhase (IDLE→PREPARING→STREAMING→COMPLETING→IDLE), streamWithHistory, and compaction follow-up dispatch
src/node/services/compactionHandler.ts handleCompletion() already processes compaction results and returns boolean to signal follow-up dispatch
src/node/services/idleCompactionService.ts Already detects idle workspaces and emits events; just doesn't execute the compaction itself
src/node/services/streamManager.ts Has per-workspace mutex, existing retry for previous_response_not_found
src/common/types/message.ts CompactionRequestData, MuxFrontendMetadata, CompactionFollowUpRequest already defined
src/common/orpc/schemas/stream.ts WorkspaceChatMessage union already includes idle-compaction-needed variant
src/browser/stores/WorkspaceStore.ts Retry state reset happens here on stream-end

Architecture Overview

The core change: AgentSession gains a post-turn decision loop that can chain compaction or retry turns without frontend involvement.

Current:  Frontend polls/decides → IPC call → Backend executes one turn → returns
Proposed: Frontend sends message  → Backend executes turn → Backend decides next action
                                                           → auto-retry? loop internally
                                                           → needs compaction? chain compaction turn
                                                           → done? emit stream-end to frontend

The frontend becomes a thin display layer that shows status events ("retrying attempt 2/5…", "auto-compacting…") rather than driving the state machine.


Phase 1: Move Auto-Retry into Backend

Goal: AgentSession automatically retries failed streams with exponential backoff. Frontend useResumeManager is removed.

~350 LoC added (backend), ~400 LoC removed (frontend). Net: −50 LoC.

1.1 Extract shared retry utilities to src/common/

Move pure logic from frontend to common (no DOM/React dependencies):

  • src/browser/utils/messages/retryEligibility.tssrc/common/utils/messages/retryEligibility.ts

    • isEligibleForAutoRetry() — adapt signature to work with MuxMessage[] instead of DisplayedMessage[]
    • NON_RETRYABLE_STREAM_ERRORS, NON_RETRYABLE_SEND_ERRORS — export as-is
    • isNonRetryableSendError(), isNonRetryableStreamError() — export as-is
  • src/browser/utils/messages/retryState.tssrc/common/utils/messages/retryState.ts

    • RetryState interface, calculateBackoffDelay(), createFreshRetryState(), createFailedRetryState()
    • Remove createManualRetryState() (manual retries become just another resumeStream call)

Update all existing frontend imports to point to src/common/....

1.2 Add RetryManager to AgentSession

New file: src/node/services/retryManager.ts

import { RetryState, calculateBackoffDelay, createFreshRetryState, createFailedRetryState } from "@/common/utils/messages/retryState";
import { isNonRetryableStreamError, isNonRetryableSendError } from "@/common/utils/messages/retryEligibility";

export class RetryManager {
  private state: RetryState = createFreshRetryState();
  private retryTimer: ReturnType<typeof setTimeout> | null = null;
  private enabled = true; // user preference

  constructor(
    private readonly workspaceId: string,
    private readonly onRetry: () => Promise<void>,
    private readonly onStatusChange: (event: RetryStatusEvent) => void,
  ) {}

  /** Called after a stream fails. Decides whether to retry. */
  handleStreamFailure(error: SendMessageError): void {
    if (!this.enabled) return;
    if (isNonRetryableSendError(error) || isNonRetryableStreamError(error)) {
      this.onStatusChange({ type: "retry-abandoned", reason: error.type });
      return;
    }

    this.state = createFailedRetryState(this.state.attempt, error);
    const delay = calculateBackoffDelay(this.state.attempt);
    
    this.onStatusChange({
      type: "retry-scheduled",
      attempt: this.state.attempt,
      delayMs: delay,
    });

    this.retryTimer = setTimeout(async () => {
      this.onStatusChange({ type: "retry-starting", attempt: this.state.attempt });
      await this.onRetry();
    }, delay);
  }

  /** Called when a stream succeeds — resets state. */
  handleStreamSuccess(): void {
    this.state = createFreshRetryState();
  }

  /** Called on user Ctrl+C or explicit cancel. */
  cancel(): void {
    if (this.retryTimer) clearTimeout(this.retryTimer);
    this.retryTimer = null;
    this.state = createFreshRetryState();
  }

  setEnabled(enabled: boolean): void { this.enabled = enabled; }
  dispose(): void { this.cancel(); }
}

1.3 Integrate RetryManager into AgentSession

In src/node/services/agentSession.ts:

  • Instantiate RetryManager in constructor, wire onRetry to call this.resumeStream()
  • Wire onStatusChange to emit new chat events (see §1.4)
  • In the stream-end handler (line ~1772): call retryManager.handleStreamSuccess()
  • In error paths (stream-abort, stream-error): call retryManager.handleStreamFailure(error)
  • In interrupt() method: call retryManager.cancel() (user explicitly stopped)
  • Add setAutoRetryEnabled(enabled: boolean) method, called from IPC when user toggles preference

1.4 New chat event types

In src/common/orpc/schemas/stream.ts, add to the WorkspaceChatMessage union:

// New variants
| { type: "auto-retry-scheduled"; attempt: number; delayMs: number; maxDelay: number }
| { type: "auto-retry-starting"; attempt: number }
| { type: "auto-retry-abandoned"; reason: string }

1.5 New IPC method for retry preference

In src/node/orpc/router.ts, add:

workspace.setAutoRetryEnabled.input(z.object({
  workspaceId: z.string(),
  enabled: z.boolean(),
}))

This replaces the current localStorage-based autoRetryPreference.ts.

1.6 Frontend cleanup

Remove:

  • src/browser/hooks/useResumeManager.ts — entire file
  • src/browser/utils/messages/autoRetryPreference.ts — replaced by IPC call
  • src/browser/utils/messages/retryState.ts — moved to common (update imports)
  • src/browser/utils/messages/retryEligibility.ts — moved to common (update imports)
  • localStorage keys for retry state in WorkspaceStore.ts (line ~438)

Update:

  • src/browser/stores/WorkspaceStore.ts — handle new auto-retry-scheduled/starting/abandoned events, update workspace state for UI display
  • src/browser/components/Messages/ChatBarrier/RetryBarrier.tsx — show "Backend retrying (attempt N)…" status from store state instead of driving retries
  • ChatPane.tsx or wherever useResumeManager is mounted — remove the hook call

Phase 2: Move Idle Compaction Execution to Backend

Goal: When IdleCompactionService detects an idle workspace, the backend executes the compaction itself instead of asking the frontend.

~100 LoC added (backend), ~150 LoC removed (frontend). Net: −50 LoC.

2.1 Backend: IdleCompactionService executes directly

In src/node/services/idleCompactionService.ts (or workspaceService.ts):

  • Instead of emitting idle-compaction-needed, call agentSession.sendMessage() directly with the compaction metadata
  • The compaction message is built the same way executeCompaction does in the frontend — construct muxMetadata with type: "compaction-request" and source: "idle-compaction"
  • Serialize across workspaces using the existing per-workspace AsyncMutex in StreamManager (natural serialization — only one stream per workspace at a time)
  • For cross-workspace serialization (avoid thundering herd), add a simple queue in IdleCompactionService itself (mirrors the queueRef logic from useIdleCompactionHandler)

2.2 New chat event for status

Add to WorkspaceChatMessage:

| { type: "idle-compaction-started" }

The existing idle-compaction-needed event can be kept for backwards compatibility (frontend shows a notification) or removed if the frontend no longer needs to know about idle compaction until it's happening. Recommend: replace with idle-compaction-started so the frontend can show "Compacting idle workspace…" status.

2.3 Build compaction prompt in backend

Extract from src/browser/utils/chatCommands.ts (prepareCompactionMessage):

  • The prompt text construction (buildCompactionPrompt) — move to src/common/utils/compaction/ or src/node/services/
  • The metadata construction (muxMetadata with type: "compaction-request", source, displayStatus) — this is already defined in src/common/types/message.ts, so the backend can construct it directly

2.4 Frontend cleanup

Remove:

  • src/browser/hooks/useIdleCompactionHandler.ts — entire file
  • Remove mounting of useIdleCompactionHandler from the app root component
  • Remove idle-compaction-needed event handling from WorkspaceStore.ts (or replace with idle-compaction-started handler)

Phase 3: Move On-Send & Mid-Stream Compaction to Backend

Goal: The backend automatically compacts when context usage exceeds thresholds, without frontend involvement. This is the largest change.

~250 LoC added (backend), ~300 LoC removed (frontend). Net: −50 LoC.

3.1 Extract compaction threshold logic to src/common/

Move from src/browser/utils/compaction/autoCompactionCheck.ts to src/common/utils/compaction/autoCompactionCheck.ts:

  • checkAutoCompaction() — adapt to take raw token counts + model info rather than frontend store types
  • getContextTokens() — pure math, moves as-is
  • getEffectiveContextLimit() from src/browser/utils/compaction/contextLimit.ts — already only depends on model stats, move to common

3.2 Add CompactionMonitor to AgentSession

New file: src/node/services/compactionMonitor.ts

export class CompactionMonitor {
  private threshold = 0.70; // default, configurable
  private hasTriggeredForCurrentStream = false;

  constructor(
    private readonly workspaceId: string,
    private readonly onCompactionNeeded: (reason: "on-send" | "mid-stream") => Promise<void>,
    private readonly onStatusChange: (event: CompactionStatusEvent) => void,
  ) {}

  /** Called before sending a new message. Returns true if compaction should happen first. */
  checkBeforeSend(usage: TokenUsage, contextLimit: number): boolean {
    const pct = (usage.input + usage.cached + usage.cacheCreate) / contextLimit;
    return pct >= this.threshold;
  }

  /** Called on each usage-delta during streaming. Triggers mid-stream compaction if needed. */
  handleUsageDelta(usage: TokenUsage, contextLimit: number): void {
    if (this.hasTriggeredForCurrentStream) return;
    const pct = (usage.input + usage.cached + usage.cacheCreate) / contextLimit;
    const forceThreshold = this.threshold + 0.05;
    if (pct >= forceThreshold) {
      this.hasTriggeredForCurrentStream = true;
      this.onStatusChange({ type: "force-compaction-triggered", usagePercent: pct });
      this.onCompactionNeeded("mid-stream");
    }
  }

  resetForNewStream(): void { this.hasTriggeredForCurrentStream = false; }
  setThreshold(threshold: number): void { this.threshold = threshold; }
}

3.3 Integrate into AgentSession

In src/node/services/agentSession.ts:

On-send compaction:

  • In sendMessage(), before calling streamWithHistory(), call compactionMonitor.checkBeforeSend()
  • If true: synthesize a compaction message with the user's original message as followUpContent (same as the frontend's executeCompaction does today), then call streamWithHistory() with the compaction request
  • The existing compactionHandler.handleCompletion()dispatchPendingFollowUp() flow handles sending the original message after compaction finishes — this already works today

Mid-stream compaction:

  • In the usage-delta event handler, call compactionMonitor.handleUsageDelta()
  • If triggered: interrupt the current stream (this.interrupt()), then chain a compaction turn followed by a [CONTINUE] resume — reusing the existing compaction-then-follow-up infrastructure

3.4 Threshold preference via IPC

In src/node/orpc/router.ts, add:

workspace.setAutoCompactionThreshold.input(z.object({
  workspaceId: z.string(),
  threshold: z.number().min(0.1).max(1.0),
}))

The frontend's ContextUsageIndicatorButton slider calls this instead of persisting locally. The backend stores the threshold on AgentSession (or a workspace config object).

3.5 New chat events

| { type: "auto-compaction-started"; reason: "on-send" | "mid-stream" | "idle"; usagePercent: number }
| { type: "auto-compaction-completed"; newUsagePercent: number }

3.6 Frontend cleanup

Remove:

  • src/browser/hooks/useForceCompaction.ts — entire file
  • Auto-compaction check in src/browser/components/ChatInput/index.tsx — the shouldTriggerAutoCompaction pre-send check
  • autoCompactionResult computation in ChatPane.tsx — no longer needed for driving compaction (may still be needed for the warning banner UI)
  • src/browser/utils/compaction/autoCompactionCheck.ts — moved to common (update imports)

Keep (modified):

  • CompactionWarning.tsx — can still show "context usage is high" based on usage-delta events from the store, but it becomes informational rather than actionable (the backend handles it automatically)
  • ContextUsageIndicatorButton — slider still exists, but calls IPC to set threshold on backend instead of persisting locally

Phase 4: Clean Up & Polish

~Net −100 LoC (removing dead code, consolidating types).

4.1 Remove dead frontend-to-backend compaction path

  • The frontend no longer calls sendMessage with type: "compaction-request" metadata — all compaction is backend-initiated
  • Remove executeCompaction and prepareCompactionMessage from src/browser/utils/chatCommands.ts
  • Remove formatCompactionCommandLine if no longer used

4.2 Consolidate types

  • MuxFrontendMetadata in src/common/types/message.ts — the "compaction-request" variant is now backend-internal; consider renaming to MuxMessageMetadata since it's no longer frontend-specific
  • CompactionFollowUpRequest — now constructed entirely by the backend

4.3 Update tests

  • Backend tests: Add tests for RetryManager (backoff, non-retryable errors, cancel) and CompactionMonitor (threshold checks, mid-stream trigger, reset)
  • Integration tests: Verify that AgentSession auto-retries on transient errors and auto-compacts on threshold breach
  • Frontend tests: Update any tests that depend on useResumeManager, useForceCompaction, or useIdleCompactionHandler

4.4 Manual user-triggered /compact still works

The user can still type /compact in chat input → the frontend sends it as a regular sendMessage with type: "compaction-request" metadata → the backend processes it as before. This path doesn't change.


Execution Order & Dependencies

graph TD
    A["Phase 1: Auto-Retry → Backend"] --> C["Phase 3: On-Send + Mid-Stream Compaction → Backend"]
    B["Phase 2: Idle Compaction → Backend"] --> C
    C --> D["Phase 4: Cleanup & Polish"]
    style A fill:#4CAF50,color:#fff
    style B fill:#4CAF50,color:#fff
    style C fill:#FF9800,color:#fff
    style D fill:#2196F3,color:#fff
Loading
  • Phases 1 and 2 are independent — can be done in parallel by separate agents.
  • Phase 3 depends on both — it needs the shared utilities extracted in Phase 1 and the backend compaction execution pattern from Phase 2.
  • Phase 4 is cleanup — depends on all prior phases.

Risk Mitigation

Risk Mitigation
User loses ability to cancel during auto-retry/compaction interrupt() already cancels active streams; RetryManager.cancel() clears pending timers. Frontend sends interrupt IPC as before.
Threshold preferences out of sync Backend is authoritative; frontend reads from backend via workspace state events, writes via IPC.
Crash during auto-retry loop RetryManager state is in-memory only; on crash, AgentSession restarts fresh. The existing resumeStream path on app restart handles recovery.
Backend compaction races with user input StreamManager mutex ensures one stream per workspace. Queued user messages are held until the compaction turn completes (existing sendQueuedMessages behavior).

Total Estimated Impact

Phase Backend LoC Frontend LoC Net
Phase 1: Auto-Retry +350 −400 −50
Phase 2: Idle Compaction +100 −150 −50
Phase 3: On-Send/Mid-Stream +250 −300 −50
Phase 4: Cleanup +0 −100 −100
Total +700 −950 −250

Generated with mux • Model: anthropic:claude-opus-4-6 • Thinking: xhigh • Cost: $4.01

@ThomasK33
Copy link
Member Author

@codex review

@ThomasK33
Copy link
Member Author

@codex review

Rebased onto main and resolved conflicts. All static checks (typecheck, lint, fmt-check) and targeted tests pass.

Copy link

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

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: dd1f8f9f16

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Fixed the failing integration tests for backend-driven compaction. All static checks pass locally.

Copy link

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

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: 3967369f54

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed P1 feedback:

  • setEnabled(false) now cancels pending retry timer AND emits auto-retry-abandoned event so frontend clears retry status
  • Replaced hard assertion on duplicate failures with graceful cancel+reschedule (fixes integration test crash)
  • Added test coverage for both behaviors

Copy link

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

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: 136e8a4c4f

ℹ️ 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".

@ThomasK33 ThomasK33 force-pushed the compaction-k3gj branch 2 times, most recently from 6b5cd3d to d979a7e Compare February 17, 2026 22:51
@ThomasK33
Copy link
Member Author

@codex review

Addressed P1: non-retryable errors now cancel pending retry timer via cancelPendingTimer(). Also fixed mock AI stream player's abort event to emit correct abortReason: "user" instead of reason: "user_cancelled", which fixes the streamInterrupt.test.ts test.

Added test coverage for the non-retryable-supersedes-retryable scenario.

@ThomasK33
Copy link
Member Author

@codex review

Addressed all 3 review comments:

  1. P1 - Compaction before persist: Moved on-send compaction check BEFORE persisting user message. When compaction triggers, the user message is NOT written to history (avoids duplication) — it becomes the follow-up sent after compaction completes.
  2. P2 - Emit retry-status clear: setEnabled(false) now emits auto-retry-abandoned when cancelling a pending retry (was already addressed in prior push, now confirmed in P1 fix too).
  3. P1 - Cancel timer on success: handleStreamSuccess() now calls cancelPendingTimer() to prevent stale timers from firing after recovery.

Also fixed mock AI stream player's abort event (abortReason: "user" instead of reason: "user_cancelled").

Copy link

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

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: fc6bee46f1

ℹ️ 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".

@ethanndickson ethanndickson linked an issue Feb 18, 2026 that may be closed by this pull request
2 tasks
@ThomasK33
Copy link
Member Author

@codex review

Addressed the P1 feedback:

Seed usage state before backend pre-send compaction checks — Added seedUsageStateFromHistory() which lazily reads the last 10 messages from persisted history on the first sendMessage call after restart. It finds the most recent assistant message with contextUsage metadata and populates lastUsageState via updateUsageStateFromModelUsage(). This restores parity with the old frontend behavior that reconstructed usage from stored transcript metadata.

Also rebased onto latest origin/main.

Copy link

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

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: f4cb6281ed

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed both review comments:

P1: Skip duplicate user turn emission — When on-send compaction triggers, the original user message is no longer emitted to the frontend immediately. The follow-up path dispatches it (persisted + emitted) after compaction completes, preventing duplicate prompts.

P2: Clear retry status on in-flight disablesetEnabled(false) now checks state.attempt > 0 in addition to isRetryPending, so the UI receives auto-retry-abandoned even when the timer has already fired and the retry callback is still executing. Added test coverage for this edge case.

Copy link

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

Choose a reason for hiding this comment

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

💡 Codex Review

if (snapshotResult?.snapshotMessage) {
const snapshotAppendResult = await this.historyService.appendToHistory(
this.workspaceId,
snapshotResult.snapshotMessage
);

P1 Badge Defer snapshot persistence until after compaction decision

sendMessage() persists file/skill snapshot messages before running the on-send compaction check, so when shouldForceCompact is true the deferred user prompt is not written yet but the new snapshots are already part of history. That means the compaction request is executed against a larger context than intended, and large @file snapshots can push an already-near-limit turn over the context window so compaction fails before the follow-up can run. Persist these snapshots only after deciding not to force compact (or attach them with the deferred follow-up path).

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed the latest P1 feedback about snapshot timing during on-send auto-compaction.

What changed

  • sendMessage() now materializes snapshots early but defers persistence and emission until after the pre-send compaction decision.
  • Snapshots are only persisted/emitted when the turn is sent immediately (autoCompactionMessage === null).
  • When on-send compaction triggers, snapshot rows are deferred with the follow-up turn (instead of inflating compaction-request context upfront).

Regression coverage

  • Added agentSession.autoCompaction.test.ts asserting that with forced on-send compaction, synthetic @file snapshot rows are neither persisted nor emitted before compaction.

Copy link

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

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: edffb64901

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed the latest providersConfig feedback:

  • Added getProvidersConfigForCompaction() in AgentSession (defensive fallback for test configs).
  • Pre-send compaction check now passes provider config into compactionMonitor.checkBeforeSend.
  • Mid-stream compaction check now uses provider config captured in activeStreamContext and passes it into checkMidStream.
  • Added regression coverage in agentSession.autoCompaction.test.ts verifying providers config is threaded into both pre-send and mid-stream compaction monitor calls.

Also reran local validation (make static-check) successfully.

Copy link

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

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: 0de6005353

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed the latest P1:

  • Updated CompactionMonitor.checkMidStream() to avoid double-counting cached tokens.
    • Old: inputTokens + cachedInputTokens
    • New: inputTokens ?? cachedInputTokens ?? 0
  • Added regression test checkMidStream does not double-count cachedInputTokens.

Local validation rerun and passing: make static-check.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

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

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: f03489c6ca

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed the latest two comments:

  1. Retry opt-out on synthetic sends
  • AgentSession.sendMessage() now resets/re-enables retry only for non-synthetic sends.
  • Synthetic/system sends (mid-stream compaction, task recovery prompts, etc.) no longer silently opt users back into auto-retry.
  • Added regression coverage in agentSession.disposeRace.test.ts.
  1. Manual resume failure handling in RetryBarrier
  • RetryBarrier now awaits resumeStream and handles failure Result values.
  • On pre-stream failures, it shows a visible Retry failed: details row with formatted error text.
  • It also rolls back auto-retry enable (enabled: false) so preference/state stays consistent.
  • Added RetryBarrier.test.tsx coverage for the failure path.

Local validation rerun and passing: make static-check.

Copy link

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

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: 288dddecbe

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Addressed the idle-compaction synthetic-send feedback.

  • WorkspaceService.executeIdleCompaction() now passes synthetic: true (alongside skipAutoResumeReset: true) when dispatching the backend compaction request.
  • This keeps idle/background maintenance turns from being treated as explicit user re-engagement and prevents silent auto-retry opt-in resets.
  • Added regression coverage in workspaceService.test.ts (marks idle compaction send as synthetic).

Local validation rerun and passing: make static-check.

@ThomasK33
Copy link
Member Author

@codex review

Addressed startup auto-retry rerun behavior on transient history-read failures:

  • return deferred (not completed) when startup history inspection fails
  • added regression test ensuring startup auto-retry check reruns and schedules retry after transient read failure

Copy link

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

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: 673f68420e

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Implemented startup auto-retry history-read backoff to avoid tight rerun loops on persistent I/O failures:

  • exponential delay for history-read failure deferrals (capped)
  • state-driven deferrals still rerun immediately when idle
  • added regression test coverage for both transient rerun and persistent-failure backoff behavior

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Hooray!

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

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

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: 66010e13de

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

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

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: 331cec7828

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

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

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: 05acab326f

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

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

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: 79b22c22f9

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Bravo.

ℹ️ 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".

@ThomasK33
Copy link
Member Author

@codex review

1 similar comment
@ThomasK33
Copy link
Member Author

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. 🚀

ℹ️ 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".

@ThomasK33 ThomasK33 added this pull request to the merge queue Feb 20, 2026
Merged via the queue into main with commit 89f29e5 Feb 20, 2026
23 checks passed
@ThomasK33 ThomasK33 deleted the compaction-k3gj branch February 20, 2026 16:27
github-actions bot pushed a commit that referenced this pull request Feb 20, 2026
This function was only used by the frontend RetryBarrier component's
manual retry flow. PR #2469 moved retry management to the backend
RetryManager, which uses createFreshRetryState and createFailedRetryState
instead. The function definition and its tests were left behind as dead
code.
github-actions bot pushed a commit that referenced this pull request Feb 21, 2026
This function was only used by the frontend RetryBarrier component's
manual retry flow. PR #2469 moved retry management to the backend
RetryManager, which uses createFreshRetryState and createFailedRetryState
instead. The function definition and its tests were left behind as dead
code.
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.

Move idle compaction request creation to backend

1 participant