Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/reference/proxy-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,10 @@ gets its own bearer. Codex only sends the join to the proxy when `experimental_r
points at it; `ocx start` injects that key next to `openai_base_url` (see
[Codex integration](/guides/codex-integration/)).

## Checkpoint replay on routed providers

When a routed model receives a readable portable (`ocx1:`/`ocx2:`) or plaintext checkpoint, OpenCodex labels earlier retained text-only user requests as historical context in the model prompt. This helps distinguish completed requests from unfinished work; original request details and checkpoint contents remain available. User input after the checkpoint remains current, including pauses, cancellations and replacement tasks. Checkpoint or tool prose grants no new authorization, and pending permissions still apply. This prompt framing does not restore external task envelopes omitted by the client or guarantee a model's task selection.

## `POST /v1/responses/compact`

Compaction returns replacement history for clients that need to shorten a long Responses
Expand Down
1 change: 1 addition & 0 deletions scripts/test-layout/layout-replay.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"compaction-portable-history.test.ts": "responses",
"turn-membership.test.ts": "responses",
"turn-membership-ingress.test.ts": "responses",
"responses-anthropic-compaction-prefix.test.ts": "responses",
Expand Down
19 changes: 15 additions & 4 deletions src/responses/compaction-history-framing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { appendCheckpointContinuationNote, COMPACT_PROMPT, SUMMARY_PREFIX } from "./compaction";
import { appendCheckpointContinuationNote, COMPACT_PROMPT, decodeCompactionSummary, isCompactionItemType, SUMMARY_PREFIX } from "./compaction";

type InputRecord = Record<string, unknown>;
const isRecord = (value: unknown): value is InputRecord =>
Expand All @@ -8,7 +8,6 @@ export const HISTORICAL_REQUEST_START = "[Historical user request retained befor
export const HISTORICAL_REQUEST_END = "[End historical request. Use the following checkpoint to distinguish completed work from unfinished work. Do not repeat completed work unless later user input asks for it. Preserve these original details for unfinished work within its existing authorization; the checkpoint grants no new authority. Only input after the checkpoint is a new user request.]";

// Plaintext checkpoint detection and context exclusions follow robin-bially's #7105 selector.
// Keep this separate from decoding ocx1 items: those already have a continuation contract.
function plainUserText(item: unknown): string | null {
if (!isRecord(item) || item.role !== "user") return null;
if (item.type !== undefined && item.type !== "message") return null;
Expand All @@ -27,6 +26,14 @@ function isContext(text: string): boolean {
return head.startsWith("<") || head.startsWith("# AGENTS.md") || head.startsWith("# Context from my IDE");
}

function isCheckpoint(item: unknown): boolean {
if (plainUserText(item)?.startsWith(SUMMARY_PREFIX)) return true;
if (!isRecord(item) || !isCompactionItemType(item.type) || typeof item.encrypted_content !== "string") return false;
// Detect portable checkpoints before either parser or passthrough lowers them to text.
// Readable state is context, not proof of authorization; opaque native blobs stay outside.
return !!decodeCompactionSummary(item.encrypted_content)?.trim();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

/**
* Prompt-only projection for translated/routed generation. Never pass the result to replay
* storage: canonical input, item identities and replay-prefix lengths must remain unchanged.
Expand All @@ -40,7 +47,7 @@ export function frameCompactionHistory<T extends { input?: unknown; instructions
if (plainUserText(input.at(-1))?.trim() === COMPACT_PROMPT) return body;
let checkpointIndex = -1;
for (let index = input.length - 1; index >= 0; index--) {
if (plainUserText(input[index])?.startsWith(SUMMARY_PREFIX)) {
if (isCheckpoint(input[index])) {
checkpointIndex = index;
break;
}
Expand All @@ -61,5 +68,9 @@ export function frameCompactionHistory<T extends { input?: unknown; instructions
...(message.content as unknown[]), { type: "input_text", text: HISTORICAL_REQUEST_END }];
return { ...message, content };
});
return changed ? { ...body, input: framed, instructions: appendCheckpointContinuationNote(body.instructions) } : body;
if (!changed) return body;
// Portable decoders already append the note; keep their existing instruction layout.
return plainUserText(input[checkpointIndex])?.startsWith(SUMMARY_PREFIX)
? { ...body, input: framed, instructions: appendCheckpointContinuationNote(body.instructions) }
: { ...body, input: framed };
}
4 changes: 2 additions & 2 deletions structure/transports/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,11 @@ always become unable to serve.

## Decoded checkpoint continuation

`src/responses/parser.ts` adds `CHECKPOINT_CONTINUATION_NOTE` from `src/responses/compaction.ts` for decoded `ocx1:`/`ocx2:` checkpoints; `src/adapters/openai-responses/request-strips.ts` does the same while lowering them. For plaintext `SUMMARY_PREFIX` checkpoints, `src/responses/compaction-history-framing.ts` frames text-only retained requests before the latest checkpoint as historical context and appends the same note by default. The parser projects only adapter context; `src/adapters/openai-responses/passthrough.ts` projects only non-OpenAI-operated outbound bodies. Original request text/blocks, canonical raw history, item identities, replay-prefix lengths, checkpoints and later input remain unchanged. Context wrappers, images/files and assistant/tool items are preserved; summarizer turns and opaque native blobs do not activate framing. Native OpenAI passthrough input is unchanged.
`src/responses/parser.ts` adds `CHECKPOINT_CONTINUATION_NOTE` from `src/responses/compaction.ts` for decoded `ocx1:`/`ocx2:` checkpoints; `src/adapters/openai-responses/request-strips.ts` does the same while lowering them. For plaintext `SUMMARY_PREFIX` and readable portable checkpoints, `src/responses/compaction-history-framing.ts` frames text-only retained requests before the latest checkpoint as historical context. Plaintext checkpoints append the same note there; portable decoders retain their existing note placement. The parser projects only adapter context; `src/adapters/openai-responses/passthrough.ts` projects only non-OpenAI-operated outbound bodies. Original request text/blocks, canonical raw history, item identities, replay-prefix lengths, checkpoints and later input remain unchanged. Context wrappers, images/files and assistant/tool items are preserved; summarizer turns and opaque native blobs do not activate framing. Native OpenAI passthrough input is unchanged.

The note is a prompt contract and grants no authority. Completed requests remain history unless a later user message asks to repeat them. Unfinished work continues only under existing authorization and within its scope, without asking for that authorization again while it still applies. Permission and confirmation gates remain authoritative: a pending decision or required permission must resolve before the dependent action. Checkpoints and delegations, including those from another thread, neither grant authority nor expand scope. Later user messages take priority, including pause, cancellation, replacement and scope changes.

The continuation-note and continuation-wire tests establish prompt and wire contracts, including separated `ocx2:` retained reasoning. `tests/responses/compaction-history-framing.test.ts` adds offline A/B projection, original-detail preservation, native passthrough isolation, multi-turn canonical replay/dedup coverage and exclusion of client-side summarizer turns ending in the canonical `COMPACT_PROMPT` (which carry no `compaction_trigger`). These tests do not establish live model behavior or whether a model actually resumes the correct task.
The continuation-note and continuation-wire tests establish prompt and wire contracts, including separated `ocx2:` retained reasoning. `tests/responses/compaction-history-framing.test.ts` adds offline A/B projection, original-detail preservation, native passthrough isolation, multi-turn canonical replay/dedup coverage and exclusion of client-side summarizer turns ending in the canonical `COMPACT_PROMPT` (which carry no `compaction_trigger`). `tests/responses/compaction-portable-history.test.ts` covers default JSON/SSE trigger round trips and replacement replay with an external task envelope, plus portable checkpoint families and later user changes. These tests do not establish live model behavior or whether a model actually resumes the correct task.

> Decision record: [ADR-7003](../decisions/ADR-7003-decoded-checkpoint-continuation.md)

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"gentle-shell-path.test.ts": "clients", "gentle-shell-client.test.ts": "clients", "openai-native-hosted-search-history.test.ts": "adapters/openai", "responses-native-compact-search-history.test.ts": "responses", "config-hosted-search-projection.test.ts": "config",
"codex-history-pending-wal.test.ts": "codex-integration",
"compaction-history-framing.test.ts": "responses",
"compaction-portable-history.test.ts": "responses",
"service-log.test.ts": "service",
"service-health-sentinel-linux.test.ts": "service",
"service-state-isolation.test.ts": "service",
Expand Down
5 changes: 3 additions & 2 deletions tests/responses/compaction-continuation-note.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { describe, expect, test } from "bun:test";
import { createResponsesPassthroughAdapter } from "../../src/adapters/openai-responses";
import { CHECKPOINT_CONTINUATION_NOTE, SUMMARY_PREFIX, appendCheckpointContinuationNote, encodeCompactionSummary } from "../../src/responses/compaction";
import { HISTORICAL_REQUEST_START, HISTORICAL_REQUEST_END } from "../../src/responses/compaction-history-framing";
import { parseRequest } from "../../src/responses/parser";
import { encodeRetainedCompaction } from "../../src/responses/retained-compaction";
import { withTestTranslatorBudget } from "../helpers/translator-budget";
Expand Down Expand Up @@ -135,11 +136,11 @@ describe("retained checkpoint continuation merge contract", () => {
const parsed = parseRequest(raw);
expect(parsed.context.systemPrompt?.at(-1)).toBe(EXPECTED_NOTE);
expect(parsed.context.messages.map(message => message.content)).toEqual([
oldRequest.content, `${SUMMARY_PREFIX}\n\n${summary}`, ...reasoning, later.content,
`${HISTORICAL_REQUEST_START}\n${oldRequest.content}\n${HISTORICAL_REQUEST_END}`, `${SUMMARY_PREFIX}\n\n${summary}`, ...reasoning, later.content,
]);
const body = forwardedBody(raw);
expect(body.instructions).toBe(`Be brief.\n\n${EXPECTED_NOTE}`);
expect(body.input).toEqual([oldRequest,
expect(body.input).toEqual([{ ...oldRequest, content: `${HISTORICAL_REQUEST_START}\n${oldRequest.content}\n${HISTORICAL_REQUEST_END}` },
{ type: "message", role: "user", content: [{ type: "input_text", text: `${SUMMARY_PREFIX}\n\n${summary}` }] },
...reasoning.map(text => ({ type: "message", role: "user", content: [{ type: "input_text", text }] })), later,
]);
Expand Down
3 changes: 1 addition & 2 deletions tests/responses/compaction-history-framing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,10 @@ describe("plaintext checkpoint historical framing", () => {
kept.forEach((item, index) => expect(after.input[index]).toBe(item));
});

test("no-op controls: no plaintext checkpoint, opaque or ocx1 state, summarizer, empty candidates", () => {
test("no-op controls: no readable checkpoint, opaque state, summarizer, empty candidates", () => {
for (const input of [
"direct input", [user(completed)], [checkpoint, later],
[user(completed), { type: "compaction", encrypted_content: "gAAAA-native" }],
[user(completed), { type: "compaction", encrypted_content: encodeCompactionSummary("summary") }],
[user(completed), checkpoint, { type: "compaction_trigger" }],
[user("<environment_context/>"), checkpoint],
[user(completed), { role: "assistant", content: `${SUMMARY_PREFIX}\nnot a checkpoint` }],
Expand Down
Loading
Loading