Skip to content
Open
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
158 changes: 158 additions & 0 deletions src/context-projection/recovery-live.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import type { OcxContext, OcxParsedRequest } from "../types";
import type { InternalToolCallV1, InternalToolExecutionV1 } from "../internal-tools/types";
import { recoveryEligibilityV1, type RecoverySuspensionReasonV1 } from "./capability";
import { LIVE_DUPLICATE_CONTEXT_PROJECTION_POLICY_V1, LIVE_RECOVERY_CONTEXT_PROJECTION_POLICY_V1 } from "./policy";
import { applyContextProjectionPlan, planContextProjection } from "./projector";
import { ContextRecoverySessionV1 } from "./recovery";
import { executeContextRecoveryCallV1 } from "./recovery-tool";
import { addContextRecoveryToolV1 } from "./synthetic-tool";
import type { ContextRecoverySidecarV1 } from "./composition";
import type {
ContextProjectionContinuationV1,
ContextProjectionMetricsV1,
ContextProjectionSuspensionReason,
} from "./types";

export type LiveRecoverySuspensionReasonV1 = RecoverySuspensionReasonV1
| "inactive_epoch"
| "compaction"
| "tool_name_collision"
| "planner";

export type LiveRecoveryProjectionV1 =
| {
active: false;
providerContext: OcxContext;
reason: LiveRecoverySuspensionReasonV1;
metrics?: ContextProjectionMetricsV1;
}
| {
active: true;
providerContext: OcxContext;
metrics: ContextProjectionMetricsV1;
session: ContextRecoverySessionV1;
execute: (call: InternalToolCallV1) => InternalToolExecutionV1;
};

export interface PrepareLiveRecoveryProjectionV1Options {
epoch?: ContextProjectionContinuationV1;
adapterKind: "stateless" | "runTurn";
providerStateful?: boolean;
isPassthrough: boolean;
isCompaction?: boolean;
hasSidecar?: boolean;
sidecarKind?: ContextRecoverySidecarV1;
collapseDuplicatesFirst?: boolean;
recoveryToolSurvivesBudget: boolean;
/** Exact logical-to-wire tool-name transform used by the selected provider adapter. */
toWireName?: (logicalName: string) => string;
signal?: AbortSignal;
}


function applyDuplicatePrePassV1(canonicalContext: OcxContext, signal?: AbortSignal): OcxContext {
const plan = planContextProjection(canonicalContext, LIVE_DUPLICATE_CONTEXT_PROJECTION_POLICY_V1, { signal });
if (plan.eligibility.state !== "eligible") return canonicalContext;
return applyContextProjectionPlan(canonicalContext, plan);
}
function suspendedRecoveryMetricsV1(
reason: ContextProjectionSuspensionReason,
sidecarKind?: ContextRecoverySidecarV1,
): ContextProjectionMetricsV1 {
return {
version: 1,
variant: "recovery",
candidates: 0,
duplicateCandidates: 0,
duplicateProjected: 0,
largeCandidates: 0,
largeProjected: 0,
originalBytes: 0,
projectedBytes: 0,
planningMs: 0,
plannerScannedBytes: 0,
hashScannedBytes: 0,
recoveryCalls: 0,
recoveryBytes: 0,
recoveryMisses: 0,
recoveryLimitHits: 0,
recoveryScanBytes: 0,
internalModelRounds: 0,
failOpenRestarts: 0,
receiptActiveTurns: 0,
receiptActiveTurnsWithRecovery: 0,
suspensionReason: reason,
...(sidecarKind ? { sidecarKind } : {}),
};
}

function suspendedRecoveryProjectionV1(
providerContext: OcxContext,
reason: LiveRecoverySuspensionReasonV1,
metrics: ContextProjectionMetricsV1 = suspendedRecoveryMetricsV1(reason),
): LiveRecoveryProjectionV1 {
return { active: false, providerContext, reason, metrics };
}

export function prepareLiveRecoveryProjectionV1(
parsed: OcxParsedRequest,
canonicalContext: OcxContext,
options: PrepareLiveRecoveryProjectionV1Options,
): LiveRecoveryProjectionV1 {
if (
options.epoch?.state !== "active"
|| options.epoch.variant !== "recovery-v1"
|| options.epoch.policyId !== LIVE_RECOVERY_CONTEXT_PROJECTION_POLICY_V1.policyId
) return suspendedRecoveryProjectionV1(canonicalContext, "inactive_epoch");
if (options.isCompaction) return suspendedRecoveryProjectionV1(canonicalContext, "compaction");
if (options.hasSidecar) {
return suspendedRecoveryProjectionV1(
canonicalContext,
"sidecar",
suspendedRecoveryMetricsV1("sidecar", options.sidecarKind),
);
}

const eligibility = recoveryEligibilityV1(parsed, {
adapterKind: options.adapterKind,
providerStateful: options.providerStateful === true,
isPassthrough: options.isPassthrough,
recoveryToolSurvivesBudget: options.recoveryToolSurvivesBudget,
});
if (!eligibility.eligible) {
return suspendedRecoveryProjectionV1(canonicalContext, eligibility.reason);
}

const addedTool = addContextRecoveryToolV1(canonicalContext.tools, {
...(options.toWireName ? { toWireName: options.toWireName } : {}),
});
if (!addedTool.ok) {
return suspendedRecoveryProjectionV1(canonicalContext, "tool_name_collision");
}

const planningContext = options.collapseDuplicatesFirst
? applyDuplicatePrePassV1(canonicalContext, options.signal)
: canonicalContext;
const plan = planContextProjection(planningContext, LIVE_RECOVERY_CONTEXT_PROJECTION_POLICY_V1, {
signal: options.signal,
});
if (plan.eligibility.state !== "eligible") {
// Correctness beats cache continuity: if projection itself cannot be constructed safely,
// advertise no internal tool and send the untouched provider context for this request.
return suspendedRecoveryProjectionV1(canonicalContext, "planner", plan.metrics);
}

const projected = applyContextProjectionPlan(planningContext, plan);
const providerContext = { ...projected, tools: addedTool.tools };
const session = new ContextRecoverySessionV1(plan.registry, options.signal);
return {
active: true,
providerContext,
metrics: plan.metrics,
session,
execute: call => executeContextRecoveryCallV1(session, call),
};
}



115 changes: 115 additions & 0 deletions src/context-projection/recovery-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { OcxToolResultMessage } from "../types";
import type { InternalToolCallV1, InternalToolExecutionV1 } from "../internal-tools/types";
import { ContextRecoverySessionV1, type ContextRecoveryErrorV1 } from "./recovery";
import { CONTEXT_RECOVERY_TOOL_NAME } from "./synthetic-tool";

const RECOVERABLE_LOOKUP_ERRORS = new Set<ContextRecoveryErrorV1>([
"not_found",
"stale_cursor",
"invalid_range",
"line_limit",
"query_too_long",
"empty_query",
]);

function objectArgs(text: string): Record<string, unknown> | null {
try {
const value: unknown = JSON.parse(text || "{}");
return value !== null && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: null;
} catch {
return null;
}
}

function optionalPositiveInteger(value: unknown): number | undefined | null {
if (value === undefined) return undefined;
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
}

function toolResult(callId: string, payload: unknown, isError: boolean): OcxToolResultMessage {
return {
role: "toolResult",
toolCallId: callId,
toolName: CONTEXT_RECOVERY_TOOL_NAME,
content: JSON.stringify(payload),
isError,
timestamp: Date.now(),
};
}

function recoverableProtocolError(
call: InternalToolCallV1,
error: "invalid_arguments" | "invalid_operation",
op?: string,
ref?: string,
): InternalToolExecutionV1 {
return {
ok: true,
toolResult: toolResult(call.id, {
ok: false,
...(op ? { op } : {}),
...(ref ? { ref } : {}),
error,
}, true),
};
}

function recoveryFailureOrToolError(
call: InternalToolCallV1,
op: "read" | "grep",
ref: string,
error: ContextRecoveryErrorV1,
): InternalToolExecutionV1 {
if (!RECOVERABLE_LOOKUP_ERRORS.has(error)) return { ok: false, reason: error };
return {
ok: true,
toolResult: toolResult(call.id, { ok: false, op, ref, error }, true),
};
}

/**
* Execute one request-local bounded exact-recovery call.
* Correctable lookup/schema mistakes are returned to the hidden model as bounded tool errors.
* Hard outer budgets, cancellation, and executor mismatches abandon the hidden trajectory so the
* caller can restart once from untouched canonical state.
*/
export function executeContextRecoveryCallV1(
session: ContextRecoverySessionV1,
call: InternalToolCallV1,
): InternalToolExecutionV1 {
if (call.name !== CONTEXT_RECOVERY_TOOL_NAME) return { ok: false, reason: "wrong_tool" };
const args = objectArgs(call.argumentsText);
if (!args || typeof args.op !== "string" || typeof args.ref !== "string" || args.ref.length === 0) {
return recoverableProtocolError(call, "invalid_arguments");
}

if (args.op === "read") {
const startLine = optionalPositiveInteger(args.start_line);
const endLine = optionalPositiveInteger(args.end_line);
const maxBytes = optionalPositiveInteger(args.max_bytes);
if (startLine === null || endLine === null || maxBytes === null || (args.cursor !== undefined && typeof args.cursor !== "string")) {
return recoverableProtocolError(call, "invalid_arguments", "read", args.ref);
}
const result = session.read(args.ref, {
...(startLine !== undefined ? { startLine } : {}),
...(endLine !== undefined ? { endLine } : {}),
...(typeof args.cursor === "string" ? { cursor: args.cursor } : {}),
...(maxBytes !== undefined ? { maxBytes } : {}),
});
if (!result.ok) return recoveryFailureOrToolError(call, "read", args.ref, result.error);
return { ok: true, toolResult: toolResult(call.id, { op: "read", ref: args.ref, ...result }, false) };
}

if (args.op === "grep") {
if (typeof args.query !== "string") {
return recoverableProtocolError(call, "invalid_arguments", "grep", args.ref);
}
const result = session.grepLiteral(args.ref, { query: args.query });
if (!result.ok) return recoveryFailureOrToolError(call, "grep", args.ref, result.error);
return { ok: true, toolResult: toolResult(call.id, { op: "grep", ref: args.ref, ...result }, false) };
}

return recoverableProtocolError(call, "invalid_operation", args.op, args.ref);
}
20 changes: 8 additions & 12 deletions src/context-projection/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,34 @@ export interface ResolveLiveContextProjectionEpochV1Input {
}

/**
* CPG-04 intentionally activates only duplicate-v1. Recovery config is accepted by
* the parser but remains non-live until the recovery protocol/loop exists.
* Projection policy is latched for a Responses chain. Existing duplicate/recovery epochs keep
* their original variant; legacy chains without metadata fail open disabled instead of upgrading.
* Native passthrough never starts or resumes CPG and permanently disables an inherited epoch.
*/
export function resolveLiveContextProjectionEpochV1(
input: ResolveLiveContextProjectionEpochV1Input,
): ContextProjectionContinuationV1 | undefined {
if (input.isPassthrough) {
if (!input.previous) return undefined;
if (input.previous.variant !== "duplicate-v1") {
return { ...input.previous, state: "disabled" };
}
return resolveContextProjectionEpochV1({
previousResponseId: input.previousResponseId,
previous: input.previous,
requestedMode: "duplicate",
requestedMode: input.previous.variant === "recovery-v1" ? "recovery" : "duplicate",
emergencyDisable: true,
});
}
if (input.previous) {
if (input.previous.variant !== "duplicate-v1") {
return { ...input.previous, state: "disabled" };
}
return resolveContextProjectionEpochV1({
previousResponseId: input.previousResponseId,
previous: input.previous,
requestedMode: "duplicate",
requestedMode: input.previous.variant === "recovery-v1" ? "recovery" : "duplicate",
emergencyDisable: input.emergencyDisable,
});
}
if (input.requestedMode !== "duplicate") return undefined;
if (input.requestedMode !== "duplicate" && input.requestedMode !== "recovery" && input.requestedMode !== "on") return undefined;
return resolveContextProjectionEpochV1({
previousResponseId: input.previousResponseId,
requestedMode: "duplicate",
requestedMode: input.requestedMode,
emergencyDisable: input.emergencyDisable,
});
}
Expand Down Expand Up @@ -92,3 +87,4 @@ export function applyDuplicateProjectionV1(
metrics: plan.metrics,
};
}

Loading
Loading