Skip to content

Commit 244289a

Browse files
committed
Stabilize A2A prompt cache metadata
A2A session routing identifiers are needed for delivery provenance, but concrete session keys in extraSystemPrompt make the agent system prompt vary between otherwise identical handoffs. Keep the model-facing system context stable by describing high-cardinality session slots with placeholders while retaining concrete values in inputProvenance. Channel names stay concrete: they are low-cardinality (discord/slack/webchat/...), so they do not meaningfully fragment the cache, and they inform reply formatting on the receiving agent. Constraint: OpenClaw contributor PRs require focused behavior proof and tests for prompt/cache-facing changes. Rejected: Removing routing metadata entirely | would weaken model context for requester/target roles. Rejected: Placeholdering channel values too | drops model-visible formatting context for negligible cache benefit (reviewer feedback). Confidence: medium Scope-risk: narrow Directive: Keep concrete session identifiers out of extraSystemPrompt; preserve them in structured provenance or payload fields. Low-cardinality channel labels may stay model-visible. Tested: node scripts/run-vitest.mjs src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts Tested: corepack pnpm exec oxfmt --check src/agents/tools/sessions-send-helpers.ts src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts Tested: node scripts/run-oxlint.mjs src/agents/tools/sessions-send-helpers.ts src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts Tested: git diff --check Tested: live before/after provider cache trace (isolated local gateway, two A2A sends from distinct requester sessions; see PR Real behavior proof)
1 parent bb1f3e8 commit 244289a

3 files changed

Lines changed: 66 additions & 5 deletions

File tree

src/agents/openclaw-tools.sessions.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ type AgentCallParams = {
185185
inputProvenance?: {
186186
kind?: string;
187187
sourceSessionKey?: string;
188+
sourceChannel?: string;
189+
sourceTool?: string;
188190
};
189191
};
190192

@@ -1118,6 +1120,24 @@ describe("sessions tools", () => {
11181120
),
11191121
),
11201122
).toBe(true);
1123+
const initialAgentCall = agentCalls.find((call) =>
1124+
agentParams(call).extraSystemPrompt?.includes("Agent-to-agent message context"),
1125+
);
1126+
const initialAgentParams = agentParams(initialAgentCall ?? {});
1127+
expect(initialAgentParams.extraSystemPrompt).toContain(
1128+
"Agent 1 (requester) session: <REQUESTER_SESSION>.",
1129+
);
1130+
expect(initialAgentParams.extraSystemPrompt).toContain("Agent 1 (requester) channel: discord.");
1131+
expect(initialAgentParams.extraSystemPrompt).toContain(
1132+
"Agent 2 (target) session: <TARGET_SESSION>.",
1133+
);
1134+
expect(initialAgentParams.extraSystemPrompt).not.toContain(requesterKey);
1135+
expect(initialAgentParams.inputProvenance).toMatchObject({
1136+
kind: "inter_session",
1137+
sourceSessionKey: requesterKey,
1138+
sourceChannel: "discord",
1139+
sourceTool: "sessions_send",
1140+
});
11211141
expect(
11221142
agentCalls.some(
11231143
(call) =>

src/agents/tools/sessions-send-helpers.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { beforeEach, describe, expect, it } from "vitest";
22
import { setActivePluginRegistry } from "../../plugins/runtime.js";
33
import { createSessionConversationTestRegistry } from "../../test-utils/session-conversation-registry.js";
4-
import { resolveAnnounceTargetFromKey, resolvePingPongTurns } from "./sessions-send-helpers.js";
4+
import {
5+
buildAgentToAgentMessageContext,
6+
buildAgentToAgentReplyContext,
7+
resolveAnnounceTargetFromKey,
8+
resolvePingPongTurns,
9+
} from "./sessions-send-helpers.js";
510

611
describe("resolveAnnounceTargetFromKey", () => {
712
beforeEach(() => {
@@ -88,3 +93,38 @@ describe("resolvePingPongTurns", () => {
8893
).toBe(20);
8994
});
9095
});
96+
97+
describe("agent-to-agent prompt context", () => {
98+
it("keeps volatile routing identifiers out of system prompt context", () => {
99+
const context = buildAgentToAgentMessageContext({
100+
requesterSessionKey: "agent:main:slack:channel:C123:thread:171.222",
101+
requesterChannel: "slack",
102+
targetSessionKey: "agent:worker:discord:channel:ops:run:run-123",
103+
});
104+
105+
expect(context).toContain("Agent 1 (requester) session: <REQUESTER_SESSION>.");
106+
expect(context).toContain("Agent 1 (requester) channel: slack.");
107+
expect(context).toContain("Agent 2 (target) session: <TARGET_SESSION>.");
108+
expect(context).not.toContain("agent:main:slack:channel:C123:thread:171.222");
109+
expect(context).not.toContain("agent:worker:discord:channel:ops:run:run-123");
110+
});
111+
112+
it("preserves optional session line shape with concrete channel values", () => {
113+
const context = buildAgentToAgentReplyContext({
114+
requesterSessionKey: "agent:requester:main",
115+
targetSessionKey: "agent:target:main",
116+
targetChannel: "telegram",
117+
currentRole: "target",
118+
turn: 2,
119+
maxTurns: 5,
120+
});
121+
122+
expect(context).toContain("Current agent: Agent 2 (target).");
123+
expect(context).toContain("Agent 1 (requester) session: <REQUESTER_SESSION>.");
124+
expect(context).not.toContain("Agent 1 (requester) channel:");
125+
expect(context).toContain("Agent 2 (target) session: <TARGET_SESSION>.");
126+
expect(context).toContain("Agent 2 (target) channel: telegram.");
127+
expect(context).not.toContain("agent:requester:main");
128+
expect(context).not.toContain("agent:target:main");
129+
});
130+
});

src/agents/tools/sessions-send-helpers.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ function buildAgentSessionLines(params: {
5454
targetChannel?: string;
5555
}): string[] {
5656
return [
57-
params.requesterSessionKey
58-
? `Agent 1 (requester) session: ${params.requesterSessionKey}.`
59-
: undefined,
57+
// Session keys are high-cardinality (thread/run ids), so concrete values churn the
58+
// system prompt and break provider prompt-cache reuse across A2A turns. Channels are
59+
// low-cardinality and inform reply formatting, so they stay concrete.
60+
params.requesterSessionKey ? "Agent 1 (requester) session: <REQUESTER_SESSION>." : undefined,
6061
params.requesterChannel
6162
? `Agent 1 (requester) channel: ${params.requesterChannel}.`
6263
: undefined,
63-
`Agent 2 (target) session: ${params.targetSessionKey}.`,
64+
"Agent 2 (target) session: <TARGET_SESSION>.",
6465
params.targetChannel ? `Agent 2 (target) channel: ${params.targetChannel}.` : undefined,
6566
].filter((line): line is string => Boolean(line));
6667
}

0 commit comments

Comments
 (0)