Fix default conversation system prompt recompilation routing [LET-7976] (#1373)

This commit is contained in:
Devansh Jain
2026-03-12 19:34:35 -07:00
committed by GitHub
parent 182c65b77e
commit 94ff9f6796
4 changed files with 85 additions and 80 deletions

View File

@@ -270,59 +270,46 @@ export async function updateConversationLLMConfig(
return client.conversations.update(conversationId, payload); return client.conversations.update(conversationId, payload);
} }
export interface RecompileAgentSystemPromptOptions {
dryRun?: boolean;
/**
* Required when recompiling the special "default" conversation route.
* Passed as body param `agent_id` for agent-direct mode.
*/
agentId?: string;
}
interface ConversationSystemPromptRecompileClient {
conversations: {
recompile: (
conversationId: string,
params: {
dry_run?: boolean;
},
) => Promise<string>;
};
}
/** /**
* Recompile an agent's system prompt after memory writes so server-side prompt * Recompile an agent's system prompt after memory writes so server-side prompt
* state picks up the latest memory content. * state picks up the latest memory content.
* *
* @param conversationId - The conversation whose prompt should be recompiled * @param conversationId - The conversation whose prompt should be recompiled
* @param options - Optional dry-run control * @param agentId - Agent id for the parent conversation
* @param dryRun - Optional dry-run control
* @param clientOverride - Optional injected client for tests * @param clientOverride - Optional injected client for tests
* @returns The compiled system prompt returned by the API * @returns The compiled system prompt returned by the API
*/ */
export async function recompileAgentSystemPrompt( export async function recompileAgentSystemPrompt(
conversationId: string, conversationId: string,
options: RecompileAgentSystemPromptOptions = {}, agentId: string,
clientOverride?: ConversationSystemPromptRecompileClient, dryRun?: boolean,
): Promise<string> { clientOverride?: {
const client = (clientOverride ?? conversations: {
(await getClient())) as ConversationSystemPromptRecompileClient; recompile: (
conversationId: string,
const params: { params: {
dry_run?: boolean; dry_run?: boolean;
agent_id?: string; agent_id?: string;
} = { },
dry_run: options.dryRun, ) => Promise<string>;
}; };
},
): Promise<string> {
const client = (clientOverride ?? (await getClient())) as Exclude<
typeof clientOverride,
undefined
>;
if (conversationId === "default") { if (!agentId) {
if (!options.agentId) { throw new Error("recompileAgentSystemPrompt requires agentId");
throw new Error(
'recompileAgentSystemPrompt requires options.agentId when conversationId is "default"',
);
}
params.agent_id = options.agentId;
} }
const params = {
dry_run: dryRun,
agent_id: agentId,
};
return client.conversations.recompile(conversationId, params); return client.conversations.recompile(conversationId, params);
} }

View File

@@ -1,13 +1,11 @@
import { import { recompileAgentSystemPrompt } from "../../agent/modify";
type RecompileAgentSystemPromptOptions,
recompileAgentSystemPrompt,
} from "../../agent/modify";
export type MemorySubagentType = "init" | "reflection"; export type MemorySubagentType = "init" | "reflection";
type RecompileAgentSystemPromptFn = ( type RecompileAgentSystemPromptFn = (
conversationId: string, conversationId: string,
options?: RecompileAgentSystemPromptOptions, agentId: string,
dryRun?: boolean,
) => Promise<string>; ) => Promise<string>;
export interface MemorySubagentCompletionArgs { export interface MemorySubagentCompletionArgs {
@@ -46,9 +44,7 @@ export async function handleMemorySubagentCompletion(
inFlight = (async () => { inFlight = (async () => {
do { do {
deps.recompileQueuedByConversation.delete(conversationId); deps.recompileQueuedByConversation.delete(conversationId);
await recompileAgentSystemPromptFn(conversationId, { await recompileAgentSystemPromptFn(conversationId, agentId);
...(conversationId === "default" ? { agentId } : {}),
});
} while (deps.recompileQueuedByConversation.has(conversationId)); } while (deps.recompileQueuedByConversation.has(conversationId));
})().finally(() => { })().finally(() => {
// Cleanup runs only after the shared promise settles, so every // Cleanup runs only after the shared promise settles, so every

View File

@@ -3,71 +3,87 @@ import { recompileAgentSystemPrompt } from "../../agent/modify";
describe("recompileAgentSystemPrompt", () => { describe("recompileAgentSystemPrompt", () => {
test("calls the conversation recompile endpoint with mapped params", async () => { test("calls the conversation recompile endpoint with mapped params", async () => {
const agentsRecompileMock = mock( const conversationsRecompileMock = mock(
(_conversationId: string, _params?: Record<string, unknown>) => (_conversationId: string, _params?: Record<string, unknown>) =>
Promise.resolve("compiled-system-prompt"), Promise.resolve("compiled-system-prompt"),
); );
const client = { const client = {
conversations: { conversations: {
recompile: agentsRecompileMock, recompile: conversationsRecompileMock,
}, },
}; };
const compiledPrompt = await recompileAgentSystemPrompt( const compiledPrompt = await recompileAgentSystemPrompt(
"conv-123", "conv-123",
{ "agent-123",
dryRun: true, true,
},
client, client,
); );
expect(compiledPrompt).toBe("compiled-system-prompt"); expect(compiledPrompt).toBe("compiled-system-prompt");
expect(agentsRecompileMock).toHaveBeenCalledWith("conv-123", { expect(conversationsRecompileMock).toHaveBeenCalledWith("conv-123", {
dry_run: true, dry_run: true,
agent_id: "agent-123",
}); });
}); });
test("passes agent_id for default conversation recompiles", async () => { test("passes agent_id for default conversation recompiles", async () => {
const agentsRecompileMock = mock( const conversationsRecompileMock = mock(
(_conversationId: string, _params?: Record<string, unknown>) => (_conversationId: string, _params?: Record<string, unknown>) =>
Promise.resolve("compiled-system-prompt"), Promise.resolve("compiled-system-prompt"),
); );
const client = { const client = {
conversations: { conversations: {
recompile: agentsRecompileMock, recompile: conversationsRecompileMock,
}, },
}; };
await recompileAgentSystemPrompt( await recompileAgentSystemPrompt("default", "agent-123", undefined, client);
"default",
{
agentId: "agent-123",
},
client,
);
expect(agentsRecompileMock).toHaveBeenCalledWith("default", { expect(conversationsRecompileMock).toHaveBeenCalledWith("default", {
dry_run: undefined, dry_run: undefined,
agent_id: "agent-123", agent_id: "agent-123",
}); });
}); });
test("throws when default conversation recompile lacks agent id", async () => { test("passes non-default conversation ids through unchanged", async () => {
const agentsRecompileMock = mock( const conversationsRecompileMock = mock(
(_conversationId: string, _params?: Record<string, unknown>) => (_conversationId: string, _params?: Record<string, unknown>) =>
Promise.resolve("compiled-system-prompt"), Promise.resolve("compiled-system-prompt"),
); );
const client = { const client = {
conversations: { conversations: {
recompile: agentsRecompileMock, recompile: conversationsRecompileMock,
},
};
await recompileAgentSystemPrompt(
"['default']",
"agent-123",
undefined,
client,
);
expect(conversationsRecompileMock).toHaveBeenCalledWith("['default']", {
dry_run: undefined,
agent_id: "agent-123",
});
});
test("throws when conversation recompile has empty agent id", async () => {
const conversationsRecompileMock = mock(
(_conversationId: string, _params?: Record<string, unknown>) =>
Promise.resolve("compiled-system-prompt"),
);
const client = {
conversations: {
recompile: conversationsRecompileMock,
}, },
}; };
await expect( await expect(
recompileAgentSystemPrompt("default", {}, client), recompileAgentSystemPrompt("default", "", undefined, client),
).rejects.toThrow( ).rejects.toThrow("recompileAgentSystemPrompt requires agentId");
'recompileAgentSystemPrompt requires options.agentId when conversationId is "default"', expect(conversationsRecompileMock).not.toHaveBeenCalled();
);
expect(agentsRecompileMock).not.toHaveBeenCalled();
}); });
}); });

View File

@@ -1,9 +1,8 @@
import { beforeEach, describe, expect, mock, test } from "bun:test"; import { beforeEach, describe, expect, mock, test } from "bun:test";
import type { RecompileAgentSystemPromptOptions } from "../../agent/modify";
import { handleMemorySubagentCompletion } from "../../cli/helpers/memorySubagentCompletion"; import { handleMemorySubagentCompletion } from "../../cli/helpers/memorySubagentCompletion";
const recompileAgentSystemPromptMock = mock( const recompileAgentSystemPromptMock = mock(
(_conversationId: string, _opts?: RecompileAgentSystemPromptOptions) => (_conversationId: string, _agentId: string, _dryRun?: boolean) =>
Promise.resolve("compiled-system-prompt"), Promise.resolve("compiled-system-prompt"),
); );
@@ -19,7 +18,7 @@ describe("memory subagent recompile handling", () => {
beforeEach(() => { beforeEach(() => {
recompileAgentSystemPromptMock.mockReset(); recompileAgentSystemPromptMock.mockReset();
recompileAgentSystemPromptMock.mockImplementation( recompileAgentSystemPromptMock.mockImplementation(
(_agentId: string, _opts?: RecompileAgentSystemPromptOptions) => (_conversationId: string, _agentId: string, _dryRun?: boolean) =>
Promise.resolve("compiled-system-prompt"), Promise.resolve("compiled-system-prompt"),
); );
}); });
@@ -44,7 +43,7 @@ describe("memory subagent recompile handling", () => {
); );
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith( expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith(
"conv-init-1", "conv-init-1",
{}, "agent-init-1",
); );
}); });
@@ -63,9 +62,10 @@ describe("memory subagent recompile handling", () => {
}, },
); );
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith("default", { expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith(
agentId: "agent-default", "default",
}); "agent-default",
);
}); });
test("queues a trailing recompile when later completions land mid-flight", async () => { test("queues a trailing recompile when later completions land mid-flight", async () => {
@@ -176,7 +176,13 @@ describe("memory subagent recompile handling", () => {
"Reflected on /palace, the halls remember more now.", "Reflected on /palace, the halls remember more now.",
); );
expect(recompileAgentSystemPromptMock).toHaveBeenCalledTimes(2); expect(recompileAgentSystemPromptMock).toHaveBeenCalledTimes(2);
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith("conv-a", {}); expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith(
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith("conv-b", {}); "conv-a",
"agent-shared",
);
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith(
"conv-b",
"agent-shared",
);
}); });
}); });