# Codex Compact implementation reference

This document describes protocol, replay, and lifecycle details for extension and provider developers. For installation, safe recovery, and operational limits, use the [README](../README.md).

## Protocol and replay

A custom provider must honor Pi's public `transport: "sse"`, `onPayload`, and injected `fetch` options. Its backend must support the selected request fields and opaque replay; an API label or profile alone does not establish that support. Opaque replay requires the issuing provider ID, the real API label, resolved profile, and current authorization of any custom API profile. Matching API/profile labels across providers do not establish that they can decrypt each other's opaque state. The recorded model ID is provenance rather than an exact-match replay gate within that provider.

1. Pi prepares compaction and selects the recent message suffix it will retain.
2. If an earlier compatible checkpoint is present, the extension identifies its boundary from the summary persisted on the active `CompactionEntry` and validates the retained suffix fingerprints.
3. Remote V2 projects that checkpoint into a normal Responses SSE request and appends exactly one final `compaction_trigger`.
4. Unary compact captures the provider-built payload and authentication, rewrites only the same-origin `/responses` path to `/responses/compact`, and requests JSON without making a normal inference call.
5. Context Management makes a streaming inference request, selecting the latest completed checkpoint and all safe completed output after it in event order.
6. Remote V2 and unary compact require one bounded non-empty opaque `compaction` item; unary output may precede it only with user-role retained messages.
7. It stores bounded replacement history with Pi suffix fingerprints, the real API label, and the resolved compatibility profile in versioned `CompactionEntry.details`. Context Management stores checkpoint-first history with an exact output suffix; the other protocols keep bounded user history with the checkpoint last.
8. On later compatible requests, it replaces an exactly validated marker with the persisted replacement history immediately before provider dispatch.

Retained fingerprints follow Pi's finalized context projection, including pre-checkpoint omissions and replacements and suppression of nested older compaction summaries. For older checkpoints that fingerprinted raw entries, replay first verifies the complete saved raw snapshot, then derives its finalized retained suffix without changing session entries. Edits made after the checkpoint that change this covered suffix still fail validation rather than being silently absorbed. When an upgrade repairs a previously failed projection, the first correctly replayed request establishes a new prefix baseline; subsequent ordinary turns append to it.

The persisted entry remains the summary identity source, so replay does not depend on the wording generated by the currently installed extension version.
If persisted summary identity, fingerprints, API/profile compatibility, payload shape, or marker count do not match exactly, the extension leaves Pi's visible fallback context unchanged instead of guessing.
Older v1 and v2 checkpoints are normalized to the current checkpoint format when read; their built-in API label determines the profile. They still replay only when the current route authorizes that API/profile combination. The recorded model ID identifies the checkpoint creator; changing models within the same provider and compatible route preserves replay and subsequent compaction. The backend still determines whether it accepts the opaque state.

See [model-switch validation](model-switch-validation.md) for the reproduced failure, regression coverage, and observed cross-model backend compatibility.

## Provider changes

When the selected provider differs from the checkpoint issuer but its API/profile remains compatible, the context hook first validates the persisted summary identity and retained-history fingerprints. With `checkpointRecovery: "summarize"`, it resolves the original provider/model and its authentication independently and asks that issuer to summarize only the checkpoint's replacement history. This reuses the recovery stream's terminal-output, size, timeout, and cancellation validation; tools and compaction triggers are disabled. Opaque state and issuer credentials are never substituted into the target provider's request.

A successful summary is saved through Pi's `appendEntry()` as a `codex-compact:plaintext-v1` custom entry, keyed by both compaction entry ID and checkpoint ID. On the active branch, reload/resume/fork can reuse it without another issuer request. The projected context replaces the exact checkpoint marker with a native plaintext `compactionSummary` while preserving retained messages. The original checkpoint remains intact, so switching back can replay it directly. A later cross-provider compaction summarizes the validated prefix through the issuer and publishes an ordinary plaintext compaction instead of sending the foreign opaque state to the target.

Missing source models/authentication, disabled recovery, failed or invalid summaries, and stale branch/session/model/settings ownership stop the request without publishing a transfer or replacing history. The final payload hook also blocks an unresolved foreign checkpoint marker. This transfer makes an additional issuer inference request and is a lossy summary, not local decryption or a guarantee of exact recall. A provider ID is a conservative boundary; changing an endpoint/account behind the same provider ID can still invalidate old ciphertext. Checkpoints do not store a cryptographic issuer identity.

See [provider-switch validation](provider-switch-validation.md) for the observed service failure and live transfer results.

## Compaction ownership

Compaction captures immutable serialized branch values before awaiting authentication. The live branch must retain that exact prefix, and appended entries must form direct descendants. Bookkeeping entries (`custom`, `usage`, `label`, and `session_info`) can extend the branch without canceling work. A `context_edit` append is accepted only when Pi's finalized session context remains identical; replacement and omission edits that change the input cancel it. New conversation messages, even hidden custom messages or subsequently omitted turns, new summaries, model/thinking changes, and different ancestry are not bookkeeping.

Ownership checks also require the effective system prompt and ordered active tool names, descriptions, and schemas to remain unchanged. State entries that indirectly change that request input cannot bypass cancellation. The same checks run after asynchronous authentication and provider work, before checkpoint publication, and before recovery or native fallback. Session replacement, reload, shutdown, explicit abort, model/settings changes, and completed tree navigation retain their existing invalidation paths. No extension-specific state identifier or cross-extension dependency is used.

## Recovery and native fallback

Before the first checkpoint, remote failure leaves the `session_before_compact` result unset so Pi runs its default summary generator. With a valid compatible checkpoint and `checkpointRecovery: "summarize"`, recovery first validates the checkpoint projection and retained cut point, then makes ordinary inference with the checkpoint and tools disabled. Successful recovery publishes only validated plaintext output.

After a recovery request/provider failure, timeout, or invalid/oversized output, the hook leaves its result unset only while session, branch, model, settings, and ownership remain current. Pi owns native summarization, publication, and failure handling for manual, threshold, and overflow compaction. Recovery releases its timer, abort listener, controller, and stream before handoff. A changed dispatched backend blocks fallback. Invalid/incompatible checkpoints and projection/cut-point failures also cancel; explicit Cancel and stale or aborted work never fall through.

Native sees Pi's previous fallback summary marker and visible messages, not decrypted checkpoint history. Publication intentionally starts a new model-visible prefix epoch: the native summary supersedes the opaque checkpoint, and subsequent ordinary requests no longer expand it. The first post-publication request establishes the new baseline; later requests append normally. No new tools, system instructions, continuation, or protocol fallback are introduced. Notifications follow `notifyOnFallback` and UI availability and redact request secrets. See [Checkpoint recovery](../README.md#checkpoint-recovery) for context-loss, quota, and restoration risks.

## Context Management request and publication

Pi still decides when to compact. Only the extension-owned compaction request adds `context_management: [{"type":"compaction","compact_threshold":1024}]`, forces streaming with `store: false`, requests `reasoning.encrypted_content` while preserving other `include` fields and the provider's thinking effort, and appends a deterministic maintenance-only user message. Original instructions, history, and tool schemas remain provider-owned; `tool_choice: "none"` prevents new tool work. The request uses no `compaction_trigger`, server-side response ID, alternate credentials, or endpoint rewrite.

Successful completed-item events are authoritative: a stream can emit several checkpoints while omitting them from terminal response output. The extension persists the latest checkpoint and its exact completed assistant/reasoning suffix only after successful completion (`response.completed` or Codex's `response.done`). Post-checkpoint reasoning must have non-empty encrypted content for stateless replay; terminal output can supply missing encryption before publication, otherwise the request follows the documented fallback/recovery policy. It never executes generated tools or silently truncates that suffix. Missing checkpoints, malformed/unsafe output, exceeded bounds, permission errors, and unsupported capabilities follow the same fallback/recovery policy; very short history may not cross the 1,024-token server threshold. The normal inference can consume quota even if no checkpoint is returned, and fallback/recovery can make an additional summarization request. Safe generated output is retained as provider context, not displayed as new assistant work.

The maintenance instruction is request-local and asks for a work handoff rather than a fixed acknowledgment. Old opaque checkpoints and their exact output suffixes are not rewritten.

## Automatic continuation

Continuation state belongs to the session manager and original user run, not to the UI or each low-level agent retry. Only a matching persisted, extension-produced automatic Context Management checkpoint can arm the candidate. Manual, native, plaintext recovery, other-protocol, and restored checkpoint publication cannot arm it.

An ordinary provider request after publication consumes the candidate. Pi overflow retries, queued work, and pre-prompt or mid-turn requests therefore do not need a duplicate request. At `agent_before_settle`, a completed run with a compatible candidate can append one persisted, hidden `codex-compact:continue-v1` message at the context tail and return `continue: true`. Existing boundary drafts are preserved. An earlier continuation is reused only when the boundary context can actually continue; a flag alone after a completed assistant response is insufficient. Queued user work is checked through `ctx.hasPendingMessages()`, not the boundary's combined `pendingMessages` preview. That preview also contains deferred context-only custom messages, which permit continuation but do not schedule it. Context-only messages must not suppress the forced request. Custom messages queued to trigger work are handled by Pi's post-run queue drain and consume the candidate at normal provider dispatch. Later handlers can override the proposal through Pi's public boundary API.

The allowance is consumed before returning the proposal and resets only for a new original user run. Compaction during the forced continuation cannot start an extension-driven loop. Failure, abort, shutdown, replacement, reload, navigation, or model/settings incompatibility invalidates pending state. Asynchronous compaction results must still match the current owner and run before publication can arm them.

Ordinary requests preserve their prefix after each compaction and append the continuation message once. Prior messages and the system prompt are not rewritten for continuation. Compaction changes the prefix intentionally; the first request after that change is the baseline for later ordinary requests. This schedules work but does not infer task completion or guarantee model compliance.

## OAuth rejection state

Later compactions skip the rejected operation while compatible checkpoint replay remains enabled, even with fallback notifications off. Rejections are isolated by session manager and provider/model/API/profile/protocol plus the configured and dispatched endpoint origin/path (and Azure's `api-version`). Pi/provider authentication and request preparation still run to resolve the effective backend; rejected dispatches stop before network work. Changing the resolved compaction protocol (including selecting Context Management) or the backend permits a new attempt. Switching back remains paused.

Session start, `/reload`, resume, fork, replacement, and shutdown clear rejection state. The first 128 rejected identities are retained until reset; further identities follow ordinary failure handling without evicting earlier ones. The menu reports the last observed dispatch identity, which is rechecked at the next compaction. URL credentials and query secrets are excluded; non-Azure query routing changes, header-/body-only backend changes, and credentials on an otherwise unchanged route require `/reload` to retry. State is temporary, not a settings write or an entitlement cache.

## Codex CLI routing comparison

The examined Codex CLI source uses Remote V2 on its resolved Responses route: ChatGPT/Codex OAuth defaults to the Codex backend, while API keys default to the OpenAI API. This extension already uses V2 for Codex-profile `auto` routes; OpenAI/Azure `auto` remains unary Compact API. Select Remote V2 explicitly to use that request form on a compatible API-key backend. Pi's newer OpenAI ChatGPT token-sharing OAuth is a different grant: neither an API label nor Codex CLI source establishes its compaction permission. No authentication-dependent automatic routing is added.

## Menu compatibility

Menu rendering inherits Kit's minimum one-cell width. Synthetic zero-column renders are clamped to one cell. This is a rendering compatibility limit, not an additional command mode.
