Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
cac705d
feat(agent): define the provider replay checkpoint contract (stage 1)
kojiwakayama Aug 29, 2026
b94dd10
fix(agent): type the replay checkpoint fixture and publish its error …
kojiwakayama Aug 29, 2026
2eddfb1
fix(agent): harden the replay contract per review findings
kojiwakayama Aug 29, 2026
0a8dcc7
Guard provider replay delivery scope
kwakayama Aug 29, 2026
7a5086b
fix(agent): reject provider replay checkpoints anchored to segmented …
kwakayama Aug 29, 2026
371ac15
docs(changelog): drop provider replay checkpoint entry with no behavi…
kwakayama Aug 29, 2026
66ccbac
refactor(runtime): drop redundant provider replay checkpoint re-parse
kwakayama Aug 29, 2026
05cd09e
Honor internal runtime import boundaries
kojiwakayama Aug 29, 2026
e0c4d45
Anchor provider replay only by exact message id
kojiwakayama Aug 29, 2026
4e0c853
Clarify split provider metadata failures
kojiwakayama Aug 29, 2026
5e21bde
Validate replay checkpoints before reuse
kojiwakayama Aug 29, 2026
4a64ac4
Stabilize remote project path expectations
kojiwakayama Aug 29, 2026
1e3442e
Keep replay preservation out of the chat monolith
kojiwakayama Aug 29, 2026
c194223
Keep opaque replay validation on the public transcript boundary
kojiwakayama Aug 29, 2026
1956a0c
Keep provider replay anchors intact through preparation
kojiwakayama Aug 29, 2026
d86cf1c
Keep replay preservation within the chat size budget
kojiwakayama Aug 29, 2026
62529ae
Prevent late workflow reads from escaping teardown
kojiwakayama Aug 29, 2026
0cbbd7a
Keep message preparation under the chat ratchet
kojiwakayama Aug 29, 2026
fe7c870
fix(agent): forward replay checkpoint ids through hosted preparation
kwakayama Aug 29, 2026
32642a3
Keep replay checkpoints inside typed contracts
kojiwakayama Aug 29, 2026
0d5aae3
fix(chat): carry providerExecuted through provider message conversion
kwakayama Aug 29, 2026
b981fc5
docs(api-reference): regenerate agent reference
kwakayama Aug 29, 2026
9231c65
fix(agent): normalize transcript text when validating replay checkpoints
kwakayama Aug 29, 2026
a35bd2c
revert(chat): stop carrying providerExecuted through message preparation
kwakayama Aug 29, 2026
3307af2
fix(typecheck): use the augmented field's value in the consumer fixture
kwakayama Aug 29, 2026
66c97aa
Keep replay checkpoints authoritative through preparation
kojiwakayama Aug 29, 2026
39b70ca
Separate split provider metadata from replay checkpoints
kojiwakayama Aug 29, 2026
f6c9a97
docs(api-reference): regenerate for the replay checkpoint contract co…
kwakayama Aug 29, 2026
1e974a9
fix(agent): accept every supported provider-result content shape
kwakayama Aug 29, 2026
83474bb
test(errors): record the provider-metadata-split-unsupported slug
kwakayama Aug 29, 2026
78213bd
Publish the split provider metadata error contract
kojiwakayama Aug 29, 2026
fe150c8
Fail closed on invalid replay result state
kojiwakayama Aug 29, 2026
835a216
Keep replay API reference current
kojiwakayama Aug 29, 2026
e2d8fda
Remove duplicate generated error entry
kojiwakayama Aug 29, 2026
8a6c961
Preserve checkpoint history through direct message prep
kojiwakayama Aug 29, 2026
73def1a
Preserve provider replay across durable resume boundaries
kojiwakayama Aug 29, 2026
a8185f7
fix(agent): validate provider result caller and raw message group count
kwakayama Aug 29, 2026
c1ef871
Preserve provider replay across durable resume boundaries
kojiwakayama Aug 29, 2026
7cf18a9
fix(agent): reject outer provider tool-result error block types
kwakayama Aug 29, 2026
a5d851d
test(chat): cover tool-pair repair and split replay metadata branches
kwakayama Aug 29, 2026
988879c
chore(docs): regenerate api reference source line anchors
kwakayama Aug 29, 2026
f09f2a5
fix(agent): close replay validation gaps
kojiwakayama Aug 29, 2026
4e1723f
docs(agent): refresh replay source anchors
kojiwakayama Aug 29, 2026
e4996dd
fix(agent): validate replay before transcript matching
kojiwakayama Aug 29, 2026
16a2aa9
Merge remote-tracking branch 'origin/main' into HEAD
kojiwakayama Aug 29, 2026
9dfa6d2
fix(agent): close replay fail-open paths
kojiwakayama Aug 29, 2026
5e6cd1e
fix(agent): keep replay stage 1 off the always-on message path
Aug 29, 2026
36f17e6
fix(agent): dedupe shared-id anchor groups and classify hosted runtim…
Aug 29, 2026
72744a7
test(agent): pin persisted provider-executed turns at the prompt boun…
Aug 29, 2026
3edc793
fix(agent): preserve replay across hosted preparation
kojiwakayama Aug 29, 2026
bbc4a5e
Merge remote-tracking branch 'origin/feat/522-provider-replay-stage1'…
kojiwakayama Aug 29, 2026
1ae3786
fix(lint): re-pin message-prep size ceiling after gated repair call site
Aug 29, 2026
02aac17
fix(agent): retain trailing replay anchors
kojiwakayama Aug 29, 2026
2637f89
Merge remote-tracking branch 'origin/feat/522-provider-replay-stage1'…
kojiwakayama Aug 29, 2026
b3cdb37
fix(chat): scope tool deduplication by turn
kojiwakayama Aug 29, 2026
9711d56
Merge origin/main into feat/522-provider-replay-stage1
Aug 29, 2026
fae901a
fix(agent): keep trailing replay anchors only for delivered checkpoints
Aug 29, 2026
fea6b72
Merge remote-tracking branch 'origin/feat/522-provider-replay-stage1'…
Aug 29, 2026
a480d0e
fix(lint): re-pin message-prep size ceiling after turn-scoped dedupe
Aug 29, 2026
1b59b59
fix(agent): distribute split replay metadata
kojiwakayama Aug 29, 2026
71bf634
Merge remote-tracking branch 'origin/feat/522-provider-replay-stage1'…
kojiwakayama Aug 29, 2026
1eaddaf
fix(agent): reject duplicate replay tool ids
kojiwakayama Aug 29, 2026
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
100 changes: 50 additions & 50 deletions docs/api-reference/veryfront/agent.md

Large diffs are not rendered by default.

279 changes: 140 additions & 139 deletions docs/api-reference/veryfront/errors.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions docs/guides/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,13 @@ Default model has no matching provider credential.
- **HTTP status:** 400
- **What to do:** Set the agent's model to a provider you have a key for, or use model: "auto"

### provider-replay-checkpoint-invalid

Provider replay checkpoint is invalid.

- **HTTP status:** 500
- **What to do:** Verify the trusted source that resolved the run's provider replay checkpoints; do not retry with the same replay state

## General

Raised anywhere; these are not specific to one subsystem.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2988,6 +2988,73 @@ describe("ext-llm-anthropic/anthropic-request-builder", () => {
}]);
});

it("replays checkpoint-shaped signed thinking blocks byte-exact on the resume turn", () => {
// Shape delivered by a provider replay checkpoint (veryfront-issue-inbox#522):
// on Opus 4.7+/Sonnet 5/Fable 5 the thinking text comes back empty and the
// signature is the only carrier, so the rebuilt turn must echo the raw
// blocks unchanged beside the tool_use they accompanied.
const rawAssistantContent = [{
type: "thinking",
thinking: "",
signature: "sig-checkpoint-secret",
}, {
type: "redacted_thinking",
data: "redacted-checkpoint-secret",
}, {
type: "tool_use",
id: "local_lookup_2",
name: "local_lookup",
input: { query: "resume" },
}];
const prompt = [{
role: "user",
content: [{ type: "text", text: "Look this up" }],
}, {
role: "assistant",
content: [{
type: "reasoning",
text: "",
}, {
type: "tool-call",
toolCallId: "local_lookup_2",
toolName: "local_lookup",
input: { query: "resume" },
}],
providerMetadata: { anthropic: { rawAssistantMessages: [rawAssistantContent] } },
}, {
role: "tool",
content: [{
type: "tool-result",
toolCallId: "local_lookup_2",
toolName: "local_lookup",
output: { type: "json", value: { matches: 2 } },
}],
}] as unknown as RuntimePromptMessage[];

const body = buildAnthropicMessagesRequest(
"claude-sonnet-4-6",
"anthropic",
{ prompt, maxOutputTokens: 64 },
false,
createWarningCollector(),
);

assertEquals(body.messages, [{
role: "user",
content: [{ type: "text", text: "Look this up" }],
}, {
role: "assistant",
content: rawAssistantContent,
}, {
role: "user",
content: [{
type: "tool_result",
tool_use_id: "local_lookup_2",
content: '{"matches":2}',
}],
}]);
});

it("rejects raw client tool tampering when canonical content survives", () => {
const canonicalCall = {
type: "tool-call" as const,
Expand Down
52 changes: 52 additions & 0 deletions src/agent/ag-ui/encoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1075,4 +1075,56 @@ describe("agent/ag-ui-encoder tool-input lifecycle", () => {
// emitted it, so a normal tool failure must not emit a second.
assertEquals(events.map((entry) => entry.event), ["ToolCallResult"]);
});

it("never carries reasoning signatures or redacted data into AG-UI payloads", () => {
// Signed reasoning blocks are replayed through the private provider replay
// channel (veryfront-issue-inbox#522); the AG-UI transcript boundary must
// stay signature-free so signed material cannot reach display text.
const state = createAgUiEncoderState({ nowMs: null, epochMs: null });
const signature = "sig-secret-3c2b1a";
const redactedData = "redacted-secret-4d5e6f";

const events = [
...mapRuntimeStreamEventToAgUiEvents(state, {
type: "message-start",
messageId: "assistant-signed",
}),
...mapRuntimeStreamEventToAgUiEvents(state, {
type: "reasoning-start",
id: "reasoning-signed",
}),
...mapRuntimeStreamEventToAgUiEvents(state, {
type: "reasoning-delta",
id: "reasoning-signed",
delta: "",
}),
...mapRuntimeStreamEventToAgUiEvents(state, {
type: "reasoning-end",
id: "reasoning-signed",
signature,
redactedData,
}),
];

assertEquals(
events.map((entry) => entry.event),
["ReasoningMessageStart", "ReasoningMessageContent", "ReasoningMessageEnd"],
"reasoning lifecycle only",
);
const serialized = JSON.stringify(events);
assertEquals(serialized.includes(signature), false, "signature never leaves the runtime");
assertEquals(
serialized.includes(redactedData),
false,
"redacted data never leaves the runtime",
);
assertEquals(
events.at(-1),
{
event: "ReasoningMessageEnd",
payload: { messageId: "assistant-signed:reasoning:0" },
},
"reasoning end carries only its message anchor",
);
});
});
24 changes: 24 additions & 0 deletions src/agent/hosted/chat-preparation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ Deno.test("prepareHostedChatRuntimeCreationOptions builds runtime options from r
version: 1,
loadedToolNames: ["get_release"],
},
serverResolvedProviderReplayCheckpoints: [{
version: 1,
messageId: "assistant-message-1",
provider: "anthropic",
providerBlocks: [{
type: "provider-block",
provider: "anthropic",
block: { type: "thinking", thinking: "", signature: "sig-threading" },
}],
providerBlockPositions: [0],
totalPartCount: 1,
}],
resolveModelId: (modelId) => modelId ? `resolved:${modelId}` : undefined,
resolveModelThinking: (modelId) => modelId ? { enabled: true, budgetTokens: 1234 } : undefined,
fetchSteering: (input) => {
Expand Down Expand Up @@ -369,6 +381,18 @@ Deno.test("prepareHostedChatRuntimeCreationOptions builds runtime options from r
version: 1,
loadedToolNames: ["get_release"],
},
serverResolvedProviderReplayCheckpoints: [{
version: 1,
messageId: "assistant-message-1",
provider: "anthropic",
providerBlocks: [{
type: "provider-block",
provider: "anthropic",
block: { type: "thinking", thinking: "", signature: "sig-threading" },
}],
providerBlockPositions: [0],
totalPartCount: 1,
}],
liveProjectSteering: {
agent: {
id: "agent-1",
Expand Down
11 changes: 11 additions & 0 deletions src/agent/hosted/chat-preparation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
} from "./context-budget-manager.ts";
import { findSubmittedFormInputResult } from "./form-input-tool.ts";
import type { ToolExposureCheckpoint } from "../runtime/tool-exposure.ts";
import type { ProviderReplayCheckpoint } from "../runtime/provider-replay.ts";
import {
createToolExposureCheckpointEvent,
TOOL_SEARCH_TOOL_NAME,
Expand Down Expand Up @@ -126,6 +127,8 @@ export type HostedChatRuntimeCreationPreparationInput<TRuntimeAgentDefinition> =
rootRunContext?: HostedChatRuntimePreparationRootRunContext;
/** Trusted checkpoint resolved after hosted service authentication. */
serverResolvedToolExposureCheckpoint?: ToolExposureCheckpoint;
/** Verified provider replay state resolved by the authenticated server. */
serverResolvedProviderReplayCheckpoints?: readonly ProviderReplayCheckpoint[];
/** Verified integration tool grant for this run, resolved by the control plane. */
serverResolvedIntegrationToolNames?: readonly string[];
/** Service-owned authorization ceiling for Framework host tools. */
Expand Down Expand Up @@ -273,6 +276,8 @@ export type HostedChatExecutionPreparationInput<
contextBudget?: HostedChatContextBudgetOptions;
/** Trusted checkpoint resolved by the authenticated hosted service. */
serverResolvedToolExposureCheckpoint?: ToolExposureCheckpoint;
/** Verified provider replay state resolved by the authenticated server. */
serverResolvedProviderReplayCheckpoints?: readonly ProviderReplayCheckpoint[];
/** Verified integration tool grant for this run, resolved by the control plane. */
serverResolvedIntegrationToolNames?: readonly string[];
/** Service-owned authorization ceiling for Framework host tools. */
Expand Down Expand Up @@ -438,6 +443,11 @@ export async function prepareHostedChatRuntimeCreationOptions<
...(input.serverResolvedToolExposureCheckpoint
? { serverResolvedToolExposureCheckpoint: input.serverResolvedToolExposureCheckpoint }
: {}),
...(input.serverResolvedProviderReplayCheckpoints?.length
? {
serverResolvedProviderReplayCheckpoints: input.serverResolvedProviderReplayCheckpoints,
}
: {}),
...(input.serverResolvedIntegrationToolNames?.length
? { serverResolvedIntegrationToolNames: input.serverResolvedIntegrationToolNames }
: {}),
Expand Down Expand Up @@ -555,6 +565,7 @@ export async function prepareHostedChatExecution<
fetchSteering: input.fetchSteering,
buildInstructions: input.buildInstructions,
serverResolvedToolExposureCheckpoint: input.serverResolvedToolExposureCheckpoint,
serverResolvedProviderReplayCheckpoints: input.serverResolvedProviderReplayCheckpoints,
serverResolvedIntegrationToolNames: input.serverResolvedIntegrationToolNames,
hostToolPolicy: input.hostToolPolicy,
});
Expand Down
3 changes: 3 additions & 0 deletions src/agent/hosted/chat-runtime-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { AgentRuntimeMessage } from "../runtime/message-adapter.ts";
import type { ConversationRunEvent } from "../conversation/run-events.ts";
import type { RuntimeClientProfile } from "../runtime/client-profile.ts";
import type { ToolExposureCheckpoint } from "../runtime/tool-exposure.ts";
import type { ProviderReplayCheckpoint } from "../runtime/provider-replay.ts";
import type { RuntimeSkillDefinition } from "../runtime/skill-metadata.ts";
import type { ResolvedSkillSelectorPolicy } from "#veryfront/skill/selector.ts";

Expand Down Expand Up @@ -164,6 +165,8 @@ export type HostedChatRuntimeCreationOptions<TRuntimeAgentDefinition, TThinkingC
submittedFormInputResult?: HostedSubmittedFormInputResult;
/** @internal Latest private checkpoint loaded from trusted run state. */
serverResolvedToolExposureCheckpoint?: ToolExposureCheckpoint;
/** @internal Verified provider replay state for persisted assistant turns. */
serverResolvedProviderReplayCheckpoints?: readonly ProviderReplayCheckpoint[];
/** @internal Persists private checkpoint state outside model messages. */
persistToolExposureCheckpoint?: (
checkpoint: ToolExposureCheckpoint,
Expand Down
9 changes: 8 additions & 1 deletion src/agent/hosted/cloud-agent-chat-execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ import {
resolveMcpServers,
setFilteredTraceAttributes,
} from "./cloud-agent-child-tools.ts";
import { getServerResolvedToolExposureCheckpoint } from "./runtime-request-config.ts";
import {
getServerResolvedProviderReplayCheckpoints,
getServerResolvedToolExposureCheckpoint,
} from "./runtime-request-config.ts";
import { resolveHostedRequestPreparationSignal } from "../service/request-preparation-context.ts";

const DEFAULT_FORWARDED_CONFIG_NAMESPACE = "veryfront";
Expand Down Expand Up @@ -329,6 +332,10 @@ export async function prepareChatExecutionWithinProjectRuntime(
req.forwardedProps,
req.serverEnvelopeVerified === true,
),
serverResolvedProviderReplayCheckpoints: getServerResolvedProviderReplayCheckpoints(
req.forwardedProps,
req.serverEnvelopeVerified === true,
),
// Sourced from the verified run-event token, never from forwardedProps, so
// it is trusted on the durable-chat path without trusting that body.
...(req.serverResolvedIntegrationToolNames?.length
Expand Down
1 change: 1 addition & 0 deletions src/agent/hosted/default-chat-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ function createRuntimeAgentConfig(input: {
__vfAllowedRemoteTools: input.toolAssembly.compatibleRemoteToolNames,
__vfSourceIntegrationPolicy: input.sourceIntegrationPolicy,
__vfToolExposureCheckpoint: input.options.serverResolvedToolExposureCheckpoint,
__vfProviderReplayCheckpoints: input.options.serverResolvedProviderReplayCheckpoints,
__vfPersistToolExposureCheckpoint: input.options.persistToolExposureCheckpoint,
__vfToolExposureCheckpointPersistenceRequired:
input.options.requireToolExposureCheckpointPersistence === true,
Expand Down
Loading
Loading