Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
86 changes: 77 additions & 9 deletions assistant/src/__tests__/llm-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { describe, expect, test } from "bun:test";

import { z } from "zod";

import { resolveCallSiteConfig, resolveDefaultProfileKey } from "../config/llm-resolver.js";
import {
resolveCallSiteConfig,
resolveDefaultProfileKey,
} from "../config/llm-resolver.js";
import { type LLMCallSite, LLMSchema } from "../config/schemas/llm.js";

const fullDefault = {
Expand Down Expand Up @@ -690,13 +693,28 @@ describe("resolveCallSiteConfig", () => {
});

const callSites: LLMCallSite[] = [
"mainAgent", "subagentSpawn", "heartbeatAgent", "filingAgent",
"compactionAgent", "analyzeConversation", "callAgent",
"memoryExtraction", "memoryConsolidation", "memoryRetrieval",
"memoryRouter", "recall", "conversationSummarization",
"commitMessage", "conversationStarters", "replySuggestion",
"conversationTitle", "identityIntro", "emptyStateGreeting",
"notificationDecision", "interactionClassifier", "inference",
"mainAgent",
"subagentSpawn",
"heartbeatAgent",
"filingAgent",
"compactionAgent",
"analyzeConversation",
"callAgent",
"memoryExtraction",
"memoryConsolidation",
"memoryRetrieval",
"memoryRouter",
"recall",
"conversationSummarization",
"commitMessage",
"conversationStarters",
"replySuggestion",
"conversationTitle",
"identityIntro",
"emptyStateGreeting",
"notificationDecision",
"interactionClassifier",
"inference",
];

for (const cs of callSites) {
Expand Down Expand Up @@ -778,7 +796,10 @@ describe("resolveCallSiteConfig", () => {
provider_connection: "anthropic-managed",
},
profiles: {
fireworks: { provider: "fireworks", model: "accounts/fireworks/models/kimi-k2p5" },
fireworks: {
provider: "fireworks",
model: "accounts/fireworks/models/kimi-k2p5",
},
},
activeProfile: "fireworks",
});
Expand Down Expand Up @@ -874,3 +895,50 @@ describe("resolveDefaultProfileKey", () => {
);
});
});

describe("memory v3 call sites resolve through the standard resolver", () => {
const llm = LLMSchema.parse({
default: fullDefault,
profiles: {
balanced: { provider: "anthropic", model: "claude-sonnet-4-7" },
"cost-optimized": {
provider: "anthropic",
model: "claude-haiku-4-5-20251001",
},
},
});

test("memoryV3Filter and memoryV3Descent resolve to the cost-optimized profile", () => {
expect(resolveDefaultProfileKey("memoryV3Filter", llm)).toBe(
"cost-optimized",
);
expect(resolveDefaultProfileKey("memoryV3Descent", llm)).toBe(
"cost-optimized",
);
expect(resolveCallSiteConfig("memoryV3Filter", llm).model).toBe(
"claude-haiku-4-5-20251001",
);
expect(resolveCallSiteConfig("memoryV3Descent", llm).model).toBe(
"claude-haiku-4-5-20251001",
);
});

test("memoryV3Gate resolves to the balanced (capable) profile", () => {
expect(resolveDefaultProfileKey("memoryV3Gate", llm)).toBe("balanced");
expect(resolveCallSiteConfig("memoryV3Gate", llm).model).toBe(
"claude-sonnet-4-7",
);
});

test("v3 call sites are addressable as call-site override keys", () => {
const overridden = LLMSchema.parse({
default: fullDefault,
callSites: {
memoryV3Gate: { model: "claude-opus-4-7" },
},
});
expect(resolveCallSiteConfig("memoryV3Gate", overridden).model).toBe(
"claude-opus-4-7",
);
});
});
4 changes: 4 additions & 0 deletions assistant/src/config/call-site-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
memoryV2Migration: { profile: "cost-optimized" },
memoryV2Sweep: { profile: "cost-optimized" },
memoryV2Consolidation: { profile: "balanced" },
// memory v3: cheap filter + descent, capable gate.
memoryV3Filter: { profile: "cost-optimized" },
memoryV3Descent: { profile: "cost-optimized" },
memoryV3Gate: { profile: "balanced" },
conversationSummarization: { profile: "cost-optimized" },
conversationTitle: { profile: "cost-optimized" },
approvalCopy: { profile: "cost-optimized" },
Expand Down
109 changes: 108 additions & 1 deletion assistant/src/config/schemas/__tests__/memory-v2.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";

import { MemoryConfigSchema } from "../memory.js";
import { MemoryV2ConfigSchema } from "../memory-v2.js";
import { MemoryV2ConfigSchema, MemoryV3ConfigSchema } from "../memory-v2.js";

describe("MemoryV2ConfigSchema", () => {
test("parses an empty object to documented defaults", () => {
Expand Down Expand Up @@ -212,6 +212,113 @@ describe("MemoryV2ConfigSchema", () => {
});
});

describe("MemoryV3ConfigSchema", () => {
test("parses an empty object to documented defaults", () => {
const parsed = MemoryV3ConfigSchema.parse({});
expect(parsed).toEqual({
enabled: false,
shadow: false,
passCap: 3,
breadthBudget: 6,
maxDepth: 6,
denseQuota: { activeDomain: 30, offDomain: 8 },
lanes: { hot: true, sparse: true, dense: true, tree: true, edges: true },
ks: [5, 10, 25, 50],
});
});

test("parses undefined to the same defaults (top-level .default)", () => {
expect(MemoryV3ConfigSchema.parse(undefined)).toEqual(
MemoryV3ConfigSchema.parse({}),
);
});

test("defaults to disabled for backwards compatibility", () => {
expect(MemoryV3ConfigSchema.parse({}).enabled).toBe(false);
expect(MemoryV3ConfigSchema.parse({}).shadow).toBe(false);
});

test("accepts explicit scalar overrides", () => {
const parsed = MemoryV3ConfigSchema.parse({
enabled: true,
shadow: true,
passCap: 5,
breadthBudget: 10,
maxDepth: 8,
});
expect(parsed.enabled).toBe(true);
expect(parsed.shadow).toBe(true);
expect(parsed.passCap).toBe(5);
expect(parsed.breadthBudget).toBe(10);
expect(parsed.maxDepth).toBe(8);
});

test("accepts explicit denseQuota override", () => {
const parsed = MemoryV3ConfigSchema.parse({
denseQuota: { activeDomain: 50, offDomain: 12 },
});
expect(parsed.denseQuota).toEqual({ activeDomain: 50, offDomain: 12 });
});

test("accepts a partial lanes override and defaults the rest", () => {
const parsed = MemoryV3ConfigSchema.parse({ lanes: { dense: false } });
expect(parsed.lanes).toEqual({
hot: true,
sparse: true,
dense: false,
tree: true,
edges: true,
});
});

test("accepts an explicit ks override", () => {
const parsed = MemoryV3ConfigSchema.parse({ ks: [1, 3, 7] });
expect(parsed.ks).toEqual([1, 3, 7]);
});

test("rejects a non-boolean enabled", () => {
expect(() => MemoryV3ConfigSchema.parse({ enabled: "yes" })).toThrow();
});

test("rejects a non-integer passCap", () => {
expect(() => MemoryV3ConfigSchema.parse({ passCap: 2.5 })).toThrow();
});

test("rejects non-number ks entries", () => {
expect(() => MemoryV3ConfigSchema.parse({ ks: ["a"] })).toThrow();
});
});

describe("MemoryConfigSchema integration with v3 block", () => {
test("includes a v3 block defaulting to disabled when v3 is omitted", () => {
const parsed = MemoryConfigSchema.parse({});
expect(parsed.v3).toBeDefined();
expect(parsed.v3.enabled).toBe(false);
expect(parsed.v3.shadow).toBe(false);
expect(parsed.v3.passCap).toBe(3);
expect(parsed.v3.lanes.dense).toBe(true);
expect(parsed.v3.ks).toEqual([5, 10, 25, 50]);
});

test("leaves pre-existing configs (no v3 key) otherwise unchanged", () => {
// A config authored before v3 existed parses fine and its v2 block is
// untouched; the v3 block is purely additive.
const parsed = MemoryConfigSchema.parse({ v2: { top_k: 50 } });
expect(parsed.v2.top_k).toBe(50);
expect(parsed.v3.enabled).toBe(false);
});

test("propagates v3 overrides through MemoryConfigSchema", () => {
const parsed = MemoryConfigSchema.parse({
v3: { enabled: true, passCap: 4 },
});
expect(parsed.v3.enabled).toBe(true);
expect(parsed.v3.passCap).toBe(4);
// Non-overridden v3 fields keep their defaults.
expect(parsed.v3.maxDepth).toBe(6);
});
});

describe("MemoryConfigSchema integration with v2 block", () => {
test("parses an empty memory config and includes a v2 block with defaults", () => {
const parsed = MemoryConfigSchema.parse({});
Expand Down
21 changes: 21 additions & 0 deletions assistant/src/config/schemas/call-site-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,27 @@ const CATALOG_RECORD: CatalogRecord = {
"Selects which concept pages to inject for the next agent turn by routing over a cached page index.",
domain: "memory",
},
memoryV3Filter: {
id: "memoryV3Filter",
displayName: "Memory V3 Filter",
description:
"Cheaply filters the V3 multi-lane candidate set before descent.",
domain: "memory",
},
memoryV3Descent: {
id: "memoryV3Descent",
displayName: "Memory V3 Descent",
description:
"Drives the V3 bounded-descent traversal through the memory tree.",
domain: "memory",
},
memoryV3Gate: {
id: "memoryV3Gate",
displayName: "Memory V3 Gate",
description:
"Final capable gate that decides which V3 candidates are injected for the next turn.",
domain: "memory",
},
memoryV2Consolidation: {
id: "memoryV2Consolidation",
displayName: "Memory V2 Consolidation",
Expand Down
3 changes: 3 additions & 0 deletions assistant/src/config/schemas/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export const LLMCallSiteEnum = z.enum([
"memoryV2Migration",
"memoryV2Sweep",
"memoryRouter",
"memoryV3Filter",
"memoryV3Descent",
"memoryV3Gate",
"memoryV2Consolidation",
"memoryRetrospective",
"recall",
Expand Down
Loading
Loading