feat(engine): sort tools deterministically for prompt cache stability

This commit is contained in:
Matthew Greenberg
2026-05-08 19:58:39 -04:00
parent b01a360a59
commit 44303f696c
2 changed files with 39 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import { describe, it, expect } from "vitest";
/**
* Verifies that tools are sorted deterministically by name.
* This is critical for prompt caching — tool schemas are part of the
* API request, and reordering them breaks cache prefix matching.
*/
describe("deterministic tool ordering", () => {
it("sorts tools alphabetically by name", () => {
const tools = [
{ name: "write", execute: async () => {} },
{ name: "bash", execute: async () => {} },
{ name: "read", execute: async () => {} },
{ name: "edit", execute: async () => {} },
];
const sorted = [...tools].sort((a, b) => a.name.localeCompare(b.name));
expect(sorted.map((t) => t.name)).toEqual(["bash", "edit", "read", "write"]);
});
it("is stable across repeated sorts", () => {
const tools = [
{ name: "grep" },
{ name: "bash" },
{ name: "find" },
{ name: "read" },
];
const sorted1 = [...tools].sort((a, b) => a.name.localeCompare(b.name));
const sorted2 = [...tools].sort((a, b) => a.name.localeCompare(b.name));
expect(sorted1.map((t) => t.name)).toEqual(sorted2.map((t) => t.name));
});
});

View File

@@ -1757,6 +1757,10 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
boundaryContext.worktreePath,
boundaryContext.worktreeProjectRoot,
);
// Sort tools alphabetically by name for deterministic ordering.
// Prompt caching requires the tool list to be byte-identical across
// sessions — reordering breaks cache prefix matching.
customToolList.sort((a, b) => a.name.localeCompare(b.name));
// Last-chance abort hook. Fires *here* — after every awaited setup step
// in createFnAgent (provider registration, worktree validation, resource
// loader reload) and immediately before the actual LLM session spawn.