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
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ function makeConfig(): AssistantConfig {
c_assistant: 0.5,
c_now: 0.5,
top_k: 8,
top_k_skills: 0,
},
},
} as unknown as AssistantConfig;
Expand Down
7 changes: 4 additions & 3 deletions assistant/src/cli/commands/memory-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,10 @@ Examples:
"after",
`
Re-runs the v2 skill catalog seed against the current skill set, replacing
both the in-process skill cache and the memory_v2_skills Qdrant collection.
Useful after editing a skill's SKILL.md, after a feature-flag flip changes
the enabled-skill set, or to recover a corrupted skills collection.
both the in-process skill cache and the skill entries in the unified
memory_v2_concept_pages Qdrant collection (under the skills/<id> slug
prefix). Useful after editing a skill's SKILL.md, after a feature-flag flip
changes the enabled-skill set, or to recover corrupted skill embeddings.

Unlike 'reembed' (concept pages), this runs synchronously inside the
daemon — the command returns only once the seed completes. Requires both
Expand Down
4 changes: 1 addition & 3 deletions assistant/src/config/schemas/__tests__/memory-v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ describe("MemoryV2ConfigSchema", () => {
c_now: 0.2,
k: 0.5,
hops: 2,
top_k: 20,
top_k: 25,
ann_candidate_limit: null,
top_k_skills: 5,
epsilon: 0.01,
dense_weight: 0.85,
sparse_weight: 0.15,
Expand Down Expand Up @@ -166,7 +165,6 @@ describe("MemoryConfigSchema integration with v2 block", () => {
expect(parsed.v2.d).toBe(0.3);
expect(parsed.v2.dense_weight).toBe(0.85);
expect(parsed.v2.sparse_weight).toBe(0.15);
expect(parsed.v2.top_k_skills).toBe(5);
expect(parsed.v2.consolidation_interval_hours).toBe(4);
expect(parsed.v2.max_page_chars).toBe(5000);
});
Expand Down
12 changes: 2 additions & 10 deletions assistant/src/config/schemas/memory-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ export const MemoryV2ConfigSchema = z
.number({ error: "memory.v2.top_k must be a number" })
.int("memory.v2.top_k must be an integer")
.positive("memory.v2.top_k must be a positive integer")
.default(20)
.default(25)
.describe(
"Number of top-activation concept pages considered for injection per turn",
"Number of top-activation entries (concept pages and skills combined) considered for injection per turn. Skills are scored alongside concepts in the same pool; this cap covers both.",
),
ann_candidate_limit: z
.number({ error: "memory.v2.ann_candidate_limit must be a number" })
Expand All @@ -105,14 +105,6 @@ export const MemoryV2ConfigSchema = z
.describe(
"Per-channel cap on the unrestricted ANN candidate query (dense and sparse each return up to this many hits before they are unioned and fed into the activation pipeline). `null` = unlimited (every page in the v2 collection is eligible). Increase or null this out to surface more candidates at the cost of higher per-turn embedding/scoring work.",
),
top_k_skills: z
.number({ error: "memory.v2.top_k_skills must be a number" })
.int()
.nonnegative()
.default(5)
.describe(
"Cap on the per-turn skill-autoinjection slate rendered in `### Skills You Can Use`. 0 disables skill autoinjection without code changes.",
),
epsilon: z
.number({ error: "memory.v2.epsilon must be a number" })
.min(0, "memory.v2.epsilon must be >= 0")
Expand Down
18 changes: 14 additions & 4 deletions assistant/src/daemon/memory-v2-startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { getLogger } from "../util/logger.js";
const log = getLogger("memory-v2-startup");

/**
* Fire-and-forget seed of the v2 parallel skill embedding collection. Gated
* on both the `memory-v2-enabled` feature flag and the workspace-level
* `config.memory.v2.enabled` switch so v2 modules stay out of the v1 startup
* path when v2 is off.
* Fire-and-forget seed of the v2 skill entries (now indexed alongside concept
* pages in `memory_v2_concept_pages` under the `skills/<id>` slug prefix), and
* a one-shot best-effort cleanup of the legacy `memory_v2_skills` Qdrant
* collection. Gated on both the `memory-v2-enabled` feature flag and the
* workspace-level `config.memory.v2.enabled` switch so v2 modules stay out of
* the v1 startup path when v2 is off.
*
* Uses a dynamic import so v2 code does not load unless the gate passes.
* Never awaits — startup must not block on this (see `assistant/CLAUDE.md`
Expand All @@ -32,4 +34,12 @@ export function maybeSeedMemoryV2Skills(config: AssistantConfig): void {
void import("../memory/v2/skill-store.js")
.then(({ seedV2SkillEntries }) => seedV2SkillEntries())
.catch((err) => log.warn({ err }, "Failed to seed v2 skill entries"));
void import("../memory/v2/qdrant.js")
.then(({ dropLegacySkillsCollection }) => dropLegacySkillsCollection())
.catch((err) =>
log.warn(
{ err },
"Failed to drop legacy memory_v2_skills collection — non-fatal",
),
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {
MemoryV2ConceptRowRecord,
MemoryV2ConfigSnapshot,
MemoryV2SkillRowRecord,
} from "../../memory-v2-activation-log-store.js";

export const sampleConcepts: MemoryV2ConceptRowRecord[] = [
Expand All @@ -19,6 +18,20 @@ export const sampleConcepts: MemoryV2ConceptRowRecord[] = [
source: "both",
status: "injected",
},
{
slug: "skills/skill-1",
finalActivation: 0.8,
ownActivation: 0.8,
priorActivation: 0,
simUser: 0.5,
simAssistant: 0.4,
simNow: 0.3,
simUserRerankBoost: 0,
simAssistantRerankBoost: 0,
spreadContribution: 0,
source: "ann_top50",
status: "injected",
},
{
slug: "concept-b",
finalActivation: 0.4,
Expand All @@ -35,17 +48,6 @@ export const sampleConcepts: MemoryV2ConceptRowRecord[] = [
},
];

export const sampleSkills: MemoryV2SkillRowRecord[] = [
{
id: "skill-1",
activation: 0.8,
simUser: 0.5,
simAssistant: 0.4,
simNow: 0.3,
status: "injected",
},
];

export const sampleConfig: MemoryV2ConfigSnapshot = {
d: 0.85,
c_user: 1.0,
Expand All @@ -54,6 +56,5 @@ export const sampleConfig: MemoryV2ConfigSnapshot = {
k: 5,
hops: 2,
top_k: 10,
top_k_skills: 3,
epsilon: 0.001,
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { memoryV2ActivationLogs } from "../schema.js";
import {
sampleConcepts,
sampleConfig,
sampleSkills,
} from "./fixtures/memory-v2-activation-fixtures.js";

initializeDb();
Expand All @@ -53,7 +52,6 @@ describe("memory-v2-activation-log-store", () => {
turn: 3,
mode: "per-turn",
concepts: sampleConcepts,
skills: sampleSkills,
config: sampleConfig,
});

Expand All @@ -65,7 +63,6 @@ describe("memory-v2-activation-log-store", () => {
expect(result!.turn).toBe(3);
expect(result!.mode).toBe("per-turn");
expect(result!.concepts).toEqual(sampleConcepts);
expect(result!.skills).toEqual(sampleSkills);
expect(result!.config).toEqual(sampleConfig);
});

Expand All @@ -82,15 +79,13 @@ describe("memory-v2-activation-log-store", () => {
turn: 1,
mode: "context-load",
concepts: sampleConcepts,
skills: sampleSkills,
config: sampleConfig,
});
recordMemoryV2ActivationLog({
conversationId,
turn: 2,
mode: "per-turn",
concepts: sampleConcepts,
skills: sampleSkills,
config: sampleConfig,
});

Expand All @@ -110,7 +105,6 @@ describe("memory-v2-activation-log-store", () => {
turn: 3,
mode: "per-turn",
concepts: sampleConcepts,
skills: sampleSkills,
config: sampleConfig,
});

Expand Down
20 changes: 6 additions & 14 deletions assistant/src/memory/memory-v2-activation-log-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,6 @@ export interface MemoryV2ConceptRowRecord {
status: "in_context" | "injected" | "not_injected" | "page_missing";
}

export interface MemoryV2SkillRowRecord {
id: string;
activation: number;
simUser: number;
simAssistant: number;
simNow: number;
status: "injected" | "not_injected";
}

export interface MemoryV2ConfigSnapshot {
d: number;
c_user: number;
Expand All @@ -47,7 +38,6 @@ export interface MemoryV2ConfigSnapshot {
k: number;
hops: number;
top_k: number;
top_k_skills: number;
epsilon: number;
}

Expand All @@ -56,14 +46,18 @@ export interface RecordMemoryV2ActivationLogParams {
turn: number;
mode: "context-load" | "per-turn";
concepts: MemoryV2ConceptRowRecord[];
skills: MemoryV2SkillRowRecord[];
config: MemoryV2ConfigSnapshot;
}

export function recordMemoryV2ActivationLog(
params: RecordMemoryV2ActivationLogParams,
): void {
const db = getDb();
// Skills now live as concept rows under `slug: "skills/<id>"`, so the
// separate `skills_json` column is always written empty. The column itself
// remains in the schema for backwards-compat with prior log rows; the
// reader drops it. A future migration can DROP the column once those rows
// age out of relevance.
db.insert(memoryV2ActivationLogs)
.values({
id: uuid(),
Expand All @@ -72,7 +66,7 @@ export function recordMemoryV2ActivationLog(
turn: params.turn,
mode: params.mode,
conceptsJson: JSON.stringify(params.concepts),
skillsJson: JSON.stringify(params.skills),
skillsJson: "[]",
configJson: JSON.stringify(params.config),
createdAt: Date.now(),
})
Expand Down Expand Up @@ -100,7 +94,6 @@ export interface MemoryV2ActivationLog {
turn: number;
mode: "context-load" | "per-turn";
concepts: MemoryV2ConceptRowRecord[];
skills: MemoryV2SkillRowRecord[];
config: MemoryV2ConfigSnapshot;
}

Expand All @@ -122,7 +115,6 @@ export function getMemoryV2ActivationLogByMessageIds(
turn: row.turn,
mode: row.mode as "context-load" | "per-turn",
concepts: JSON.parse(row.conceptsJson) as MemoryV2ConceptRowRecord[],
skills: JSON.parse(row.skillsJson) as MemoryV2SkillRowRecord[],
config: JSON.parse(row.configJson) as MemoryV2ConfigSnapshot,
};
}
Loading
Loading