Skip to content

Commit 26befa5

Browse files
fix(agents): honor agentRuntimeOverride in memory gates
Mirrors the three-tier precedence from runReplyAgent's dispatch path: 1. agentRuntimeOverride === "pi" → use canonical provider (forces embedded) 2. agentRuntimeOverride is a CLI alias for provider → use the override 3. Fall through to resolveCliRuntimeExecutionProvider (the auth-profile-aware resolver already wired into both gates) Without (1) and (2), sessions with persisted agentRuntimeOverride: "claude-cli" were being classified as non-CLI by the memory gates while dispatch routed them through CLI — the same embedded transcript compaction failure mode that this PR's earlier commit fixed for auth-profile mappings. Adds regression tests for the CLI-override case at both gates. Addresses ClawSweeper P2 review comment on PR #86224.
1 parent 2d4f556 commit 26befa5

2 files changed

Lines changed: 174 additions & 17 deletions

File tree

src/auto-reply/reply/agent-runner-memory.test.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,4 +1663,141 @@ describe("runMemoryFlushIfNeeded", () => {
16631663
expect(flushCall.bootstrapPromptWarningSignaturesSeen).toEqual(["sig-a", "sig-b"]);
16641664
expect(flushCall.bootstrapPromptWarningSignature).toBe("sig-b");
16651665
});
1666+
1667+
it("skips memory flush when agentRuntimeOverride pins the session to claude-cli", async () => {
1668+
const sessionEntry: SessionEntry = {
1669+
sessionId: "session",
1670+
updatedAt: Date.now(),
1671+
totalTokens: 80_000,
1672+
compactionCount: 1,
1673+
agentRuntimeOverride: "claude-cli",
1674+
};
1675+
1676+
const entry = await runMemoryFlushIfNeeded({
1677+
cfg: {
1678+
agents: {
1679+
defaults: {
1680+
cliBackends: { "claude-cli": { command: "claude" } },
1681+
compaction: { memoryFlush: {} },
1682+
},
1683+
},
1684+
} as never,
1685+
followupRun: createTestFollowupRun({
1686+
provider: "anthropic",
1687+
model: "claude-opus-4-7",
1688+
}),
1689+
sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
1690+
defaultModel: "anthropic/claude-opus-4-7",
1691+
agentCfgContextTokens: 100_000,
1692+
resolvedVerboseLevel: "off",
1693+
sessionEntry,
1694+
sessionStore: { main: sessionEntry },
1695+
sessionKey: "main",
1696+
isHeartbeat: false,
1697+
replyOperation: createReplyOperation(),
1698+
});
1699+
1700+
expect(entry).toBe(sessionEntry);
1701+
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
1702+
});
1703+
1704+
it("skips preflight compaction when agentRuntimeOverride pins the session to claude-cli", async () => {
1705+
const sessionFile = path.join(rootDir, "cli-override-preflight.jsonl");
1706+
await fs.writeFile(
1707+
sessionFile,
1708+
`${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
1709+
"utf8",
1710+
);
1711+
registerMemoryFlushPlanResolverForTest(() => ({
1712+
softThresholdTokens: 1,
1713+
forceFlushTranscriptBytes: 1_000_000_000,
1714+
reserveTokensFloor: 0,
1715+
prompt: "Pre-compaction memory flush.\nNO_REPLY",
1716+
systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
1717+
relativePath: "memory/2023-11-14.md",
1718+
}));
1719+
const sessionEntry: SessionEntry = {
1720+
sessionId: "session",
1721+
sessionFile,
1722+
updatedAt: Date.now(),
1723+
totalTokensFresh: false,
1724+
agentRuntimeOverride: "claude-cli",
1725+
};
1726+
1727+
const entry = await runPreflightCompactionIfNeeded({
1728+
cfg: {
1729+
agents: {
1730+
defaults: {
1731+
cliBackends: { "claude-cli": { command: "claude" } },
1732+
compaction: { memoryFlush: {} },
1733+
},
1734+
},
1735+
} as never,
1736+
followupRun: createTestFollowupRun({
1737+
sessionId: "session",
1738+
sessionFile,
1739+
sessionKey: "main",
1740+
provider: "anthropic",
1741+
model: "claude-opus-4-7",
1742+
}),
1743+
defaultModel: "anthropic/claude-opus-4-7",
1744+
agentCfgContextTokens: 100,
1745+
sessionEntry,
1746+
sessionStore: { main: sessionEntry },
1747+
sessionKey: "main",
1748+
storePath: path.join(rootDir, "sessions.json"),
1749+
isHeartbeat: false,
1750+
replyOperation: createReplyOperation(),
1751+
});
1752+
1753+
expect(entry).toBe(sessionEntry);
1754+
expect(compactEmbeddedPiSessionMock).not.toHaveBeenCalled();
1755+
});
1756+
1757+
it("runs memory flush when agentRuntimeOverride is 'pi' even though auth.order prefers a CLI profile", async () => {
1758+
const sessionEntry: SessionEntry = {
1759+
sessionId: "session",
1760+
updatedAt: Date.now(),
1761+
totalTokens: 80_000,
1762+
compactionCount: 1,
1763+
agentRuntimeOverride: "pi",
1764+
};
1765+
1766+
await runMemoryFlushIfNeeded({
1767+
cfg: {
1768+
agents: {
1769+
defaults: {
1770+
cliBackends: { "claude-cli": { command: "claude" } },
1771+
compaction: { memoryFlush: {} },
1772+
},
1773+
},
1774+
auth: {
1775+
profiles: {
1776+
"direct-api": { provider: "anthropic" },
1777+
"cli-profile": { provider: "claude-cli" },
1778+
},
1779+
order: {
1780+
anthropic: ["cli-profile", "direct-api"],
1781+
},
1782+
},
1783+
} as never,
1784+
followupRun: createTestFollowupRun({
1785+
provider: "anthropic",
1786+
model: "claude-opus-4-7",
1787+
}),
1788+
sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
1789+
defaultModel: "anthropic/claude-opus-4-7",
1790+
agentCfgContextTokens: 100_000,
1791+
resolvedVerboseLevel: "off",
1792+
sessionEntry,
1793+
sessionStore: { main: sessionEntry },
1794+
sessionKey: "main",
1795+
isHeartbeat: false,
1796+
replyOperation: createReplyOperation(),
1797+
});
1798+
1799+
// agentRuntimeOverride: "pi" forces canonical-provider routing, so the
1800+
// CLI gate should NOT fire even when auth.order prefers a CLI profile.
1801+
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
1802+
});
16661803
});

src/auto-reply/reply/agent-runner-memory.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -634,14 +634,24 @@ export async function runPreflightCompactionIfNeeded(params: {
634634
params.followupRun.run,
635635
params.followupRun.run.provider,
636636
);
637+
const sessionRuntimeOverride = resolveMemoryFlushRuntimeOverrideForProvider({
638+
provider: params.followupRun.run.provider,
639+
entry,
640+
});
637641
const cliExecutionProvider =
638-
resolveCliRuntimeExecutionProvider({
639-
provider: params.followupRun.run.provider,
640-
cfg: params.cfg,
641-
agentId: params.followupRun.run.agentId,
642-
modelId: params.followupRun.run.model ?? params.defaultModel,
643-
authProfileId,
644-
}) ?? params.followupRun.run.provider;
642+
sessionRuntimeOverride === "pi"
643+
? params.followupRun.run.provider
644+
: ((sessionRuntimeOverride && isCliProvider(sessionRuntimeOverride, params.cfg)
645+
? sessionRuntimeOverride
646+
: undefined) ??
647+
resolveCliRuntimeExecutionProvider({
648+
provider: params.followupRun.run.provider,
649+
cfg: params.cfg,
650+
agentId: params.followupRun.run.agentId,
651+
modelId: params.followupRun.run.model ?? params.defaultModel,
652+
authProfileId,
653+
}) ??
654+
params.followupRun.run.provider);
645655
const isCli = isCliProvider(cliExecutionProvider, params.cfg);
646656
if (params.isHeartbeat || isCli) {
647657
return entry ?? params.sessionEntry;
@@ -894,23 +904,33 @@ export async function runMemoryFlushIfNeeded(params: {
894904
return sandboxCfg.workspaceAccess === "rw";
895905
})();
896906

907+
let entry =
908+
params.sessionEntry ??
909+
(params.sessionKey ? params.sessionStore?.[params.sessionKey] : undefined);
897910
const { authProfileId: flushAuthProfileId } = resolveRunAuthProfile(
898911
params.followupRun.run,
899912
params.followupRun.run.provider,
900913
);
914+
const sessionRuntimeOverride = resolveMemoryFlushRuntimeOverrideForProvider({
915+
provider: params.followupRun.run.provider,
916+
entry,
917+
});
901918
const cliExecutionProvider =
902-
resolveCliRuntimeExecutionProvider({
903-
provider: params.followupRun.run.provider,
904-
cfg: params.cfg,
905-
agentId: params.followupRun.run.agentId,
906-
modelId: params.followupRun.run.model ?? params.defaultModel,
907-
authProfileId: flushAuthProfileId,
908-
}) ?? params.followupRun.run.provider;
919+
sessionRuntimeOverride === "pi"
920+
? params.followupRun.run.provider
921+
: ((sessionRuntimeOverride && isCliProvider(sessionRuntimeOverride, params.cfg)
922+
? sessionRuntimeOverride
923+
: undefined) ??
924+
resolveCliRuntimeExecutionProvider({
925+
provider: params.followupRun.run.provider,
926+
cfg: params.cfg,
927+
agentId: params.followupRun.run.agentId,
928+
modelId: params.followupRun.run.model ?? params.defaultModel,
929+
authProfileId: flushAuthProfileId,
930+
}) ??
931+
params.followupRun.run.provider);
909932
const isCli = isCliProvider(cliExecutionProvider, params.cfg);
910933
const canAttemptFlush = memoryFlushWritable && !params.isHeartbeat && !isCli;
911-
let entry =
912-
params.sessionEntry ??
913-
(params.sessionKey ? params.sessionStore?.[params.sessionKey] : undefined);
914934
const contextWindowTokens = resolveMemoryFlushContextWindowTokens({
915935
cfg: params.cfg,
916936
provider: resolveFollowupContextConfigProvider({

0 commit comments

Comments
 (0)