Skip to content

Context-light cross-backend handoff: compact session map + recall tools on every backend #178

Description

@saucam

Problem

Switching or forking a session onto a warm backend (Claude, pi, Codex, Gemini CLI) seeds the incoming backend's first prompt with a rendered text transcript of the conversation (seedFromHistoryrenderHistorySeed). Two costs:

  1. Front-loads the context window. The seed is budgeted at ~70% of the target model's window (seedBudgetChars = window × 0.7 × 3.5, src/daemon/providers/context-windows.ts). For a long session on Codex (256k window) that is up to ~180k tokens consumed before the model does any work.
  2. Lossy at the tail, with no recovery. When the session exceeds the seed budget, the oldest turns are dropped (newest-first truncation + a user notice). The dropped turns are still in the verbatim memory store, but the recall tools (recall / recall_file / timeline) are wired only into the Claude provider (src/daemon/providers/claude/index.ts ~L326). A switched-in Codex/pi/OpenAI/Gemini cannot pull them back.

Net: the cross-backend switch is the lowest-fidelity path in the meta-harness. The per-turn path (canonical history → each backend's native messages + tool calls, never summarized) is unaffected and stays lossless — this is only about the handoff.

Proposed design

Flip the handoff from "push the whole transcript" to "seed a compact map, pull detail on demand", uniformly across all backends.

1. Expose the recall tools on every backend, not just Claude.
recall(query), recall_file(path), timeline(limit), plus a new exact-retrieval get_turn(id) / get_turns(ids). Exposure per backend:

  • Codex — native MCP via the app-server (it already surfaces mcpToolCall).
  • pi — the extension/tool bridge (same path the approval bridge already uses).
  • OpenAI / Gemini — as function/tool declarations added to each runTurn() request.
  • Claude — already done (in-process MCP); keep as the reference implementation.

All read the same workspace-scoped episode store, so recall returns the real bytes regardless of which backend asks.

2. Seed a compact session map, not the full transcript.
On switch/fork, the incoming backend's first prompt carries a short anchor:

  • the last user turn, verbatim;
  • a one-line-per-turn index of the session (turn id, role, provider/model, a short label, files touched);
  • the workspace memory index.

The model starts with a near-empty context and a directory of what happened, and calls recall / get_turn to pull any message in full when it needs it.

3. Retrieval by id.
The turn index gives each turn a stable id; get_turn(id) returns the exact canonical turn (content + structured tool calls + results). "Jump to any message in history" becomes a single tool call, and it behaves as if that turn were native to this model's session.

Result: cross-backend switching is context-light (near-empty start) and lossless on demand (any turn retrievable) — the ~180k-token front-load and the tail-truncation both disappear.

Prior art in the codebase (reuse, don't reinvent)

  • MemoryEngine + the verbatim episode store already hold the full history; the Claude memory MCP already exposes recall / recall_file / timeline.
  • The rotation seed strategy B: task-anchor (Layer D, /rotate + auto-rotation) already does exactly this shape within Claude: capture the last user turn, inject a compact anchor, let the model recall prior detail. This issue generalizes that from "Claude rotation" to "any cross-backend switch on any backend."
  • The workspace memory index (Layer C) is already the compact per-turn map to reuse for the anchor.

Conceptually: a cross-backend switch should behave like a /rotate or a compaction — the new backend inherits a compact anchor plus the ability to recall everything, not a wall of transcript.

Scope / acceptance

  • recall / recall_file / timeline + a new get_turn(id) available on Codex, pi, OpenAI, Gemini, Gemini CLI (Claude already has them).
  • Turn index carries stable ids; get_turn returns the exact canonical turn (content + structured tool calls + results).
  • Switch/fork seeds the compact map (last user turn + turn index + workspace index) instead of the full renderHistorySeed transcript.
  • Switch a long (>seed-budget) session onto any backend: it starts context-light and can retrieve any prior turn, including turns that a transcript seed would have dropped.
  • Keep the renderHistorySeed transcript path as the fallback when memory is disabled (CODEOID_MEMORY=0), since there is no episode store to recall from.

Why now

This is the highest-leverage follow-up to the v0.3.0 multi-provider release: it closes the one real fidelity gap in the cross-backend story and makes the "any harness, full history, never summarized" claim hold on every backend, not just Claude.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions