From db829b4d74d2c2306a2ebb492149186f15551ab6 Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:05:42 +0200 Subject: [PATCH 1/9] fix(setup): ensure project and bank-global mental models Guided setup no longer skips starters when a shared coding bank already has other projects' models. Offer/ensure compares expected starter ids (bank-global + active project) from the resolved template. Coding project template resolution now merges bank-global operating preferences, and bundled models request refresh_after_consolidation. Closes #528 --- docs-site/src/content/docs/start/setup-tui.md | 2 +- extensions/banks/bank-templates.ts | 62 +++++++++- extensions/tui/guided-setup.ts | 116 ++++++++++++++---- tests/bank-templates.test.ts | 45 ++++++- tests/guided-setup.test.ts | 105 +++++++++++++--- tests/mental-models.test.ts | 10 +- 6 files changed, 291 insertions(+), 49 deletions(-) diff --git a/docs-site/src/content/docs/start/setup-tui.md b/docs-site/src/content/docs/start/setup-tui.md index f4ae2f50..56c7a593 100644 --- a/docs-site/src/content/docs/start/setup-tui.md +++ b/docs-site/src/content/docs/start/setup-tui.md @@ -39,7 +39,7 @@ Guided setup handles: 2. memory profile 3. **agent use** (coding vs conversation) 4. project and/or user bank target (existing vs create; one-line `Server: … · Bank: …` status) -5. optional starter mental models (dry-run + confirm; skipped offline / when catalog already present) +5. optional starter mental models (dry-run + confirm; skipped offline / when **all expected** bank-global + this-project starters are already present — other projects' models on a shared coding bank do not skip) 6. optional dry-run-first historical import (skipped offline) Setup also prints docs links for the current area, such as [memory profiles](/pi-hindsight/start/memory-profiles/) and [imports](/pi-hindsight/guides/importing-sessions/). diff --git a/extensions/banks/bank-templates.ts b/extensions/banks/bank-templates.ts index 59126497..9479805a 100644 --- a/extensions/banks/bank-templates.ts +++ b/extensions/banks/bank-templates.ts @@ -44,6 +44,24 @@ function bankConfigFromMissions(missions: BankMissionDefaults): BankTemplateConf // Project-tier models get project: stamped at apply time (resolveBankTemplateManifest). const RETAIN_COMPAT_TAGS = ["source:pi"] as const; +/** Default after observations consolidation; matches agent createMentalModel path. */ +const DEFAULT_MM_TRIGGER: NonNullable = { + refresh_after_consolidation: true, +}; + +// Bank-global on shared coding bank (no project:* tag) — injects for every project. +const CODING_BANK_GLOBAL_MENTAL_MODELS: BankTemplateMentalModel[] = [ + { + id: "coding-assistant-operating-preferences", + name: "Coding assistant operating preferences", + source_query: + "What durable preferences has the user shown for how coding assistants should plan, verify, commit, and use tools across repositories? Capture only stable cross-project habits, not one-off task instructions.", + tags: [...RETAIN_COMPAT_TAGS], + max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, + }, +]; + // Coding project models — durable engineering knowledge (oh-my-pi-shaped core + a few extras). const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ { @@ -53,6 +71,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What are the stable architecture boundaries, modules, and seams in this project?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 800, + trigger: DEFAULT_MM_TRIGGER, }, { id: "project-conventions", @@ -61,6 +80,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What are this project's conventions for code style, build, testing, release, and review? Only include conventions explicit in the project or repeatedly enforced.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 800, + trigger: DEFAULT_MM_TRIGGER, }, { id: "project-decisions", @@ -69,6 +89,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable architectural or product decisions have been made for this project, and what rationale or trade-offs were recorded? Exclude transient plans and active task state.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 800, + trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -81,6 +102,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What goals, commitments, deadlines, and open loops is the user tracking in this context? Prefer durable ongoing items over one-off chat filler.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 800, + trigger: DEFAULT_MM_TRIGGER, }, { id: "people-and-context", @@ -89,6 +111,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "Which people, roles, relationships, and recurring situations matter here? Capture only durable context the assistant needs, not sensitive private details.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, { id: "decisions-and-preferences", @@ -97,6 +120,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable decisions, preferences, and constraints has the user stated for this conversation or life domain? Exclude one-off requests.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 800, + trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -108,6 +132,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for collaboration, review, autonomy, and communication?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, { id: "coding-assistant-operating-preferences", @@ -116,6 +141,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for how coding assistants should plan, verify, commit, and use tools?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, { id: "cross-project-workflow-habits", @@ -124,6 +150,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What workflow habits recur across the user's repositories, issue tracking, PR review, and release process?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -135,6 +162,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for tone, length, language, and how the assistant should respond?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, { id: "life-workflow-habits", @@ -143,6 +171,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What recurring real-life workflows, planning habits, and task-management preferences does the user express across conversations?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, { id: "priority-and-scheduling-preferences", @@ -151,6 +180,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "How does the user prefer to prioritize, schedule, and trade off time across personal and work tasks? Capture durable patterns only.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, + trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -250,10 +280,19 @@ function slugProjectId(projectId: string): string { ); } +/** Bank-global models merged into coding project template apply (unstamped). */ +export function bankGlobalMentalModelsForTemplate( + templateId: string, +): readonly BankTemplateMentalModel[] { + if (templateId === "pi-coding-project") return CODING_BANK_GLOBAL_MENTAL_MODELS; + return []; +} + /** * Resolve template manifest for import. For project-target templates with a projectId, * stamp project-tier mental models with project: tags and id suffixes so multi-project - * domain banks do not share one architecture model (ADR-005). + * domain banks do not share one architecture model (ADR-005). Coding project templates also + * merge bank-global models (no project tag) so setup can ensure cross-project starters. */ export function resolveBankTemplateManifest( template: BuiltInBankTemplate, @@ -269,11 +308,13 @@ export function resolveBankTemplateManifest( if (template.target === "project" && projectId && mental_models?.length) { const suffix = slugProjectId(projectId); const projectTag = `project:${projectId}`; - mental_models = mental_models.map((model) => ({ + const stamped = mental_models.map((model) => ({ ...model, id: `${model.id}--${suffix}`, tags: [...new Set([...(model.tags ?? []), "source:pi", projectTag])], })); + const bankGlobal = bankGlobalMentalModelsForTemplate(template.id); + mental_models = bankGlobal.length > 0 ? [...bankGlobal, ...stamped] : stamped; } return { ...template.manifest, @@ -281,3 +322,20 @@ export function resolveBankTemplateManifest( bank: bankConfigFromMissions(missions), }; } + +/** Expected starter mental-model ids after resolve (for setup ensure checks). */ +export function expectedStarterMentalModelIds(args: { + target: BankTemplateTarget; + agentUse: AgentUseProfile; + projectId?: string; + bankMissionSettings?: BankMissionSettings; +}): string[] { + const template = getBuiltInBankTemplate(defaultTemplateIdFor(args.target, args.agentUse)); + if (!template) return []; + const manifest = resolveBankTemplateManifest(template, args.bankMissionSettings ?? {}, { + ...(args.projectId ? { projectId: args.projectId } : {}), + }); + return (manifest.mental_models ?? []) + .map((model) => model.id) + .filter((id): id is string => typeof id === "string" && id.length > 0); +} diff --git a/extensions/tui/guided-setup.ts b/extensions/tui/guided-setup.ts index 67722499..c1b30e0d 100644 --- a/extensions/tui/guided-setup.ts +++ b/extensions/tui/guided-setup.ts @@ -12,7 +12,8 @@ import type { ImportProgressEvent } from "../imports/import-sessions.js"; import type { MemoryProfile, ProjectConfigPatchInput } from "../config/config-writer.js"; import type { SetupProfileChoice } from "./setup-tui-types.js"; import type { AgentUseProfile, HindsightLikeClient, ResolvedConfig } from "../types.js"; -import { defaultTemplateIdFor } from "../banks/bank-templates.js"; +import { defaultTemplateIdFor, expectedStarterMentalModelIds } from "../banks/bank-templates.js"; +import { resolveProjectIdentity } from "../banks/banking.js"; import { renderBankTemplateApplyResult, renderBankTemplateMentalModelDetails, @@ -292,11 +293,16 @@ export interface SetupBankMentalModelProbe { /** False when getBankProfile reports not found (or no profile API). */ bankExists: boolean; modelNames: string[]; + /** Present mental-model ids from listMentalModels (preferred for ensure checks). */ + modelIds: string[]; + /** Expected starter ids for this setup target (resolved template + projectId). */ + expectedModelIds?: string[]; + /** Subset of expectedModelIds not present on the bank. */ + missingModelIds?: string[]; error?: string; } -/** Extract mental-model names from a listMentalModels response body. */ -export function extractMentalModelNames(response: unknown): string[] { +function mentalModelListRows(response: unknown): Array> { if (!response || typeof response !== "object") return []; const body = response as { items?: unknown; mental_models?: unknown }; const rows = Array.isArray(body.items) @@ -304,19 +310,35 @@ export function extractMentalModelNames(response: unknown): string[] { : Array.isArray(body.mental_models) ? body.mental_models : []; + return rows.filter((entry): entry is Record => + Boolean(entry && typeof entry === "object"), + ); +} + +/** Extract mental-model names from a listMentalModels response body. */ +export function extractMentalModelNames(response: unknown): string[] { const names: string[] = []; - for (const entry of rows) { - if (!entry || typeof entry !== "object") continue; - const row = entry as { name?: unknown; id?: unknown }; + for (const row of mentalModelListRows(response)) { if (typeof row.name === "string" && row.name.trim()) names.push(row.name.trim()); else if (typeof row.id === "string" && row.id.trim()) names.push(row.id.trim()); } return names; } +/** Extract mental-model ids from a listMentalModels response body. */ +export function extractMentalModelIds(response: unknown): string[] { + const ids: string[] = []; + for (const row of mentalModelListRows(response)) { + if (typeof row.id === "string" && row.id.trim()) ids.push(row.id.trim()); + } + return ids; +} + /** * Decide which setup targets should be offered starter mental models. - * Existing banks that already have models are not offered by default. + * When expectedModelIds is set, skip only if every expected id is already present + * (domain-tagged multi-project: other projects' models must not skip this project). + * Without expected ids, fall back to empty-catalog offer. * Probe failures are not auto-offered (hub remains the intentional path). */ export function selectMentalModelTargetsToOffer(probes: SetupBankMentalModelProbe[]): { @@ -332,7 +354,16 @@ export function selectMentalModelTargetsToOffer(probes: SetupBankMentalModelProb unknown.push(probe); continue; } - if (probe.bankExists && probe.modelNames.length > 0) { + const expected = probe.expectedModelIds ?? []; + if (expected.length > 0) { + const have = new Set(probe.modelIds); + const missing = expected.filter((id) => !have.has(id)); + const next = { ...probe, missingModelIds: missing }; + if (missing.length === 0) alreadyProvisioned.push(next); + else toOffer.push(next); + continue; + } + if (probe.bankExists && (probe.modelIds.length > 0 || probe.modelNames.length > 0)) { alreadyProvisioned.push(probe); continue; } @@ -354,6 +385,7 @@ export async function probeBankMentalModels(args: { bankId: "", bankExists: false, modelNames: [], + modelIds: [], error: "missing bank id", }; } @@ -365,13 +397,14 @@ export async function probeBankMentalModels(args: { bankExists = true; } catch (error) { if (isNotFoundError(error)) { - return { target: args.target, bankId, bankExists: false, modelNames: [] }; + return { target: args.target, bankId, bankExists: false, modelNames: [], modelIds: [] }; } return { target: args.target, bankId, bankExists: false, modelNames: [], + modelIds: [], error: error instanceof Error ? error.message : String(error), }; } @@ -386,6 +419,7 @@ export async function probeBankMentalModels(args: { bankId, bankExists, modelNames: [], + modelIds: [], ...(bankExists ? { error: "listMentalModels unavailable" } : {}), }; } @@ -397,27 +431,45 @@ export async function probeBankMentalModels(args: { bankId, bankExists, modelNames: extractMentalModelNames(response), + modelIds: extractMentalModelIds(response), }; } catch (error) { if (isNotFoundError(error)) { - return { target: args.target, bankId, bankExists: false, modelNames: [] }; + return { target: args.target, bankId, bankExists: false, modelNames: [], modelIds: [] }; } return { target: args.target, bankId, bankExists, modelNames: [], + modelIds: [], error: error instanceof Error ? error.message : String(error), }; } } function formatExistingMentalModelsSummary(probe: SetupBankMentalModelProbe): string { + const expected = probe.expectedModelIds?.length ?? 0; + if (expected > 0) { + return `${probe.target} bank ${probe.bankId}: all ${expected} starter mental model(s) already present. Skipping starter provision; use hub (t) to re-apply intentionally.`; + } const preview = probe.modelNames.slice(0, 6).join(", "); const more = probe.modelNames.length > 6 ? ` (+${probe.modelNames.length - 6} more)` : ""; return `${probe.target} bank ${probe.bankId}: ${probe.modelNames.length} mental model(s) already present (${preview}${more}). Skipping starter provision; use hub (t) to re-apply intentionally.`; } +function formatOfferTargetSummary(probe: SetupBankMentalModelProbe): string { + const expected = probe.expectedModelIds?.length ?? 0; + const missing = probe.missingModelIds?.length ?? 0; + if (expected > 0) { + const present = expected - missing; + return `${probe.target}=${probe.bankId} (${present}/${expected} starters present; missing ${missing})`; + } + return probe.bankExists + ? `${probe.target}=${probe.bankId} (exists, empty catalog)` + : `${probe.target}=${probe.bankId} (new or missing)`; +} + async function maybeOfferMentalModelsForSetup(args: { ctx: ExtensionCommandContext; operations: MemoryOperations; @@ -426,6 +478,8 @@ async function maybeOfferMentalModelsForSetup(args: { setupProfile: SetupProfileChoice; projectBankId?: string; globalBankId?: string; + cwd?: string; + config?: ResolvedConfig; }): Promise { const targets: Array<{ target: "project" | "user"; bankId: string }> = []; if (profileUsesProject(args.setupProfile) && args.projectBankId?.trim()) { @@ -436,15 +490,31 @@ async function maybeOfferMentalModelsForSetup(args: { } if (targets.length === 0) return; + const cwd = args.cwd ?? process.cwd(); + const projectId = args.config ? resolveProjectIdentity(cwd, args.config).projectId : undefined; + const probes: SetupBankMentalModelProbe[] = []; for (const entry of targets) { - probes.push( - await probeBankMentalModels({ - client: args.client, - target: entry.target, - bankId: entry.bankId, - }), - ); + const probe = await probeBankMentalModels({ + client: args.client, + target: entry.target, + bankId: entry.bankId, + }); + const expectedModelIds = expectedStarterMentalModelIds({ + target: entry.target, + agentUse: args.agentUse, + ...(entry.target === "project" && projectId ? { projectId } : {}), + ...(args.config + ? { + bankMissionSettings: + entry.target === "user" ? args.config.banks.user : args.config.banks.project, + } + : {}), + }); + probes.push({ + ...probe, + ...(expectedModelIds.length > 0 ? { expectedModelIds } : {}), + }); } const { toOffer, alreadyProvisioned, unknown } = selectMentalModelTargetsToOffer(probes); @@ -461,16 +531,10 @@ async function maybeOfferMentalModelsForSetup(args: { if (toOffer.length === 0) return; - const bankSummary = toOffer - .map((probe) => - probe.bankExists - ? `${probe.target}=${probe.bankId} (exists, empty catalog)` - : `${probe.target}=${probe.bankId} (new or missing)`, - ) - .join("; "); + const bankSummary = toOffer.map(formatOfferTargetSummary).join("; "); const proceed = await args.ctx.ui.confirm( "Provision starter mental models?", - `Agent use: ${args.agentUse}. Targets: ${bankSummary}. Dry-run preview first; nothing is written without confirmation. Mental models become part of automatic context when present.`, + `Agent use: ${args.agentUse}. Targets: ${bankSummary}. Ensures bank-global + this project's starters when missing. Dry-run preview first; nothing is written without confirmation. Mental models become part of automatic context when present.`, ); if (!proceed) return; @@ -742,6 +806,8 @@ export async function runGuidedSetup(args: { client, agentUse, setupProfile, + cwd: args.cwd, + config, ...(projectBankId !== undefined ? { projectBankId } : {}), ...(globalBankId !== undefined ? { globalBankId } : {}), }); diff --git a/tests/bank-templates.test.ts b/tests/bank-templates.test.ts index 97b94b83..38f67494 100644 --- a/tests/bank-templates.test.ts +++ b/tests/bank-templates.test.ts @@ -5,6 +5,7 @@ import { } from "../extensions/banks/bank-operations.js"; import { BUILT_IN_BANK_TEMPLATES, + expectedStarterMentalModelIds, getBuiltInBankTemplate, listBuiltInBankTemplates, resolveBankTemplateManifest, @@ -63,10 +64,10 @@ describe("bank templates", () => { ); }); - it("does not set an auto-refresh trigger on bundled mental models", () => { + it("sets refresh_after_consolidation on bundled mental models", () => { for (const template of BUILT_IN_BANK_TEMPLATES) { for (const model of template.manifest.mental_models ?? []) { - expect(model.trigger).toBeUndefined(); + expect(model.trigger?.refresh_after_consolidation).toBe(true); } } }); @@ -106,6 +107,46 @@ describe("bank templates", () => { expect(manifest.mental_models).toBe(template.manifest.mental_models); }); + it("stamps project models and merges bank-global starters for coding project resolve", () => { + const template = getBuiltInBankTemplate("pi-coding-project")!; + const manifest = resolveBankTemplateManifest( + template, + {}, + { + projectId: "github-com-luxus-pi-hindsight", + }, + ); + const ids = manifest.mental_models?.map((model) => model.id) ?? []; + expect(ids).toContain("coding-assistant-operating-preferences"); + expect(ids).toContain("project-architecture-and-seams--github-com-luxus-pi-hindsight"); + expect(ids).toContain("project-conventions--github-com-luxus-pi-hindsight"); + expect(ids).toContain("project-decisions--github-com-luxus-pi-hindsight"); + const global = manifest.mental_models?.find( + (model) => model.id === "coding-assistant-operating-preferences", + ); + expect(global?.tags).toEqual(["source:pi"]); + const project = manifest.mental_models?.find( + (model) => model.id === "project-decisions--github-com-luxus-pi-hindsight", + ); + expect(project?.tags).toEqual( + expect.arrayContaining(["source:pi", "project:github-com-luxus-pi-hindsight"]), + ); + }); + + it("lists expected starter ids for setup ensure checks", () => { + const ids = expectedStarterMentalModelIds({ + target: "project", + agentUse: "coding", + projectId: "github-com-luxus-pi-hindsight", + }); + expect(ids).toEqual([ + "coding-assistant-operating-preferences", + "project-architecture-and-seams--github-com-luxus-pi-hindsight", + "project-conventions--github-com-luxus-pi-hindsight", + "project-decisions--github-com-luxus-pi-hindsight", + ]); + }); + it("lists all built-in templates", () => { expect(listBuiltInBankTemplates()).toHaveLength(BUILT_IN_BANK_TEMPLATES.length); }); diff --git a/tests/guided-setup.test.ts b/tests/guided-setup.test.ts index 3171688c..7aaa18c0 100644 --- a/tests/guided-setup.test.ts +++ b/tests/guided-setup.test.ts @@ -2,6 +2,8 @@ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it, vi } from "vitest"; +import { resolveProjectIdentity } from "../extensions/banks/banking.js"; +import { expectedStarterMentalModelIds } from "../extensions/banks/bank-templates.js"; import { DEFAULT_CONFIG } from "../extensions/config/config-defaults.js"; import { buildGuidedSetupGlobalPatch, @@ -341,30 +343,47 @@ describe("guided setup", () => { expect(extractMentalModelNames(null)).toEqual([]); }); - it("skips starter mental-model offer when banks already have models", () => { + it("skips starter offer only when expected starter ids are all present", () => { + const expected = [ + "coding-assistant-operating-preferences", + "project-architecture-and-seams--proj-b", + ]; const decision = selectMentalModelTargetsToOffer([ { target: "project", - bankId: "pi-coding", + bankId: "coding", bankExists: true, - modelNames: ["Architecture decisions"], + modelNames: ["Other project architecture"], + modelIds: ["project-architecture-and-seams--proj-a"], + expectedModelIds: expected, + }, + { + target: "project", + bankId: "coding-complete", + bankExists: true, + modelNames: ["Global", "Arch"], + modelIds: expected, + expectedModelIds: expected, }, { target: "user", bankId: "life", bankExists: false, modelNames: [], + modelIds: [], }, { target: "user", bankId: "broken", bankExists: true, modelNames: [], + modelIds: [], error: "timeout", }, ]); - expect(decision.toOffer.map((probe) => probe.bankId)).toEqual(["life"]); - expect(decision.alreadyProvisioned.map((probe) => probe.bankId)).toEqual(["pi-coding"]); + expect(decision.toOffer.map((probe) => probe.bankId)).toEqual(["coding", "life"]); + expect(decision.toOffer[0]?.missingModelIds).toEqual(expected); + expect(decision.alreadyProvisioned.map((probe) => probe.bankId)).toEqual(["coding-complete"]); expect(decision.unknown.map((probe) => probe.bankId)).toEqual(["broken"]); }); @@ -391,6 +410,7 @@ describe("guided setup", () => { bankId: "pi-coding", bankExists: true, modelNames: ["Project architecture"], + modelIds: ["mm1"], }); await expect( probeBankMentalModels({ client, target: "project", bankId: "new-bank" }), @@ -399,21 +419,23 @@ describe("guided setup", () => { bankId: "new-bank", bankExists: false, modelNames: [], + modelIds: [], }); expect(listMentalModels).toHaveBeenCalledTimes(1); }); - it("does not prompt for starter mental models when the bank already has them", async () => { + it("still offers starters when bank has other projects' models but not this project's", async () => { const cwd = mkdtempSync(join(tmpdir(), "pi-hindsight-guided-existing-mm-")); const notify = vi.fn(); const confirm = vi .fn() - // Write config yes; import no. No mental-model confirm should appear. + // Write config yes; decline MM provision; import no. .mockResolvedValueOnce(true) + .mockResolvedValueOnce(false) .mockResolvedValueOnce(false); const applyBankTemplate = vi.fn(); const listMentalModels = vi.fn(async () => ({ - items: [{ id: "mm1", name: "Architecture decisions" }], + items: [{ id: "project-architecture-and-seams--other-project", name: "Other arch" }], })); const getBankProfile = vi.fn(async () => ({ id: "project-bank" })); const ctx = { @@ -450,19 +472,68 @@ describe("guided setup", () => { expect(completed).toBe(true); expect(getBankProfile).toHaveBeenCalledWith("project-bank"); expect(listMentalModels).toHaveBeenCalledWith("project-bank"); + expect(confirm.mock.calls[0]?.[0]).toBe("Write Pi Hindsight config?"); + expect(confirm.mock.calls[1]?.[0]).toBe("Provision starter mental models?"); + expect(confirm.mock.calls[1]?.[1]).toEqual(expect.stringContaining("starters present")); + expect(applyBankTemplate).not.toHaveBeenCalled(); + }); + + it("does not prompt for starter mental models when expected starters already exist", async () => { + const cwd = mkdtempSync(join(tmpdir(), "pi-hindsight-guided-complete-mm-")); + const notify = vi.fn(); + const confirm = vi + .fn() + // Write config yes; import no. No mental-model confirm should appear. + .mockResolvedValueOnce(true) + .mockResolvedValueOnce(false); + const applyBankTemplate = vi.fn(); + const projectId = resolveProjectIdentity(cwd, DEFAULT_CONFIG).projectId; + const expectedIds = expectedStarterMentalModelIds({ + target: "project", + agentUse: "coding", + projectId, + }); + const listMentalModels = vi.fn(async () => ({ + items: expectedIds.map((id) => ({ id, name: id })), + })); + const getBankProfile = vi.fn(async () => ({ id: "project-bank" })); + const ctx = { + cwd, + sessionManager: { getSessionFile: () => undefined }, + ui: { + notify, + input: vi.fn().mockResolvedValueOnce("project-bank"), + select: vi + .fn() + .mockResolvedValueOnce("Coding (shared coding bank + project tags)") + .mockResolvedValueOnce("Coding (architecture, conventions, decisions)"), + confirm, + }, + } as never; + + const completed = await runGuidedSetup({ + ctx, + cwd, + deps: { + getClient: () => ({ + retain: vi.fn(), + recall: vi.fn(), + reflect: vi.fn(), + getBankProfile, + listMentalModels, + importBankTemplate: applyBankTemplate, + }), + getConfig: () => DEFAULT_CONFIG, + getProjectBankId: () => "project-bank", + } as never, + }); + + expect(completed).toBe(true); expect(confirm).toHaveBeenCalledTimes(2); expect(confirm.mock.calls[0]?.[0]).toBe("Write Pi Hindsight config?"); expect(confirm.mock.calls[1]?.[0]).toBe("Preview historical import now?"); expect(notify).toHaveBeenCalledWith( - expect.stringContaining("Using existing project bank project-bank"), - "info", - ); - expect(notify).toHaveBeenCalledWith( - expect.stringContaining("Server: reachable · project bank project-bank: existing"), - "info", - ); - expect(notify).toHaveBeenCalledWith( - expect.stringContaining("1 mental model(s) already present"), + expect.stringContaining("all 4 starter mental model(s) already present"), "info", ); expect(applyBankTemplate).not.toHaveBeenCalled(); diff --git a/tests/mental-models.test.ts b/tests/mental-models.test.ts index 02a8733b..c6011150 100644 --- a/tests/mental-models.test.ts +++ b/tests/mental-models.test.ts @@ -62,10 +62,16 @@ describe("use-profile mental model sets", () => { const manifest = resolveBankTemplateManifest(template!, {}, { projectId: "finalform" }); const models = manifest.mental_models ?? []; expect(models.length).toBeGreaterThan(0); - for (const model of models) { - expect(model.id).toContain("--finalform"); + const projectModels = models.filter((model) => model.id.includes("--finalform")); + const bankGlobal = models.filter((model) => !model.id.includes("--")); + expect(projectModels.length).toBeGreaterThan(0); + expect(bankGlobal.length).toBeGreaterThan(0); + for (const model of projectModels) { expect(model.tags).toEqual(expect.arrayContaining(["source:pi", "project:finalform"])); } + for (const model of bankGlobal) { + expect(model.tags).toEqual(["source:pi"]); + } }); }); From 4193fb58323cf34fade1e4728242e8d2d88974ca Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:08:46 +0200 Subject: [PATCH 2/9] fix(templates): sharpen bank-global MM query for clarification style Exclude probe bait "no questions" rules and capture durable up-front / high-signal clarification preferences. --- extensions/banks/bank-templates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/banks/bank-templates.ts b/extensions/banks/bank-templates.ts index 9479805a..21d62ccc 100644 --- a/extensions/banks/bank-templates.ts +++ b/extensions/banks/bank-templates.ts @@ -55,7 +55,7 @@ const CODING_BANK_GLOBAL_MENTAL_MODELS: BankTemplateMentalModel[] = [ id: "coding-assistant-operating-preferences", name: "Coding assistant operating preferences", source_query: - "What durable preferences has the user shown for how coding assistants should plan, verify, commit, and use tools across repositories? Capture only stable cross-project habits, not one-off task instructions.", + "What durable preferences has the user shown for how coding assistants should plan, verify, commit, use tools, and communicate across repositories? Especially capture clarification style: whether questions are welcome, when to ask (up front vs mid-task), and that questions should be high-signal (not answerable by the agent alone). Exclude one-off probe/bait/test session constraints such as temporary 'do not ask questions; just execute' rules. Capture only stable cross-project habits.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: 600, trigger: DEFAULT_MM_TRIGGER, From be51e4e41c5bdf7a55c832f73521230d8aa0bb97 Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:40:40 +0200 Subject: [PATCH 3/9] feat(memory): delta MM triggers, lean budgets, mission + eval docs - Bundled mental models: mode delta, refresh_after_consolidation, observation-first, exclude_mental_models; max_tokens 600/800 - Sharpen default coding/user bank missions (ignore probe noise) - Docs: coding-memory-evaluation (AMB/sde-bench/perf-monitor); update starter/core-function notes and docs-site Closes #528, #529, #530, #531, #532 --- astro.config.mjs | 4 ++ .../docs/concepts/coding-memory-evaluation.md | 40 ++++++++++++++++++ .../docs/concepts/hindsight-core-functions.md | 4 +- docs-site/src/content/docs/concepts/index.mdx | 1 + .../starter-mental-model-suggestions.md | 17 +++++++- docs/coding-memory-evaluation.md | 38 +++++++++++++++++ docs/hindsight-core-functions.md | 4 +- docs/starter-mental-model-suggestions.md | 8 ++++ extensions/banks/bank-operations.ts | 12 +++--- extensions/banks/bank-templates.ts | 42 ++++++++++++------- .../operations/memory-control-operations.ts | 1 + package.json | 3 +- tests/bank-operations.test.ts | 14 +++---- tests/bank-templates.test.ts | 10 ++++- 14 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 docs-site/src/content/docs/concepts/coding-memory-evaluation.md create mode 100644 docs/coding-memory-evaluation.md diff --git a/astro.config.mjs b/astro.config.mjs index f8b09b68..df533766 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -63,6 +63,10 @@ export default defineConfig({ label: "Starter mental model suggestions", slug: "concepts/starter-mental-model-suggestions", }, + { + label: "Coding memory evaluation", + slug: "concepts/coding-memory-evaluation", + }, ], }, { diff --git a/docs-site/src/content/docs/concepts/coding-memory-evaluation.md b/docs-site/src/content/docs/concepts/coding-memory-evaluation.md new file mode 100644 index 00000000..3617dbd5 --- /dev/null +++ b/docs-site/src/content/docs/concepts/coding-memory-evaluation.md @@ -0,0 +1,40 @@ +--- +title: "Coding memory evaluation" +--- + +What Vectorize’s own evaluation surface implies for **pi-hindsight** defaults. Not a harness in this repo—alignment notes for agents and maintainers. + +## Sources + +| Source | What it measures | Link | +| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Hindsight continuous perf monitor | Retain throughput, recall p95, recall+obs, temporal recall, consolidation throughput, graph maintenance | [dashboard](https://vectorize-io.github.io/hindsight-continuous-performance-monitor) | +| AMB (Agent Memory Benchmark) | Accuracy **and** latency/token cost; modes `rag` / `agentic-rag` / `agent` (native reflect); datasets include PersonaMem (prefs) | [repo](https://github.com/vectorize-io/agent-memory-benchmark), [leaderboard](https://agentmemorybenchmark.ai) | +| sde-bench | Does a **coding agent** benefit from memory on non-guessable project decisions? `conversation` vs `history` source | [repo](https://github.com/vectorize-io/sde-bench) | +| Best practices / oh-my-pi / MM deep dive | Retain/recall/reflect hygiene; MM seeds; delta refresh | [best practices](https://hindsight.vectorize.io/best-practices), [oh-my-pi post](https://hindsight.vectorize.io/blog/2026/06/08/oh-my-pi-hindsight-memory), [MM deep dive](https://hindsight.vectorize.io/blog/2026/06/05/mental-models-deep-dive) | + +## Implications → pi-hindsight defaults + +| Learning | Extension implication | +| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| Conversation-only decisions are the hard discriminator for strong models (sde-bench) | Auto-**retain** of real Pi sessions is the product; do not rely on git alone | +| Ranking amid noise is the problem | Prefer observations + **narrow** mental models over dumping raw facts every turn | +| Cost is co-equal with accuracy (AMB) | Cap MM `max_tokens` (600–800 seeds); keep recall budgets mid/low unless deep context is required | +| Consolidation is a server hot path (perf monitor) | Client should not re-synthesize every turn: inject cached MMs; use **delta** + `refresh_after_consolidation` | +| Prefs applied to multi-step work (PersonaMem / AMB) | Bank-global prefs MM + explicit retain of durable prefs (not probe harness noise) | +| Retain then same-turn recall is an anti-pattern | Retain at `agent_end`; recall at `context` (already) | +| One MM for everything is an anti-pattern | One model per knowledge dimension (architecture / conventions / decisions / prefs) | + +## What we intentionally do **not** do + +- Auto-`reflect` every turn (expensive; tools already expose reflect). +- Mega bank-global “everything about the user” models. +- Pre-summarizing before retain. +- Silent background MM create on every boot (setup / hub `t` is explicit). + +## Related issues + +- Setup ensure project + bank-global starters: #528 +- Delta MM triggers on templates: #529 +- MM size discipline: #530 +- Mission wording vs best practices: #531 diff --git a/docs-site/src/content/docs/concepts/hindsight-core-functions.md b/docs-site/src/content/docs/concepts/hindsight-core-functions.md index b9710ec5..a20966e0 100644 --- a/docs-site/src/content/docs/concepts/hindsight-core-functions.md +++ b/docs-site/src/content/docs/concepts/hindsight-core-functions.md @@ -112,9 +112,9 @@ Good mental model examples: - “What recurring workflow habits matter?” - “What does this project consider good design?” -Mental models are not raw recall. They are reusable synthesized answers. In Pi Hindsight they are explicit advanced resources. Pi's `/hindsight` TUI shows a read-only list/detail view and hands off create, edit, refresh, and delete to the Hindsight web interface. The extension must not auto-create mental models from routine sessions or refresh them in the background unless a future issue designs that policy. Starter suggestions are documented in [Starter mental model suggestions](/pi-hindsight/concepts/starter-mental-model-suggestions/). +Mental models are not raw recall. They are reusable synthesized answers. In Pi Hindsight they are explicit advanced resources (setup / hub `t` / agent control plane). The extension must not auto-create mental models from routine sessions. Starter suggestions are documented in [Starter mental model suggestions](/pi-hindsight/concepts/starter-mental-model-suggestions/). Evaluation alignment notes: [Coding memory evaluation](/pi-hindsight/concepts/coding-memory-evaluation/). -Creating a mental model preserves a source query. Hindsight runs that query through reflect and stores the generated content. Refresh reruns the stored source query against newer memory. +Creating a mental model preserves a source query. Hindsight runs that query through reflect and stores the generated content. Bundled templates set `trigger.mode: delta` and `refresh_after_consolidation: true` so consolidation folds new observations without full rewrites every time (server-side; not a Pi background job). ```text Observations = facts learned from repetition diff --git a/docs-site/src/content/docs/concepts/index.mdx b/docs-site/src/content/docs/concepts/index.mdx index 78d2e00a..3dbc3079 100644 --- a/docs-site/src/content/docs/concepts/index.mdx +++ b/docs-site/src/content/docs/concepts/index.mdx @@ -42,3 +42,4 @@ Concept pages explain how Pi Hindsight uses Hindsight. They should describe stab - [Memory behavior](./memory-behavior/) explains the automatic one-turn lifecycle. - [Hindsight core functions](./hindsight-core-functions/) links Pi behavior back to Hindsight primitives. - [Starter mental model suggestions](./starter-mental-model-suggestions/) explains seed material for mental models managed in the Hindsight web UI. +- [Coding memory evaluation](./coding-memory-evaluation/) ties defaults to AMB, sde-bench, and Hindsight perf-monitor learnings. diff --git a/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md b/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md index 532f20fb..4d146e58 100644 --- a/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md +++ b/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md @@ -1,5 +1,5 @@ --- -title: Starter mental model suggestions +title: "Starter mental model suggestions" --- Mental models are a **core** Hindsight feature. Pi Hindsight provisions them through use-profile-aware bank templates (guided setup or `/hindsight` → `t`), then injects non-empty model content into automatic context. @@ -15,6 +15,14 @@ Starter suggestions live here and in `extensions/banks/bank-templates.ts` (keep - **Agent use** selects which seed set applies: `coding` vs `conversation` (real-life / chat agents). - Tags on seeds must be a subset of retain tags (`source:pi`) so refresh is not empty. - When models have content, automatic context injects them ephemerally under ``; retain strips that injection like recall blocks. +- Seed `max_tokens` stays lean (600 prefs / 800 project) so inject does not dominate every turn (see [coding-memory-evaluation.md](coding-memory-evaluation.md)). +- Template triggers use `mode: delta`, `refresh_after_consolidation: true`, observation-first refresh, and `exclude_mental_models` (oh-my-pi / Claude Code pattern). + +## Coding bank-global (shared coding bank) + +| Name | Source query (summary) | Why | +| -------------------------------------- | ---------------------------------------------------------------------------------- | ---------------- | +| Coding assistant operating preferences | Durable plan/verify/commit/tool + clarification style; exclude probe harness rules | Cross-repo prefs | ## Coding project bank @@ -48,6 +56,13 @@ Starter suggestions live here and in `extensions/banks/bank-templates.ts` (keep | Life and task workflow habits | Real-life planning and task habits | LifeOS patterns | | Priority and scheduling preferences | How the user prioritizes and schedules | Trade-offs | +## What not to seed + +- Secrets, credentials, private URLs +- One-off session status +- Speculative personality profiles +- Project facts in the user bank (and vice versa for coding vs conversation mismatch) + ## Template ids | Id | Target | Agent use | diff --git a/docs/coding-memory-evaluation.md b/docs/coding-memory-evaluation.md new file mode 100644 index 00000000..acde1aa8 --- /dev/null +++ b/docs/coding-memory-evaluation.md @@ -0,0 +1,38 @@ +# Coding memory evaluation notes + +What Vectorize’s own evaluation surface implies for **pi-hindsight** defaults. Not a harness in this repo—alignment notes for agents and maintainers. + +## Sources + +| Source | What it measures | Link | +| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Hindsight continuous perf monitor | Retain throughput, recall p95, recall+obs, temporal recall, consolidation throughput, graph maintenance | [dashboard](https://vectorize-io.github.io/hindsight-continuous-performance-monitor) | +| AMB (Agent Memory Benchmark) | Accuracy **and** latency/token cost; modes `rag` / `agentic-rag` / `agent` (native reflect); datasets include PersonaMem (prefs) | [repo](https://github.com/vectorize-io/agent-memory-benchmark), [leaderboard](https://agentmemorybenchmark.ai) | +| sde-bench | Does a **coding agent** benefit from memory on non-guessable project decisions? `conversation` vs `history` source | [repo](https://github.com/vectorize-io/sde-bench) | +| Best practices / oh-my-pi / MM deep dive | Retain/recall/reflect hygiene; MM seeds; delta refresh | [best practices](https://hindsight.vectorize.io/best-practices), [oh-my-pi post](https://hindsight.vectorize.io/blog/2026/06/08/oh-my-pi-hindsight-memory), [MM deep dive](https://hindsight.vectorize.io/blog/2026/06/05/mental-models-deep-dive) | + +## Implications → pi-hindsight defaults + +| Learning | Extension implication | +| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| Conversation-only decisions are the hard discriminator for strong models (sde-bench) | Auto-**retain** of real Pi sessions is the product; do not rely on git alone | +| Ranking amid noise is the problem | Prefer observations + **narrow** mental models over dumping raw facts every turn | +| Cost is co-equal with accuracy (AMB) | Cap MM `max_tokens` (600–800 seeds); keep recall budgets mid/low unless deep context is required | +| Consolidation is a server hot path (perf monitor) | Client should not re-synthesize every turn: inject cached MMs; use **delta** + `refresh_after_consolidation` | +| Prefs applied to multi-step work (PersonaMem / AMB) | Bank-global prefs MM + explicit retain of durable prefs (not probe harness noise) | +| Retain then same-turn recall is an anti-pattern | Retain at `agent_end`; recall at `context` (already) | +| One MM for everything is an anti-pattern | One model per knowledge dimension (architecture / conventions / decisions / prefs) | + +## What we intentionally do **not** do + +- Auto-`reflect` every turn (expensive; tools already expose reflect). +- Mega bank-global “everything about the user” models. +- Pre-summarizing before retain. +- Silent background MM create on every boot (setup / hub `t` is explicit). + +## Related issues + +- Setup ensure project + bank-global starters: #528 +- Delta MM triggers on templates: #529 +- MM size discipline: #530 +- Mission wording vs best practices: #531 diff --git a/docs/hindsight-core-functions.md b/docs/hindsight-core-functions.md index 55553275..de997fdf 100644 --- a/docs/hindsight-core-functions.md +++ b/docs/hindsight-core-functions.md @@ -110,9 +110,9 @@ Good mental model examples: - “What recurring workflow habits matter?” - “What does this project consider good design?” -Mental models are not raw recall. They are reusable synthesized answers. In Pi Hindsight they are explicit advanced resources. Pi's `/hindsight` TUI shows a read-only list/detail view and hands off create, edit, refresh, and delete to the Hindsight web interface. The extension must not auto-create mental models from routine sessions or refresh them in the background unless a future issue designs that policy. Starter suggestions are documented in [`starter-mental-model-suggestions.md`](starter-mental-model-suggestions.md). +Mental models are not raw recall. They are reusable synthesized answers. In Pi Hindsight they are explicit advanced resources (setup / hub `t` / agent control plane). The extension must not auto-create mental models from routine sessions. Starter suggestions are documented in [`starter-mental-model-suggestions.md`](starter-mental-model-suggestions.md). Evaluation alignment notes: [`coding-memory-evaluation.md`](coding-memory-evaluation.md). -Creating a mental model preserves a source query. Hindsight runs that query through reflect and stores the generated content. Refresh reruns the stored source query against newer memory. +Creating a mental model preserves a source query. Hindsight runs that query through reflect and stores the generated content. Bundled templates set `trigger.mode: delta` and `refresh_after_consolidation: true` so consolidation folds new observations without full rewrites every time (server-side; not a Pi background job). ```text Observations = facts learned from repetition diff --git a/docs/starter-mental-model-suggestions.md b/docs/starter-mental-model-suggestions.md index d1f4e47a..3067402e 100644 --- a/docs/starter-mental-model-suggestions.md +++ b/docs/starter-mental-model-suggestions.md @@ -13,6 +13,14 @@ Starter suggestions live here and in `extensions/banks/bank-templates.ts` (keep - **Agent use** selects which seed set applies: `coding` vs `conversation` (real-life / chat agents). - Tags on seeds must be a subset of retain tags (`source:pi`) so refresh is not empty. - When models have content, automatic context injects them ephemerally under ``; retain strips that injection like recall blocks. +- Seed `max_tokens` stays lean (600 prefs / 800 project) so inject does not dominate every turn (see [coding-memory-evaluation.md](coding-memory-evaluation.md)). +- Template triggers use `mode: delta`, `refresh_after_consolidation: true`, observation-first refresh, and `exclude_mental_models` (oh-my-pi / Claude Code pattern). + +## Coding bank-global (shared coding bank) + +| Name | Source query (summary) | Why | +| -------------------------------------- | ---------------------------------------------------------------------------------- | ---------------- | +| Coding assistant operating preferences | Durable plan/verify/commit/tool + clarification style; exclude probe harness rules | Cross-repo prefs | ## Coding project bank diff --git a/extensions/banks/bank-operations.ts b/extensions/banks/bank-operations.ts index 776c1dc2..c9198f8b 100644 --- a/extensions/banks/bank-operations.ts +++ b/extensions/banks/bank-operations.ts @@ -1,22 +1,22 @@ import type { BankMissionSettings, HindsightLikeClient } from "../types.js"; const DEFAULT_PROJECT_REFLECT_MISSION = - "Help a Pi coding agent recall project-specific architecture, engineering decisions, conventions, tasks, bugs, fixes, constraints, and continuity."; + "You are a senior developer helping a Pi coding agent. Prefer past technical decisions, architecture trade-offs, conventions, and constraints. Be direct and opinionated when memory supports it; do not invent facts not grounded in memory."; const DEFAULT_PROJECT_RETAIN_MISSION = - "Extract durable project memory from raw Pi coding sessions: architecture decisions, constraints, bugs, fixes, TODOs, repo conventions, and project-local user preferences. Ignore transient chatter, secrets, and resurfaced recalled memories unless they add a new correction or decision."; + "Always extract technical decisions, API/architecture trade-offs, blockers, error resolutions, repo conventions, and durable project-local preferences. Ignore greetings, small talk, scheduling logistics, secrets, probe/bait harness instructions (e.g. temporary 'do not ask questions' test rules), and resurfaced recalled memory unless it adds a new correction or decision."; const DEFAULT_PROJECT_OBSERVATIONS_MISSION = - "Identify durable project patterns, recurring constraints, architectural preferences, and contradictions across Pi coding sessions. Focus on stable repo-relevant knowledge, not transient task state."; + "Identify evolving durable project patterns, recurring constraints, architectural preferences, and contradictions with prior knowledge. Focus on stable repo-relevant knowledge — not transient task state, one-off plans, or test-harness noise."; const DEFAULT_GLOBAL_REFLECT_MISSION = - "Help a Pi coding agent recall durable cross-project user preferences, recurring workflows, coding habits, and stable assistant behavior guidance."; + "You are a coding assistant with durable cross-project context. Personalize using stable user preferences, workflows, and communication style. Be direct; prefer high-signal clarifications over speculation."; const DEFAULT_GLOBAL_RETAIN_MISSION = - "Extract durable cross-project memory from raw Pi sessions: user preferences, recurring workflows, coding habits, and stable assistant behavior. Do not retain repo-specific code facts, file paths, project-local bugs, or transcript dumps unless they generalize across projects."; + "Extract durable cross-project memory: user preferences (including clarification/communication style), recurring workflows, coding habits, and stable assistant behavior. Ignore greetings, secrets, probe/bait session rules, repo-specific code facts, file paths, and project-local bugs unless they generalize across projects."; const DEFAULT_GLOBAL_OBSERVATIONS_MISSION = - "Identify durable cross-project user preferences, recurring workflows, coding habits, and stable assistant behavior patterns. Ignore repo-specific implementation details unless they generalize across projects."; + "Identify durable cross-project preferences, recurring workflows, coding habits, and stable assistant behavior patterns. Highlight contradictions with prior knowledge. Ignore repo-specific implementation details and one-off probe constraints unless they generalize."; export interface BankMissionDefaults { reflectMission: string; diff --git a/extensions/banks/bank-templates.ts b/extensions/banks/bank-templates.ts index 21d62ccc..e86c6adf 100644 --- a/extensions/banks/bank-templates.ts +++ b/extensions/banks/bank-templates.ts @@ -44,11 +44,23 @@ function bankConfigFromMissions(missions: BankMissionDefaults): BankTemplateConf // Project-tier models get project: stamped at apply time (resolveBankTemplateManifest). const RETAIN_COMPAT_TAGS = ["source:pi"] as const; -/** Default after observations consolidation; matches agent createMentalModel path. */ -const DEFAULT_MM_TRIGGER: NonNullable = { +/** + * Coding-agent MM refresh defaults (oh-my-pi / Claude Code / Hindsight deep dive): + * delta folds new evidence; refresh after consolidation; observations-first; no MM→MM feedback. + * Template import sends the full trigger object; agent create is limited by the TS client mapping + * (refreshAfterConsolidation only) until the client maps mode/fact_types. + */ +export const DEFAULT_MM_TRIGGER: NonNullable = { + mode: "delta", refresh_after_consolidation: true, + fact_types: ["observation"], + exclude_mental_models: true, }; +/** Seed max_tokens: keep inject lean (AMB cost; inject shares mentalModels.maxChars). */ +export const DEFAULT_MM_MAX_TOKENS_PREFS = 600; +export const DEFAULT_MM_MAX_TOKENS_PROJECT = 800; + // Bank-global on shared coding bank (no project:* tag) — injects for every project. const CODING_BANK_GLOBAL_MENTAL_MODELS: BankTemplateMentalModel[] = [ { @@ -57,7 +69,7 @@ const CODING_BANK_GLOBAL_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What durable preferences has the user shown for how coding assistants should plan, verify, commit, use tools, and communicate across repositories? Especially capture clarification style: whether questions are welcome, when to ask (up front vs mid-task), and that questions should be high-signal (not answerable by the agent alone). Exclude one-off probe/bait/test session constraints such as temporary 'do not ask questions; just execute' rules. Capture only stable cross-project habits.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -70,7 +82,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What are the stable architecture boundaries, modules, and seams in this project?", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 800, + max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, trigger: DEFAULT_MM_TRIGGER, }, { @@ -79,7 +91,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What are this project's conventions for code style, build, testing, release, and review? Only include conventions explicit in the project or repeatedly enforced.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 800, + max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, trigger: DEFAULT_MM_TRIGGER, }, { @@ -88,7 +100,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What durable architectural or product decisions have been made for this project, and what rationale or trade-offs were recorded? Exclude transient plans and active task state.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 800, + max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -101,7 +113,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What goals, commitments, deadlines, and open loops is the user tracking in this context? Prefer durable ongoing items over one-off chat filler.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 800, + max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, trigger: DEFAULT_MM_TRIGGER, }, { @@ -110,7 +122,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "Which people, roles, relationships, and recurring situations matter here? Capture only durable context the assistant needs, not sensitive private details.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, { @@ -119,7 +131,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What durable decisions, preferences, and constraints has the user stated for this conversation or life domain? Exclude one-off requests.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 800, + max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -131,7 +143,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What durable preferences has the user shown for collaboration, review, autonomy, and communication?", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, { @@ -140,7 +152,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What durable preferences has the user shown for how coding assistants should plan, verify, commit, and use tools?", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, { @@ -149,7 +161,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What workflow habits recur across the user's repositories, issue tracking, PR review, and release process?", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, ]; @@ -161,7 +173,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What durable preferences has the user shown for tone, length, language, and how the assistant should respond?", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, { @@ -170,7 +182,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "What recurring real-life workflows, planning habits, and task-management preferences does the user express across conversations?", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, { @@ -179,7 +191,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ source_query: "How does the user prefer to prioritize, schedule, and trade off time across personal and work tasks? Capture durable patterns only.", tags: [...RETAIN_COMPAT_TAGS], - max_tokens: 600, + max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, trigger: DEFAULT_MM_TRIGGER, }, ]; diff --git a/extensions/operations/memory-control-operations.ts b/extensions/operations/memory-control-operations.ts index c41cf047..b15864b5 100644 --- a/extensions/operations/memory-control-operations.ts +++ b/extensions/operations/memory-control-operations.ts @@ -300,6 +300,7 @@ export function createControlOperations(deps: MemoryOperationsDeps) { ...(args.id ? { id: args.id } : {}), tags, ...(args.maxTokens !== undefined ? { maxTokens: args.maxTokens } : {}), + // Client maps only refreshAfterConsolidation; template import carries full delta trigger. trigger: { refreshAfterConsolidation: true }, }); return { bankId, result }; diff --git a/package.json b/package.json index 842d7ebc..5213c273 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "docs/risky-memory-modes.md", "docs/next-opt-out-design.md", "docs/surface-reference.md", - "docs/starter-mental-model-suggestions.md" + "docs/starter-mental-model-suggestions.md", + "docs/coding-memory-evaluation.md" ], "type": "module", "publishConfig": { diff --git a/tests/bank-operations.test.ts b/tests/bank-operations.test.ts index 808c1e4f..3b8b5f8d 100644 --- a/tests/bank-operations.test.ts +++ b/tests/bank-operations.test.ts @@ -53,8 +53,8 @@ describe("bank operations", () => { expect(createBank).toHaveBeenCalledWith( "project-bank", expect.objectContaining({ - reflectMission: expect.stringContaining("project-specific architecture"), - retainMission: expect.stringContaining("durable project memory"), + reflectMission: expect.stringContaining("senior developer"), + retainMission: expect.stringContaining("technical decisions"), observationsMission: expect.stringContaining("durable project patterns"), }), ); @@ -126,8 +126,8 @@ describe("bank operations", () => { expect(createBank).toHaveBeenCalledWith( "project-bank", expect.objectContaining({ - reflectMission: expect.stringContaining("project-specific architecture"), - retainMission: expect.stringContaining("durable project memory"), + reflectMission: expect.stringContaining("senior developer"), + retainMission: expect.stringContaining("technical decisions"), observationsMission: expect.stringContaining("durable project patterns"), }), ); @@ -140,9 +140,9 @@ describe("bank operations", () => { expect(createBank).toHaveBeenCalledWith( "global-bank", expect.objectContaining({ - reflectMission: expect.stringContaining("cross-project user preferences"), - retainMission: expect.stringContaining("Do not retain repo-specific code facts"), - observationsMission: expect.stringContaining("cross-project user preferences"), + reflectMission: expect.stringContaining("cross-project context"), + retainMission: expect.stringContaining("repo-specific code facts"), + observationsMission: expect.stringContaining("cross-project preferences"), retainExtractionMode: "concise", enableObservations: true, }), diff --git a/tests/bank-templates.test.ts b/tests/bank-templates.test.ts index 38f67494..a32781be 100644 --- a/tests/bank-templates.test.ts +++ b/tests/bank-templates.test.ts @@ -64,10 +64,16 @@ describe("bank templates", () => { ); }); - it("sets refresh_after_consolidation on bundled mental models", () => { + it("uses delta refresh defaults on bundled mental models", () => { for (const template of BUILT_IN_BANK_TEMPLATES) { for (const model of template.manifest.mental_models ?? []) { - expect(model.trigger?.refresh_after_consolidation).toBe(true); + expect(model.trigger).toMatchObject({ + mode: "delta", + refresh_after_consolidation: true, + fact_types: ["observation"], + exclude_mental_models: true, + }); + expect(model.max_tokens).toBeLessThanOrEqual(800); } } }); From d58116cc81fea101200c508cf22630cde256b4ca Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:49:33 +0200 Subject: [PATCH 4/9] fix(templates): unexport internal mental-model constants Keep DEFAULT_MM_* and bankGlobalMentalModelsForTemplate module-private. --- extensions/banks/bank-templates.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/extensions/banks/bank-templates.ts b/extensions/banks/bank-templates.ts index e86c6adf..542f825c 100644 --- a/extensions/banks/bank-templates.ts +++ b/extensions/banks/bank-templates.ts @@ -50,7 +50,7 @@ const RETAIN_COMPAT_TAGS = ["source:pi"] as const; * Template import sends the full trigger object; agent create is limited by the TS client mapping * (refreshAfterConsolidation only) until the client maps mode/fact_types. */ -export const DEFAULT_MM_TRIGGER: NonNullable = { +const DEFAULT_MM_TRIGGER: NonNullable = { mode: "delta", refresh_after_consolidation: true, fact_types: ["observation"], @@ -58,8 +58,8 @@ export const DEFAULT_MM_TRIGGER: NonNullable }; /** Seed max_tokens: keep inject lean (AMB cost; inject shares mentalModels.maxChars). */ -export const DEFAULT_MM_MAX_TOKENS_PREFS = 600; -export const DEFAULT_MM_MAX_TOKENS_PROJECT = 800; +const DEFAULT_MM_MAX_TOKENS_PREFS = 600; +const DEFAULT_MM_MAX_TOKENS_PROJECT = 800; // Bank-global on shared coding bank (no project:* tag) — injects for every project. const CODING_BANK_GLOBAL_MENTAL_MODELS: BankTemplateMentalModel[] = [ @@ -293,9 +293,7 @@ function slugProjectId(projectId: string): string { } /** Bank-global models merged into coding project template apply (unstamped). */ -export function bankGlobalMentalModelsForTemplate( - templateId: string, -): readonly BankTemplateMentalModel[] { +function bankGlobalMentalModelsForTemplate(templateId: string): readonly BankTemplateMentalModel[] { if (templateId === "pi-coding-project") return CODING_BANK_GLOBAL_MENTAL_MODELS; return []; } From 60213f61dcb272a4dc1afc2829e59b5ad48a17ec Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:56:10 +0200 Subject: [PATCH 5/9] feat(banks): life missions for conversation user bank Split user-bank defaults: coding keeps cross-project coding prefs; conversation/life uses defaultLifeBankMissions. Wire agentUse into ensureGlobalBank from lifecycle and guided setup. --- .../docs/concepts/coding-memory-evaluation.md | 3 ++ docs/coding-memory-evaluation.md | 1 + extensions/banks/bank-operations.ts | 33 +++++++++++++++++-- extensions/banks/bank-templates.ts | 15 ++++++--- extensions/lifecycle/memory-lifecycle.ts | 1 + extensions/tui/guided-setup.ts | 1 + tests/bank-operations.test.ts | 14 ++++++++ tests/bank-templates.test.ts | 14 ++++++++ 8 files changed, 76 insertions(+), 6 deletions(-) diff --git a/docs-site/src/content/docs/concepts/coding-memory-evaluation.md b/docs-site/src/content/docs/concepts/coding-memory-evaluation.md index 3617dbd5..928a0856 100644 --- a/docs-site/src/content/docs/concepts/coding-memory-evaluation.md +++ b/docs-site/src/content/docs/concepts/coding-memory-evaluation.md @@ -2,6 +2,8 @@ title: "Coding memory evaluation" --- +# Coding memory evaluation notes + What Vectorize’s own evaluation surface implies for **pi-hindsight** defaults. Not a harness in this repo—alignment notes for agents and maintainers. ## Sources @@ -22,6 +24,7 @@ What Vectorize’s own evaluation surface implies for **pi-hindsight** defaults. | Cost is co-equal with accuracy (AMB) | Cap MM `max_tokens` (600–800 seeds); keep recall budgets mid/low unless deep context is required | | Consolidation is a server hot path (perf monitor) | Client should not re-synthesize every turn: inject cached MMs; use **delta** + `refresh_after_consolidation` | | Prefs applied to multi-step work (PersonaMem / AMB) | Bank-global prefs MM + explicit retain of durable prefs (not probe harness noise) | +| Life vs coding user banks | Coding user bank: cross-project coding prefs missions; conversation/life: `defaultLifeBankMissions()` | | Retain then same-turn recall is an anti-pattern | Retain at `agent_end`; recall at `context` (already) | | One MM for everything is an anti-pattern | One model per knowledge dimension (architecture / conventions / decisions / prefs) | diff --git a/docs/coding-memory-evaluation.md b/docs/coding-memory-evaluation.md index acde1aa8..3493d2d5 100644 --- a/docs/coding-memory-evaluation.md +++ b/docs/coding-memory-evaluation.md @@ -20,6 +20,7 @@ What Vectorize’s own evaluation surface implies for **pi-hindsight** defaults. | Cost is co-equal with accuracy (AMB) | Cap MM `max_tokens` (600–800 seeds); keep recall budgets mid/low unless deep context is required | | Consolidation is a server hot path (perf monitor) | Client should not re-synthesize every turn: inject cached MMs; use **delta** + `refresh_after_consolidation` | | Prefs applied to multi-step work (PersonaMem / AMB) | Bank-global prefs MM + explicit retain of durable prefs (not probe harness noise) | +| Life vs coding user banks | Coding user bank: cross-project coding prefs missions; conversation/life: `defaultLifeBankMissions()` | | Retain then same-turn recall is an anti-pattern | Retain at `agent_end`; recall at `context` (already) | | One MM for everything is an anti-pattern | One model per knowledge dimension (architecture / conventions / decisions / prefs) | diff --git a/extensions/banks/bank-operations.ts b/extensions/banks/bank-operations.ts index c9198f8b..c433b8a0 100644 --- a/extensions/banks/bank-operations.ts +++ b/extensions/banks/bank-operations.ts @@ -1,4 +1,4 @@ -import type { BankMissionSettings, HindsightLikeClient } from "../types.js"; +import type { AgentUseProfile, BankMissionSettings, HindsightLikeClient } from "../types.js"; const DEFAULT_PROJECT_REFLECT_MISSION = "You are a senior developer helping a Pi coding agent. Prefer past technical decisions, architecture trade-offs, conventions, and constraints. Be direct and opinionated when memory supports it; do not invent facts not grounded in memory."; @@ -18,6 +18,16 @@ const DEFAULT_GLOBAL_RETAIN_MISSION = const DEFAULT_GLOBAL_OBSERVATIONS_MISSION = "Identify durable cross-project preferences, recurring workflows, coding habits, and stable assistant behavior patterns. Highlight contradictions with prior knowledge. Ignore repo-specific implementation details and one-off probe constraints unless they generalize."; +/** Life / conversation user bank — personal durable context, not coding-repo facts. */ +const DEFAULT_LIFE_REFLECT_MISSION = + "You are a personal assistant with durable life and task context. Use stable preferences, commitments, people/context, and planning habits. Be concise and practical; prefer high-signal clarifications over speculation. Do not invent personal facts not grounded in memory."; + +const DEFAULT_LIFE_RETAIN_MISSION = + "Extract durable personal and life-task memory: communication preferences, commitments, deadlines, people/roles/context, planning and prioritization habits, and stable constraints. Ignore greetings, secrets, probe/bait harness rules, and repo-specific engineering details (file paths, bugs, PR mechanics) unless they are truly personal durable preferences."; + +const DEFAULT_LIFE_OBSERVATIONS_MISSION = + "Identify durable life and task patterns: recurring preferences, commitments, relationship/context patterns, and planning habits. Highlight contradictions with prior knowledge. Ignore transient chat filler, one-off logistics, and coding-repo implementation noise."; + export interface BankMissionDefaults { reflectMission: string; retainMission: string; @@ -40,8 +50,24 @@ export function defaultGlobalBankMissions(): BankMissionDefaults { }; } +/** Defaults for Life / conversation user bank (agentUse conversation). */ +export function defaultLifeBankMissions(): BankMissionDefaults { + return { + reflectMission: DEFAULT_LIFE_REFLECT_MISSION, + retainMission: DEFAULT_LIFE_RETAIN_MISSION, + observationsMission: DEFAULT_LIFE_OBSERVATIONS_MISSION, + }; +} + +/** User/life bank defaults: conversation → life missions; coding → cross-project coding prefs. */ +export function defaultUserBankMissions(agentUse: AgentUseProfile = "coding"): BankMissionDefaults { + return agentUse === "conversation" ? defaultLifeBankMissions() : defaultGlobalBankMissions(); +} + export interface BankMissionConfig extends BankMissionSettings { enableObservations?: boolean; + /** Selects life vs coding-user mission defaults when creating a user bank. */ + agentUse?: AgentUseProfile; } export function resolveBankMissions( @@ -107,7 +133,10 @@ export async function ensureGlobalBank( config: BankMissionConfig = {}, ): Promise { if (!client.createBank || !(await bankNeedsCreate(client, bankId))) return; - const missions = resolveBankMissions(config, defaultGlobalBankMissions()); + const missions = resolveBankMissions( + config, + defaultUserBankMissions(config.agentUse ?? "coding"), + ); await client.createBank(bankId, { name: bankId, ...missions, diff --git a/extensions/banks/bank-templates.ts b/extensions/banks/bank-templates.ts index 542f825c..0e538fda 100644 --- a/extensions/banks/bank-templates.ts +++ b/extensions/banks/bank-templates.ts @@ -5,6 +5,7 @@ import type { } from "@vectorize-io/hindsight-client"; import { defaultGlobalBankMissions, + defaultLifeBankMissions, defaultProjectBankMissions, resolveBankMissions, } from "./bank-operations.js"; @@ -242,7 +243,7 @@ export const BUILT_IN_BANK_TEMPLATES: readonly BuiltInBankTemplate[] = [ description: "Cross-context durable communication and life-workflow preferences.", manifest: { version: "1", - bank: bankConfigFromMissions(defaultGlobalBankMissions()), + bank: bankConfigFromMissions(defaultLifeBankMissions()), mental_models: CONVERSATION_USER_MENTAL_MODELS, }, }, @@ -278,8 +279,14 @@ export function getBuiltInBankTemplate(id: string): BuiltInBankTemplate | undefi return BUILT_IN_BANK_TEMPLATES.find((template) => template.id === resolved); } -function defaultMissionsForTarget(target: BankTemplateTarget): BankMissionDefaults { - return target === "user" ? defaultGlobalBankMissions() : defaultProjectBankMissions(); +function defaultMissionsForTarget( + target: BankTemplateTarget, + agentUse: AgentUseProfile, +): BankMissionDefaults { + if (target === "user") { + return agentUse === "conversation" ? defaultLifeBankMissions() : defaultGlobalBankMissions(); + } + return defaultProjectBankMissions(); } function slugProjectId(projectId: string): string { @@ -311,7 +318,7 @@ export function resolveBankTemplateManifest( ): BankTemplateManifest { const missions = resolveBankMissions( bankMissionSettings, - defaultMissionsForTarget(template.target), + defaultMissionsForTarget(template.target, template.agentUse), ); const projectId = options?.projectId?.trim(); let mental_models = template.manifest.mental_models; diff --git a/extensions/lifecycle/memory-lifecycle.ts b/extensions/lifecycle/memory-lifecycle.ts index 2498044f..440183ff 100644 --- a/extensions/lifecycle/memory-lifecycle.ts +++ b/extensions/lifecycle/memory-lifecycle.ts @@ -193,6 +193,7 @@ export function createMemoryLifecycle(initialCwd: string = process.cwd()): Memor await ensureGlobalBank(client, config.banks.user.bankId, { ...config.banks.user, enableObservations: config.observations.enabled, + agentUse: config.agentUse, }); ensureSucceeded = true; } catch (error) { diff --git a/extensions/tui/guided-setup.ts b/extensions/tui/guided-setup.ts index c1b30e0d..96152185 100644 --- a/extensions/tui/guided-setup.ts +++ b/extensions/tui/guided-setup.ts @@ -218,6 +218,7 @@ export async function resolveSetupBankId(args: { await ensureGlobalBank(args.client, trimmed, { ...args.config.banks.user, enableObservations: args.config.observations.enabled, + agentUse: args.config.agentUse, }); } args.ctx.ui.notify(`Created ${args.kind} bank ${trimmed}.`, "info"); diff --git a/tests/bank-operations.test.ts b/tests/bank-operations.test.ts index 3b8b5f8d..a4e5cc30 100644 --- a/tests/bank-operations.test.ts +++ b/tests/bank-operations.test.ts @@ -149,6 +149,20 @@ describe("bank operations", () => { ); }); + it("uses life missions when ensuring a conversation user bank", async () => { + const createBank = vi.fn(async () => undefined); + await ensureGlobalBank(client({ createBank }), "life-bank", { agentUse: "conversation" }); + + expect(createBank).toHaveBeenCalledWith( + "life-bank", + expect.objectContaining({ + reflectMission: expect.stringContaining("personal assistant"), + retainMission: expect.stringContaining("life-task memory"), + observationsMission: expect.stringContaining("life and task patterns"), + }), + ); + }); + it("does not overwrite missions when bank profile already exists", async () => { const createBank = vi.fn(async () => undefined); const getBankProfile = vi.fn(async () => ({ bankId: "project-bank" })); diff --git a/tests/bank-templates.test.ts b/tests/bank-templates.test.ts index a32781be..298cf10a 100644 --- a/tests/bank-templates.test.ts +++ b/tests/bank-templates.test.ts @@ -55,6 +55,20 @@ describe("bank templates", () => { }); }); + it("uses life bank missions for the conversation user template", async () => { + const { defaultLifeBankMissions } = await import("../extensions/banks/bank-operations.js"); + const template = getBuiltInBankTemplate("pi-conversation-user"); + const defaults = defaultLifeBankMissions(); + + expect(template?.target).toBe("user"); + expect(template?.agentUse).toBe("conversation"); + expect(template?.manifest.bank).toMatchObject({ + reflect_mission: defaults.reflectMission, + retain_mission: defaults.retainMission, + observations_mission: defaults.observationsMission, + }); + }); + it("includes a conversation project template distinct from coding", () => { const coding = getBuiltInBankTemplate("pi-coding-project"); const conversation = getBuiltInBankTemplate("pi-conversation-project"); From 034004f0e55f95fe559103f933d1c033cf8faf01 Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:24:50 +0200 Subject: [PATCH 6/9] docs: agent guide for mission and mental-model quality Add criteria for good missions/MMs and when agents should propose create/update/refresh (dry-run first, never silent). Wire docs-site sidebar and cross-links from starters, core functions, tools surface. --- astro.config.mjs | 4 + .../docs/concepts/hindsight-core-functions.md | 2 + docs-site/src/content/docs/concepts/index.mdx | 1 + .../mission-and-mental-model-quality.md | 111 ++++++++++++++++++ .../starter-mental-model-suggestions.md | 2 + .../docs/reference/tools-and-commands.md | 2 +- docs/hindsight-core-functions.md | 2 + docs/mission-and-mental-model-quality.md | 109 +++++++++++++++++ docs/starter-mental-model-suggestions.md | 2 + docs/tools-and-commands.md | 2 + package.json | 3 +- 11 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md create mode 100644 docs/mission-and-mental-model-quality.md diff --git a/astro.config.mjs b/astro.config.mjs index df533766..fad428f6 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -67,6 +67,10 @@ export default defineConfig({ label: "Coding memory evaluation", slug: "concepts/coding-memory-evaluation", }, + { + label: "Mission and mental-model quality", + slug: "concepts/mission-and-mental-model-quality", + }, ], }, { diff --git a/docs-site/src/content/docs/concepts/hindsight-core-functions.md b/docs-site/src/content/docs/concepts/hindsight-core-functions.md index a20966e0..65386b19 100644 --- a/docs-site/src/content/docs/concepts/hindsight-core-functions.md +++ b/docs-site/src/content/docs/concepts/hindsight-core-functions.md @@ -116,6 +116,8 @@ Mental models are not raw recall. They are reusable synthesized answers. In Pi H Creating a mental model preserves a source query. Hindsight runs that query through reflect and stores the generated content. Bundled templates set `trigger.mode: delta` and `refresh_after_consolidation: true` so consolidation folds new observations without full rewrites every time (server-side; not a Pi background job). +Agents should treat missions and mental models as tunable policy: propose improvements with dry-run tools, do not mutate silently. See [Mission and mental-model quality](/pi-hindsight/concepts/mission-and-mental-model-quality/). + ```text Observations = facts learned from repetition Mental Models = answers built from remembered facts diff --git a/docs-site/src/content/docs/concepts/index.mdx b/docs-site/src/content/docs/concepts/index.mdx index 3dbc3079..bd2731e6 100644 --- a/docs-site/src/content/docs/concepts/index.mdx +++ b/docs-site/src/content/docs/concepts/index.mdx @@ -43,3 +43,4 @@ Concept pages explain how Pi Hindsight uses Hindsight. They should describe stab - [Hindsight core functions](./hindsight-core-functions/) links Pi behavior back to Hindsight primitives. - [Starter mental model suggestions](./starter-mental-model-suggestions/) explains seed material for mental models managed in the Hindsight web UI. - [Coding memory evaluation](./coding-memory-evaluation/) ties defaults to AMB, sde-bench, and Hindsight perf-monitor learnings. +- [Mission and mental-model quality](./mission-and-mental-model-quality/) tells agents when to propose mission/MM create, update, or refresh. diff --git a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md new file mode 100644 index 00000000..74160264 --- /dev/null +++ b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md @@ -0,0 +1,111 @@ +--- +title: "Mission and mental-model quality" +--- + +How bank **missions** and **mental models** should look in pi-hindsight — and when the agent should **propose** create / update / refresh (never silent bank mutation). + +Official Hindsight guidance: [Best practices](https://hindsight.vectorize.io/best-practices), [Mental models](https://hindsight.vectorize.io/developer/api/mental-models). Seeds: [Starter mental model suggestions](/pi-hindsight/concepts/starter-mental-model-suggestions/). Eval context: [Coding memory evaluation](/pi-hindsight/concepts/coding-memory-evaluation/). + +## Hard rules (always) + +- **Propose, don’t invent silently.** Create/update/refresh/delete use tools with **dry-run first** (`hindsight_mental_model`, `hindsight_bank`); user confirms via dry-run false or hub `t`. +- **Tags ⊆ retain tags.** Project models need `source:pi` + `project:`; bank-global prefs: `source:pi` only. Tags not written at retain → empty refresh. +- **One dimension per mental model.** Not “everything about the user/project.” +- **Lean inject:** prefs ~600 tokens, project ~800. Fat content burns every turn. +- **Refresh:** prefer `mode: delta` + `refresh_after_consolidation` on templates; agent create may only set consolidation refresh until the TS client maps full trigger fields. +- **Missions** steer extraction/consolidation/reflect — vague missions → noisy memory (Hindsight #1 quality failure). + +## Bank missions — what “good” looks like + +Three strings per bank (retain / observations / reflect): + +| Mission | Job | Good | Bad | +| ---------------- | ------------------------------------------ | ------------------------------------------------------------------ | ---------------------------------- | +| **retain** | What to extract / ignore from raw sessions | Concrete fact types + ignore list (greets, secrets, probe harness) | “Be helpful”, “extract everything” | +| **observations** | Durable patterns after retain | Patterns, contradictions, durable only | Transient task state | +| **reflect** | Persona for synthesis | Domain role + grounded + opinionated when supported | Generic chatbot | + +### Coding bank (project) + +- Retain: decisions, trade-offs, blockers, conventions, durable project prefs. +- Observations: stable architecture/process patterns; not TODOs of the day. +- Reflect: senior developer for _this_ repo. + +### Coding user bank (life bank **off** / cross-project prefs) + +- Retain: cross-project assistant prefs, workflows, clarification style. +- **Not** file paths, project bugs, PR noise unless it generalizes. + +### Life / conversation user bank + +- Retain: commitments, people/context, planning habits, communication prefs. +- **Not** repo engineering detail unless it’s truly personal durable preference. +- Reflect: personal/life-task assistant, not code reviewer. + +Defaults live in `extensions/banks/bank-operations.ts` (`defaultProjectBankMissions`, `defaultGlobalBankMissions`, `defaultLifeBankMissions`). + +### When to propose mission changes + +Propose `hindsight_bank` `update_mission` (dry-run) when: + +- Extraction is consistently wrong (too much noise / missing decisions). +- User says prefs are “wrong” or “missing” after several sessions. +- Switching coding ↔ conversation use without matching missions. + +Do **not** churn missions every session. + +## Mental models — what “good” looks like + +| Field | Good | Bad | +| ---------------- | ------------------------------------------------------------------------ | ------------------------------------ | +| **name** | Short dimension label | Marketing fluff | +| **source_query** | Natural-language reflect question; “durable only”; exclude probe/one-off | Keyword dump; “summarize everything” | +| **tags** | Subset of retain tags; project vs bank-global deliberate | Random tags never retained | +| **max_tokens** | 600–800 for seeds | Unbounded essays | +| **content** | Stable background; inject preamble says not instructions | Session task lists, secrets | + +### Standard dimensions (coding) + +| Id pattern | Dimension | +| ---------------------------------------- | ------------------------------------ | +| `coding-assistant-operating-preferences` | Bank-global agent prefs | +| `project-architecture-and-seams--` | Where changes belong | +| `project-conventions--` | Build/test/review style | +| `project-decisions--` | Durable product/architecture choices | + +Conversation/life seeds: goals, people/context, decisions/preferences (see starter doc). + +### When to propose mental-model actions + +| Signal | Proposal | +| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| Missing expected starter for active project (domain-tagged bank has other projects only) | Create/apply starters for **this** `project:` + bank-global prefs if missing | +| Inject shows empty / “Generating…” / useless “#” | Refresh (or clear+refresh if delta-drifted) | +| Prefs contradict user (e.g. “never ask questions” vs user wants high-signal questions) | Explicit retain of correct pref + refresh prefs MM; tighten source_query if needed | +| Content bloated / always truncated in inject | Lower `max_tokens`, refresh; avoid adding more models | +| Same question every session, no MM | Propose **one** new model for that dimension only | +| User finished a major architecture decision | Refresh `project-decisions` after retain has landed (not same turn as retain) | + +### When **not** to propose + +- Every turn “should we refresh mental models?” +- Creating models from probe/bait sessions. +- Duplicating recall (raw facts) as a mental model. +- Editing other projects’ tagged models while working in this project. + +## Agent workflow (copy pattern) + +1. **Inspect:** `hindsight_status`, `hindsight_scope`, `hindsight_mental_model` list/get, `hindsight_bank` get. +2. **Diagnose** against this doc (tags, size, empty content, wrong prefs, missing project starters). +3. **Propose** in plain language: what changes, why, dry-run payload. +4. **Apply** only after user ok: `dryRun: false` on the same tool, or hub `t`. +5. **Verify:** list/get again; warn that inject list cache can lag (~`mentalModels.cacheTtlMs`). + +## Anti-patterns + +- Pre-summarizing before retain. +- Random `document_id`s / missing `context` on retain. +- Metadata for filtering (use tags). +- One mega mental model. +- Mission “extract all information.” +- Refreshing before new retains are available (same turn). diff --git a/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md b/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md index 4d146e58..9a02564a 100644 --- a/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md +++ b/docs-site/src/content/docs/concepts/starter-mental-model-suggestions.md @@ -8,6 +8,8 @@ Mental models are a **core** Hindsight feature. Pi Hindsight provisions them thr Starter suggestions live here and in `extensions/banks/bank-templates.ts` (keep in sync). Applying a bundled template creates these models via Hindsight's bank-template import endpoint — dry-run gated, confirm before write. Arbitrary mental-model authoring remains in the Hindsight control-plane web UI. +For quality criteria and when agents should **propose** create/update/refresh (never silent), see [Mission and mental-model quality](/pi-hindsight/concepts/mission-and-mental-model-quality/). + ## Product rules - Suggestions are explicit opt-in (setup/TUI), never silent bank mutation on every boot. diff --git a/docs-site/src/content/docs/reference/tools-and-commands.md b/docs-site/src/content/docs/reference/tools-and-commands.md index e6d99909..958ef8a6 100644 --- a/docs-site/src/content/docs/reference/tools-and-commands.md +++ b/docs-site/src/content/docs/reference/tools-and-commands.md @@ -43,7 +43,7 @@ Config field `agentUse` is `coding` (default) or `conversation`. Legacy id `pi-user-preferences` still resolves to `pi-coding-user`. -When mental models exist on active banks and `mentalModels.inject` is true (default), their content is injected into automatic context alongside recall (ephemeral; not retained). See [Starter mental model suggestions](/pi-hindsight/concepts/starter-mental-model-suggestions/). +When mental models exist on active banks and `mentalModels.inject` is true (default), their content is injected into automatic context alongside recall (ephemeral; not retained). See [Starter mental model suggestions](/pi-hindsight/concepts/starter-mental-model-suggestions/). Quality guide: [Mission and mental-model quality](/pi-hindsight/concepts/mission-and-mental-model-quality/). ## Explicit tools diff --git a/docs/hindsight-core-functions.md b/docs/hindsight-core-functions.md index de997fdf..c7e9f5c7 100644 --- a/docs/hindsight-core-functions.md +++ b/docs/hindsight-core-functions.md @@ -114,6 +114,8 @@ Mental models are not raw recall. They are reusable synthesized answers. In Pi H Creating a mental model preserves a source query. Hindsight runs that query through reflect and stores the generated content. Bundled templates set `trigger.mode: delta` and `refresh_after_consolidation: true` so consolidation folds new observations without full rewrites every time (server-side; not a Pi background job). +Agents should treat missions and mental models as tunable policy: propose improvements with dry-run tools, do not mutate silently. See [Mission and mental-model quality](mission-and-mental-model-quality.md). + ```text Observations = facts learned from repetition Mental Models = answers built from remembered facts diff --git a/docs/mission-and-mental-model-quality.md b/docs/mission-and-mental-model-quality.md new file mode 100644 index 00000000..b74d8ee6 --- /dev/null +++ b/docs/mission-and-mental-model-quality.md @@ -0,0 +1,109 @@ +# Mission and mental-model quality (agent guide) + +How bank **missions** and **mental models** should look in pi-hindsight — and when the agent should **propose** create / update / refresh (never silent bank mutation). + +Official Hindsight guidance: [Best practices](https://hindsight.vectorize.io/best-practices), [Mental models](https://hindsight.vectorize.io/developer/api/mental-models). Seeds: [Starter mental model suggestions](starter-mental-model-suggestions.md). Eval context: [Coding memory evaluation](coding-memory-evaluation.md). + +## Hard rules (always) + +- **Propose, don’t invent silently.** Create/update/refresh/delete use tools with **dry-run first** (`hindsight_mental_model`, `hindsight_bank`); user confirms via dry-run false or hub `t`. +- **Tags ⊆ retain tags.** Project models need `source:pi` + `project:`; bank-global prefs: `source:pi` only. Tags not written at retain → empty refresh. +- **One dimension per mental model.** Not “everything about the user/project.” +- **Lean inject:** prefs ~600 tokens, project ~800. Fat content burns every turn. +- **Refresh:** prefer `mode: delta` + `refresh_after_consolidation` on templates; agent create may only set consolidation refresh until the TS client maps full trigger fields. +- **Missions** steer extraction/consolidation/reflect — vague missions → noisy memory (Hindsight #1 quality failure). + +## Bank missions — what “good” looks like + +Three strings per bank (retain / observations / reflect): + +| Mission | Job | Good | Bad | +| ---------------- | ------------------------------------------ | ------------------------------------------------------------------ | ---------------------------------- | +| **retain** | What to extract / ignore from raw sessions | Concrete fact types + ignore list (greets, secrets, probe harness) | “Be helpful”, “extract everything” | +| **observations** | Durable patterns after retain | Patterns, contradictions, durable only | Transient task state | +| **reflect** | Persona for synthesis | Domain role + grounded + opinionated when supported | Generic chatbot | + +### Coding bank (project) + +- Retain: decisions, trade-offs, blockers, conventions, durable project prefs. +- Observations: stable architecture/process patterns; not TODOs of the day. +- Reflect: senior developer for _this_ repo. + +### Coding user bank (life bank **off** / cross-project prefs) + +- Retain: cross-project assistant prefs, workflows, clarification style. +- **Not** file paths, project bugs, PR noise unless it generalizes. + +### Life / conversation user bank + +- Retain: commitments, people/context, planning habits, communication prefs. +- **Not** repo engineering detail unless it’s truly personal durable preference. +- Reflect: personal/life-task assistant, not code reviewer. + +Defaults live in `extensions/banks/bank-operations.ts` (`defaultProjectBankMissions`, `defaultGlobalBankMissions`, `defaultLifeBankMissions`). + +### When to propose mission changes + +Propose `hindsight_bank` `update_mission` (dry-run) when: + +- Extraction is consistently wrong (too much noise / missing decisions). +- User says prefs are “wrong” or “missing” after several sessions. +- Switching coding ↔ conversation use without matching missions. + +Do **not** churn missions every session. + +## Mental models — what “good” looks like + +| Field | Good | Bad | +| ---------------- | ------------------------------------------------------------------------ | ------------------------------------ | +| **name** | Short dimension label | Marketing fluff | +| **source_query** | Natural-language reflect question; “durable only”; exclude probe/one-off | Keyword dump; “summarize everything” | +| **tags** | Subset of retain tags; project vs bank-global deliberate | Random tags never retained | +| **max_tokens** | 600–800 for seeds | Unbounded essays | +| **content** | Stable background; inject preamble says not instructions | Session task lists, secrets | + +### Standard dimensions (coding) + +| Id pattern | Dimension | +| ---------------------------------------- | ------------------------------------ | +| `coding-assistant-operating-preferences` | Bank-global agent prefs | +| `project-architecture-and-seams--` | Where changes belong | +| `project-conventions--` | Build/test/review style | +| `project-decisions--` | Durable product/architecture choices | + +Conversation/life seeds: goals, people/context, decisions/preferences (see starter doc). + +### When to propose mental-model actions + +| Signal | Proposal | +| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| Missing expected starter for active project (domain-tagged bank has other projects only) | Create/apply starters for **this** `project:` + bank-global prefs if missing | +| Inject shows empty / “Generating…” / useless “#” | Refresh (or clear+refresh if delta-drifted) | +| Prefs contradict user (e.g. “never ask questions” vs user wants high-signal questions) | Explicit retain of correct pref + refresh prefs MM; tighten source_query if needed | +| Content bloated / always truncated in inject | Lower `max_tokens`, refresh; avoid adding more models | +| Same question every session, no MM | Propose **one** new model for that dimension only | +| User finished a major architecture decision | Refresh `project-decisions` after retain has landed (not same turn as retain) | + +### When **not** to propose + +- Every turn “should we refresh mental models?” +- Creating models from probe/bait sessions. +- Duplicating recall (raw facts) as a mental model. +- Editing other projects’ tagged models while working in this project. + +## Agent workflow (copy pattern) + +1. **Inspect:** `hindsight_status`, `hindsight_scope`, `hindsight_mental_model` list/get, `hindsight_bank` get. +2. **Diagnose** against this doc (tags, size, empty content, wrong prefs, missing project starters). +3. **Propose** in plain language: what changes, why, dry-run payload. +4. **Apply** only after user ok: `dryRun: false` on the same tool, or hub `t`. +5. **Verify:** list/get again; warn that inject list cache can lag (~`mentalModels.cacheTtlMs`). + +## Anti-patterns + +- Pre-summarizing before retain. +- Random `document_id`s / missing `context` on retain. +- Metadata for filtering (use tags). +- One mega mental model. +- Mission “extract all information.” +- Refreshing before new retains are available (same turn). diff --git a/docs/starter-mental-model-suggestions.md b/docs/starter-mental-model-suggestions.md index 3067402e..17bcdc86 100644 --- a/docs/starter-mental-model-suggestions.md +++ b/docs/starter-mental-model-suggestions.md @@ -6,6 +6,8 @@ Mental models are a **core** Hindsight feature. Pi Hindsight provisions them thr Starter suggestions live here and in `extensions/banks/bank-templates.ts` (keep in sync). Applying a bundled template creates these models via Hindsight's bank-template import endpoint — dry-run gated, confirm before write. Arbitrary mental-model authoring remains in the Hindsight control-plane web UI. +For quality criteria and when agents should **propose** create/update/refresh (never silent), see [Mission and mental-model quality](mission-and-mental-model-quality.md). + ## Product rules - Suggestions are explicit opt-in (setup/TUI), never silent bank mutation on every boot. diff --git a/docs/tools-and-commands.md b/docs/tools-and-commands.md index b966cea9..64639c2c 100644 --- a/docs/tools-and-commands.md +++ b/docs/tools-and-commands.md @@ -41,6 +41,8 @@ Legacy id `pi-user-preferences` still resolves to `pi-coding-user`. When mental models exist on active banks and `mentalModels.inject` is true (default), their content is injected into automatic context alongside recall (ephemeral; not retained). List results are cached for `mentalModels.cacheTtlMs` (default 5 minutes). +Quality criteria and when the agent should **propose** mission/MM create, update, or refresh (dry-run first, never silent): [Mission and mental-model quality](mission-and-mental-model-quality.md). + ## Model-facing tools Available for the agent (control plane is agent-first; TUI is thin): diff --git a/package.json b/package.json index 5213c273..a24a242c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "docs/next-opt-out-design.md", "docs/surface-reference.md", "docs/starter-mental-model-suggestions.md", - "docs/coding-memory-evaluation.md" + "docs/coding-memory-evaluation.md", + "docs/mission-and-mental-model-quality.md" ], "type": "module", "publishConfig": { From 8230bfccf6f679f3361b30cb3cf3eb245199fcdd Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:25:42 +0200 Subject: [PATCH 7/9] docs: add optimization signals for missions and mental models When it's worth optimizing, concrete inspect signals, cadence, and priority order so agents propose fixes instead of nagging every turn. --- .../mission-and-mental-model-quality.md | 63 ++++++++++++++++--- docs/mission-and-mental-model-quality.md | 63 ++++++++++++++++--- 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md index 74160264..c0fcf048 100644 --- a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md +++ b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md @@ -54,6 +54,59 @@ Propose `hindsight_bank` `update_mission` (dry-run) when: Do **not** churn missions every session. +## When optimization is worth it (signals) + +Optimize only when a **repeatable problem** shows up—not because content is imperfect once. + +### Mental-model signals (inspect inject + `hindsight_mental_model` list/get) + +| Signal | What you see | Likely fix | Priority | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| **Broken / empty content** | Inject or get shows `"#"`, empty body, “Generating…”, or only a title | `refresh`; if still empty after retain has landed, check tags ⊆ retain tags; clear+refresh if delta-drifted | **High** — do this session | +| **Wrong durable policy** | Prefs MM contradicts the user’s stated preference (e.g. “never ask” vs wants high-signal questions) | Explicit `hindsight_retain` of the correct pref → then refresh prefs MM; tighten `source_query` if probe noise keeps winning | **High** | +| **Missing project starters** | Active `project:` has no architecture/conventions/decisions models; bank only has other projects’ models | Propose apply/create starters for **this** project (+ bank-global prefs if missing) | **High** on setup / first real work in repo | +| **Inject always truncated** | Blocks end mid-sentence; one model dominates (multi‑k chars, e.g. 8k–12k) while others starve | Cap `max_tokens` (600–800), refresh fat model; don’t add more models | **Medium** | +| **Stale after big decisions** | User finished a real architecture/product choice; MM still describes the old world | Wait until retain finished (next turn+); refresh `project-decisions` (and architecture if seams changed) | **Medium** — after the decision is retained | +| **Recurring same question** | Every session user asks the same durable “how do we X?” | One new MM for that **single** dimension, lean max_tokens | **Medium** | +| **Cross-project bleed** | Prefs or decisions clearly about another repo appear under this project’s inject | Fix tags (project vs bank-global); never “fix” by editing another project’s ids while working here | **High** if isolation broken | +| **Noise as “facts”** | Probe/bait/test harness text treated as durable prefs | Retain correction + refresh; mission ignore-list for probes; don’t import bait sessions | **Medium** | +| **Slightly imperfect prose** | Wording could be nicer but policy is right | Leave it; delta refresh after consolidation is enough | **Low** — do not nag | + +### Mission signals (`hindsight_bank` get + memory quality over several sessions) + +| Signal | What you see | Likely fix | Priority | +| --------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------- | +| **Noise flood** | Recall full of greets, scheduling, tool-probe rules, one-off chat | Tighten **retain** mission ignore-list | Medium–High if multi-session | +| **Missing decisions** | Real architecture choices never appear in recall/MMs after real work | Broaden retain “extract decisions/trade-offs”; keep ignore list | Medium | +| **Transient as durable** | Observations track active TODOs / “today we…” as eternal truth | Observations mission: durable only, contradictions, not task state | Medium | +| **Wrong persona** | Reflect answers like a generic chatbot or wrong domain (coding vs life) | Align reflect mission with bank role (coding project vs life user) | Medium when reflect is used | +| **Coding vs life mismatch** | Life bank extract file paths/PRs; coding bank extract pure calendar chit-chat | Use life vs global defaults (`defaultLifeBankMissions` vs coding user) | High only if life bank is enabled | + +### Cadence (how often to even look) + +| Cadence | Action | +| ---------------------------------------------- | ------------------------------------------------------------------------------------- | +| **Session start / first work in a repo** | Quick: expected project starters present? Prefs MM non-empty and not obviously wrong? | +| **After a major decision or multi-hour slice** | Optional: refresh decisions/architecture **after** retain has had a chance to land | +| **User complains about memory** | Full inspect (status, scope, list MMs, bank missions); propose a **small** patch list | +| **Not every turn** | No “shall I refresh mental models?” spam | + +### Priority order when several things look off + +1. Broken/empty MM content or isolation bleed +2. Prefs contradicting the user +3. Missing starters for **this** project +4. Fat inject / truncation +5. Mission noise (only if pattern spans sessions) +6. Polish / “could be nicer” + +### Cost check before proposing + +Ask: will this change save **future** turns or only rephrase once? + +- **Yes (optimize):** empty prefs, wrong “never ask”, 12k decisions eating inject, missing project MMs. +- **No (skip):** single awkward sentence, one stale bullet that recall already covers, speculative “maybe add 5 more models.” + ## Mental models — what “good” looks like | Field | Good | Bad | @@ -77,14 +130,7 @@ Conversation/life seeds: goals, people/context, decisions/preferences (see start ### When to propose mental-model actions -| Signal | Proposal | -| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| Missing expected starter for active project (domain-tagged bank has other projects only) | Create/apply starters for **this** `project:` + bank-global prefs if missing | -| Inject shows empty / “Generating…” / useless “#” | Refresh (or clear+refresh if delta-drifted) | -| Prefs contradict user (e.g. “never ask questions” vs user wants high-signal questions) | Explicit retain of correct pref + refresh prefs MM; tighten source_query if needed | -| Content bloated / always truncated in inject | Lower `max_tokens`, refresh; avoid adding more models | -| Same question every session, no MM | Propose **one** new model for that dimension only | -| User finished a major architecture decision | Refresh `project-decisions` after retain has landed (not same turn as retain) | +Use the **signals table above** (broken content, wrong prefs, missing starters, fat inject, post-decision refresh, recurring questions). Same tool path: dry-run create/update/refresh → user confirms. ### When **not** to propose @@ -92,6 +138,7 @@ Conversation/life seeds: goals, people/context, decisions/preferences (see start - Creating models from probe/bait sessions. - Duplicating recall (raw facts) as a mental model. - Editing other projects’ tagged models while working in this project. +- Optimizing for polish when policy and inject budget are already fine. ## Agent workflow (copy pattern) diff --git a/docs/mission-and-mental-model-quality.md b/docs/mission-and-mental-model-quality.md index b74d8ee6..3a7ebf9e 100644 --- a/docs/mission-and-mental-model-quality.md +++ b/docs/mission-and-mental-model-quality.md @@ -52,6 +52,59 @@ Propose `hindsight_bank` `update_mission` (dry-run) when: Do **not** churn missions every session. +## When optimization is worth it (signals) + +Optimize only when a **repeatable problem** shows up—not because content is imperfect once. + +### Mental-model signals (inspect inject + `hindsight_mental_model` list/get) + +| Signal | What you see | Likely fix | Priority | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| **Broken / empty content** | Inject or get shows `"#"`, empty body, “Generating…”, or only a title | `refresh`; if still empty after retain has landed, check tags ⊆ retain tags; clear+refresh if delta-drifted | **High** — do this session | +| **Wrong durable policy** | Prefs MM contradicts the user’s stated preference (e.g. “never ask” vs wants high-signal questions) | Explicit `hindsight_retain` of the correct pref → then refresh prefs MM; tighten `source_query` if probe noise keeps winning | **High** | +| **Missing project starters** | Active `project:` has no architecture/conventions/decisions models; bank only has other projects’ models | Propose apply/create starters for **this** project (+ bank-global prefs if missing) | **High** on setup / first real work in repo | +| **Inject always truncated** | Blocks end mid-sentence; one model dominates (multi‑k chars, e.g. 8k–12k) while others starve | Cap `max_tokens` (600–800), refresh fat model; don’t add more models | **Medium** | +| **Stale after big decisions** | User finished a real architecture/product choice; MM still describes the old world | Wait until retain finished (next turn+); refresh `project-decisions` (and architecture if seams changed) | **Medium** — after the decision is retained | +| **Recurring same question** | Every session user asks the same durable “how do we X?” | One new MM for that **single** dimension, lean max_tokens | **Medium** | +| **Cross-project bleed** | Prefs or decisions clearly about another repo appear under this project’s inject | Fix tags (project vs bank-global); never “fix” by editing another project’s ids while working here | **High** if isolation broken | +| **Noise as “facts”** | Probe/bait/test harness text treated as durable prefs | Retain correction + refresh; mission ignore-list for probes; don’t import bait sessions | **Medium** | +| **Slightly imperfect prose** | Wording could be nicer but policy is right | Leave it; delta refresh after consolidation is enough | **Low** — do not nag | + +### Mission signals (`hindsight_bank` get + memory quality over several sessions) + +| Signal | What you see | Likely fix | Priority | +| --------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------- | +| **Noise flood** | Recall full of greets, scheduling, tool-probe rules, one-off chat | Tighten **retain** mission ignore-list | Medium–High if multi-session | +| **Missing decisions** | Real architecture choices never appear in recall/MMs after real work | Broaden retain “extract decisions/trade-offs”; keep ignore list | Medium | +| **Transient as durable** | Observations track active TODOs / “today we…” as eternal truth | Observations mission: durable only, contradictions, not task state | Medium | +| **Wrong persona** | Reflect answers like a generic chatbot or wrong domain (coding vs life) | Align reflect mission with bank role (coding project vs life user) | Medium when reflect is used | +| **Coding vs life mismatch** | Life bank extract file paths/PRs; coding bank extract pure calendar chit-chat | Use life vs global defaults (`defaultLifeBankMissions` vs coding user) | High only if life bank is enabled | + +### Cadence (how often to even look) + +| Cadence | Action | +| ---------------------------------------------- | ------------------------------------------------------------------------------------- | +| **Session start / first work in a repo** | Quick: expected project starters present? Prefs MM non-empty and not obviously wrong? | +| **After a major decision or multi-hour slice** | Optional: refresh decisions/architecture **after** retain has had a chance to land | +| **User complains about memory** | Full inspect (status, scope, list MMs, bank missions); propose a **small** patch list | +| **Not every turn** | No “shall I refresh mental models?” spam | + +### Priority order when several things look off + +1. Broken/empty MM content or isolation bleed +2. Prefs contradicting the user +3. Missing starters for **this** project +4. Fat inject / truncation +5. Mission noise (only if pattern spans sessions) +6. Polish / “could be nicer” + +### Cost check before proposing + +Ask: will this change save **future** turns or only rephrase once? + +- **Yes (optimize):** empty prefs, wrong “never ask”, 12k decisions eating inject, missing project MMs. +- **No (skip):** single awkward sentence, one stale bullet that recall already covers, speculative “maybe add 5 more models.” + ## Mental models — what “good” looks like | Field | Good | Bad | @@ -75,14 +128,7 @@ Conversation/life seeds: goals, people/context, decisions/preferences (see start ### When to propose mental-model actions -| Signal | Proposal | -| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| Missing expected starter for active project (domain-tagged bank has other projects only) | Create/apply starters for **this** `project:` + bank-global prefs if missing | -| Inject shows empty / “Generating…” / useless “#” | Refresh (or clear+refresh if delta-drifted) | -| Prefs contradict user (e.g. “never ask questions” vs user wants high-signal questions) | Explicit retain of correct pref + refresh prefs MM; tighten source_query if needed | -| Content bloated / always truncated in inject | Lower `max_tokens`, refresh; avoid adding more models | -| Same question every session, no MM | Propose **one** new model for that dimension only | -| User finished a major architecture decision | Refresh `project-decisions` after retain has landed (not same turn as retain) | +Use the **signals table above** (broken content, wrong prefs, missing starters, fat inject, post-decision refresh, recurring questions). Same tool path: dry-run create/update/refresh → user confirms. ### When **not** to propose @@ -90,6 +136,7 @@ Conversation/life seeds: goals, people/context, decisions/preferences (see start - Creating models from probe/bait sessions. - Duplicating recall (raw facts) as a mental model. - Editing other projects’ tagged models while working in this project. +- Optimizing for polish when policy and inject budget are already fine. ## Agent workflow (copy pattern) From e2d34ea610a6a8d35050c409e6757f663cd5c736 Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:28:48 +0200 Subject: [PATCH 8/9] feat(skills): hindsight-memory-doctor on memory complaints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Small on-demand skill: when the user is frustrated about memory, inspect status/scope/MMs/missions, score against quality signals, propose dry-run fixes — never silent bank mutation. --- .../mission-and-mental-model-quality.md | 2 + docs/mission-and-mental-model-quality.md | 2 + package.json | 6 +- skills/hindsight-memory-doctor/SKILL.md | 75 +++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 skills/hindsight-memory-doctor/SKILL.md diff --git a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md index c0fcf048..a058838c 100644 --- a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md +++ b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md @@ -142,6 +142,8 @@ Use the **signals table above** (broken content, wrong prefs, missing starters, ## Agent workflow (copy pattern) +On user **memory complaints**, load skill `hindsight-memory-doctor` (`/skill:hindsight-memory-doctor`) — same inspect → diagnose → dry-run propose flow. + 1. **Inspect:** `hindsight_status`, `hindsight_scope`, `hindsight_mental_model` list/get, `hindsight_bank` get. 2. **Diagnose** against this doc (tags, size, empty content, wrong prefs, missing project starters). 3. **Propose** in plain language: what changes, why, dry-run payload. diff --git a/docs/mission-and-mental-model-quality.md b/docs/mission-and-mental-model-quality.md index 3a7ebf9e..ee565eb5 100644 --- a/docs/mission-and-mental-model-quality.md +++ b/docs/mission-and-mental-model-quality.md @@ -140,6 +140,8 @@ Use the **signals table above** (broken content, wrong prefs, missing starters, ## Agent workflow (copy pattern) +On user **memory complaints**, load skill `hindsight-memory-doctor` (`/skill:hindsight-memory-doctor`) — same inspect → diagnose → dry-run propose flow. + 1. **Inspect:** `hindsight_status`, `hindsight_scope`, `hindsight_mental_model` list/get, `hindsight_bank` get. 2. **Diagnose** against this doc (tags, size, empty content, wrong prefs, missing project starters). 3. **Propose** in plain language: what changes, why, dry-run payload. diff --git a/package.json b/package.json index a24a242c..c70a9067 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "docs/surface-reference.md", "docs/starter-mental-model-suggestions.md", "docs/coding-memory-evaluation.md", - "docs/mission-and-mental-model-quality.md" + "docs/mission-and-mental-model-quality.md", + "skills" ], "type": "module", "publishConfig": { @@ -109,6 +110,9 @@ "pi": { "extensions": [ "./extensions" + ], + "skills": [ + "./skills" ] } } diff --git a/skills/hindsight-memory-doctor/SKILL.md b/skills/hindsight-memory-doctor/SKILL.md new file mode 100644 index 00000000..7add9726 --- /dev/null +++ b/skills/hindsight-memory-doctor/SKILL.md @@ -0,0 +1,75 @@ +--- +name: hindsight-memory-doctor +description: > + Self-check Pi Hindsight memory when the user is frustrated, confused, or + complains about memory quality (wrong prefs, missing context, "it forgot", + "never ask questions" vs wants questions, empty inject, wrong project). + Inspect status/scope/mental models/bank missions, compare to quality docs, + propose dry-run fixes. Trigger on memory complaints, "memory is wrong", + "forgot", "why did it inject", /hindsight-memory-doctor, or conflict with + recalled/MM content. Do NOT run every turn. +--- + +# Hindsight memory doctor + +When the user is **upset or skeptical about memory**, behave like a careful human under conflict: **stop, inspect, question the memory stack**, then propose a small fix. Do not argue from broken inject. Do not mutate banks silently. + +Authoritative criteria: `docs/mission-and-mental-model-quality.md` (packaged; also docs-site *Mission and mental-model quality*). + +## Trigger (yes) + +- User complains about memory / Hindsight / inject / prefs / “forgot” / wrong project context +- Injected MM or recall **contradicts** the user (e.g. “do not ask questions” vs wants high-signal questions) +- User asks why memory looks wrong or empty +- Explicit `/skill:hindsight-memory-doctor` + +## Do not trigger + +- Normal coding with healthy inject +- Single imperfect recall hit +- Every turn “maybe refresh?” + +## Workflow (always) + +1. **Acknowledge** the complaint in one line. Treat it as a possible **system** issue, not only user error. +2. **Inspect** (tools; no dry-run needed for reads): + - `hindsight_status` + - `hindsight_scope` + - `hindsight_mental_model` action=list (coding bank; life/user if enabled) + - `hindsight_mental_model` action=get for broken/suspicious ids + - `hindsight_bank` action=get for coding (and life if enabled) — missions +3. **Score against the quality guide** (priority order): + 1. Broken/empty MM content (`#`, empty, Generating…) + 2. Prefs contradict the user + 3. Missing starters for **this** `project:` + 4. Fat inject / truncation + 5. Mission noise (only if multi-session pattern) + 6. Polish-only → skip +4. **Propose** a short plan (max 3 actions). Prefer: + - explicit retain of the **correct** durable fact + - refresh (or clear+refresh if delta-drift) + - create missing starters for this project + - mission patch only if extraction is systematically wrong +5. **Dry-run first** for any mutation (`hindsight_mental_model` / `hindsight_bank` dryRun true). Apply only after user ok (`dryRun: false` or hub). +6. **Verify** list/get again. Note inject cache may lag (`mentalModels.cacheTtlMs`, often ~5 min) — new session or wait. + +## Output shape + +```text +Memory doctor +- What I checked: … +- What’s wrong (priority): … +- Proposed fixes (dry-run): … +- Not doing: … (noise / other projects / silent rewrite) +``` + +## Hard rules + +- Never silent create/update/delete of mental models or missions. +- Never edit another project’s `project:` models to “fix” this session. +- Same-turn retain + rely-on-recall is invalid; refresh after retain has landed. +- If tools unavailable / setup incomplete: say so and point to `/hindsight` setup. + +## Optional user invoke + +`/skill:hindsight-memory-doctor` — run full workflow once. From 334ee00b3b71c25de5f0588c1f0046dde2e2faab Mon Sep 17 00:00:00 2001 From: luxus <7449+luxus@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:35:35 +0200 Subject: [PATCH 9/9] fix(templates): prefs MMs refresh with world+experience facts Observation-only fact_types rebuilt coding-assistant-operating-preferences as empty '#' after clear. Prefs/user seeds use world+experience+observation; project architecture/conventions/decisions stay observation-first. Document signal for memory-doctor. --- .../mission-and-mental-model-quality.md | 22 +++++----- docs/mission-and-mental-model-quality.md | 22 +++++----- extensions/banks/bank-templates.ts | 43 +++++++++++-------- skills/hindsight-memory-doctor/SKILL.md | 4 +- tests/bank-templates.test.ts | 19 +++++++- 5 files changed, 68 insertions(+), 42 deletions(-) diff --git a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md index a058838c..ce502f5a 100644 --- a/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md +++ b/docs-site/src/content/docs/concepts/mission-and-mental-model-quality.md @@ -60,17 +60,17 @@ Optimize only when a **repeatable problem** shows up—not because content is im ### Mental-model signals (inspect inject + `hindsight_mental_model` list/get) -| Signal | What you see | Likely fix | Priority | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| **Broken / empty content** | Inject or get shows `"#"`, empty body, “Generating…”, or only a title | `refresh`; if still empty after retain has landed, check tags ⊆ retain tags; clear+refresh if delta-drifted | **High** — do this session | -| **Wrong durable policy** | Prefs MM contradicts the user’s stated preference (e.g. “never ask” vs wants high-signal questions) | Explicit `hindsight_retain` of the correct pref → then refresh prefs MM; tighten `source_query` if probe noise keeps winning | **High** | -| **Missing project starters** | Active `project:` has no architecture/conventions/decisions models; bank only has other projects’ models | Propose apply/create starters for **this** project (+ bank-global prefs if missing) | **High** on setup / first real work in repo | -| **Inject always truncated** | Blocks end mid-sentence; one model dominates (multi‑k chars, e.g. 8k–12k) while others starve | Cap `max_tokens` (600–800), refresh fat model; don’t add more models | **Medium** | -| **Stale after big decisions** | User finished a real architecture/product choice; MM still describes the old world | Wait until retain finished (next turn+); refresh `project-decisions` (and architecture if seams changed) | **Medium** — after the decision is retained | -| **Recurring same question** | Every session user asks the same durable “how do we X?” | One new MM for that **single** dimension, lean max_tokens | **Medium** | -| **Cross-project bleed** | Prefs or decisions clearly about another repo appear under this project’s inject | Fix tags (project vs bank-global); never “fix” by editing another project’s ids while working here | **High** if isolation broken | -| **Noise as “facts”** | Probe/bait/test harness text treated as durable prefs | Retain correction + refresh; mission ignore-list for probes; don’t import bait sessions | **Medium** | -| **Slightly imperfect prose** | Wording could be nicer but policy is right | Leave it; delta refresh after consolidation is enough | **Low** — do not nag | +| Signal | What you see | Likely fix | Priority | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| **Broken / empty content** | Inject or get shows `"#"`, empty body, “Generating…”, or only a title | `refresh`; if still empty: tags ⊆ retain tags; **prefs** MMs need `fact_types` world+experience+observation (not observation-only after clear); clear+full rebuild if needed | **High** — do this session | +| **Wrong durable policy** | Prefs MM contradicts the user’s stated preference (e.g. “never ask” vs wants high-signal questions) | Explicit `hindsight_retain` of the correct pref → then refresh prefs MM; tighten `source_query` if probe noise keeps winning | **High** | +| **Missing project starters** | Active `project:` has no architecture/conventions/decisions models; bank only has other projects’ models | Propose apply/create starters for **this** project (+ bank-global prefs if missing) | **High** on setup / first real work in repo | +| **Inject always truncated** | Blocks end mid-sentence; one model dominates (multi‑k chars, e.g. 8k–12k) while others starve | Cap `max_tokens` (600–800), refresh fat model; don’t add more models | **Medium** | +| **Stale after big decisions** | User finished a real architecture/product choice; MM still describes the old world | Wait until retain finished (next turn+); refresh `project-decisions` (and architecture if seams changed) | **Medium** — after the decision is retained | +| **Recurring same question** | Every session user asks the same durable “how do we X?” | One new MM for that **single** dimension, lean max_tokens | **Medium** | +| **Cross-project bleed** | Prefs or decisions clearly about another repo appear under this project’s inject | Fix tags (project vs bank-global); never “fix” by editing another project’s ids while working here | **High** if isolation broken | +| **Noise as “facts”** | Probe/bait/test harness text treated as durable prefs | Retain correction + refresh; mission ignore-list for probes; don’t import bait sessions | **Medium** | +| **Slightly imperfect prose** | Wording could be nicer but policy is right | Leave it; delta refresh after consolidation is enough | **Low** — do not nag | ### Mission signals (`hindsight_bank` get + memory quality over several sessions) diff --git a/docs/mission-and-mental-model-quality.md b/docs/mission-and-mental-model-quality.md index ee565eb5..7e6535c6 100644 --- a/docs/mission-and-mental-model-quality.md +++ b/docs/mission-and-mental-model-quality.md @@ -58,17 +58,17 @@ Optimize only when a **repeatable problem** shows up—not because content is im ### Mental-model signals (inspect inject + `hindsight_mental_model` list/get) -| Signal | What you see | Likely fix | Priority | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| **Broken / empty content** | Inject or get shows `"#"`, empty body, “Generating…”, or only a title | `refresh`; if still empty after retain has landed, check tags ⊆ retain tags; clear+refresh if delta-drifted | **High** — do this session | -| **Wrong durable policy** | Prefs MM contradicts the user’s stated preference (e.g. “never ask” vs wants high-signal questions) | Explicit `hindsight_retain` of the correct pref → then refresh prefs MM; tighten `source_query` if probe noise keeps winning | **High** | -| **Missing project starters** | Active `project:` has no architecture/conventions/decisions models; bank only has other projects’ models | Propose apply/create starters for **this** project (+ bank-global prefs if missing) | **High** on setup / first real work in repo | -| **Inject always truncated** | Blocks end mid-sentence; one model dominates (multi‑k chars, e.g. 8k–12k) while others starve | Cap `max_tokens` (600–800), refresh fat model; don’t add more models | **Medium** | -| **Stale after big decisions** | User finished a real architecture/product choice; MM still describes the old world | Wait until retain finished (next turn+); refresh `project-decisions` (and architecture if seams changed) | **Medium** — after the decision is retained | -| **Recurring same question** | Every session user asks the same durable “how do we X?” | One new MM for that **single** dimension, lean max_tokens | **Medium** | -| **Cross-project bleed** | Prefs or decisions clearly about another repo appear under this project’s inject | Fix tags (project vs bank-global); never “fix” by editing another project’s ids while working here | **High** if isolation broken | -| **Noise as “facts”** | Probe/bait/test harness text treated as durable prefs | Retain correction + refresh; mission ignore-list for probes; don’t import bait sessions | **Medium** | -| **Slightly imperfect prose** | Wording could be nicer but policy is right | Leave it; delta refresh after consolidation is enough | **Low** — do not nag | +| Signal | What you see | Likely fix | Priority | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| **Broken / empty content** | Inject or get shows `"#"`, empty body, “Generating…”, or only a title | `refresh`; if still empty: tags ⊆ retain tags; **prefs** MMs need `fact_types` world+experience+observation (not observation-only after clear); clear+full rebuild if needed | **High** — do this session | +| **Wrong durable policy** | Prefs MM contradicts the user’s stated preference (e.g. “never ask” vs wants high-signal questions) | Explicit `hindsight_retain` of the correct pref → then refresh prefs MM; tighten `source_query` if probe noise keeps winning | **High** | +| **Missing project starters** | Active `project:` has no architecture/conventions/decisions models; bank only has other projects’ models | Propose apply/create starters for **this** project (+ bank-global prefs if missing) | **High** on setup / first real work in repo | +| **Inject always truncated** | Blocks end mid-sentence; one model dominates (multi‑k chars, e.g. 8k–12k) while others starve | Cap `max_tokens` (600–800), refresh fat model; don’t add more models | **Medium** | +| **Stale after big decisions** | User finished a real architecture/product choice; MM still describes the old world | Wait until retain finished (next turn+); refresh `project-decisions` (and architecture if seams changed) | **Medium** — after the decision is retained | +| **Recurring same question** | Every session user asks the same durable “how do we X?” | One new MM for that **single** dimension, lean max_tokens | **Medium** | +| **Cross-project bleed** | Prefs or decisions clearly about another repo appear under this project’s inject | Fix tags (project vs bank-global); never “fix” by editing another project’s ids while working here | **High** if isolation broken | +| **Noise as “facts”** | Probe/bait/test harness text treated as durable prefs | Retain correction + refresh; mission ignore-list for probes; don’t import bait sessions | **Medium** | +| **Slightly imperfect prose** | Wording could be nicer but policy is right | Leave it; delta refresh after consolidation is enough | **Low** — do not nag | ### Mission signals (`hindsight_bank` get + memory quality over several sessions) diff --git a/extensions/banks/bank-templates.ts b/extensions/banks/bank-templates.ts index 0e538fda..33021d81 100644 --- a/extensions/banks/bank-templates.ts +++ b/extensions/banks/bank-templates.ts @@ -47,16 +47,25 @@ const RETAIN_COMPAT_TAGS = ["source:pi"] as const; /** * Coding-agent MM refresh defaults (oh-my-pi / Claude Code / Hindsight deep dive): - * delta folds new evidence; refresh after consolidation; observations-first; no MM→MM feedback. - * Template import sends the full trigger object; agent create is limited by the TS client mapping - * (refreshAfterConsolidation only) until the client maps mode/fact_types. + * delta folds new evidence; refresh after consolidation; no MM→MM feedback. + * Project MMs: observation-first (less probe/world noise). + * Prefs/user MMs: world+experience+observation — observation-only can rebuild empty after clear + * (live: coding-assistant-operating-preferences → "#"). + * Template import sends the full trigger; agent create only maps refreshAfterConsolidation today. */ -const DEFAULT_MM_TRIGGER: NonNullable = { +type MmTrigger = NonNullable; +const DEFAULT_MM_TRIGGER_PROJECT: MmTrigger = { mode: "delta", refresh_after_consolidation: true, fact_types: ["observation"], exclude_mental_models: true, }; +const DEFAULT_MM_TRIGGER_PREFS: MmTrigger = { + mode: "delta", + refresh_after_consolidation: true, + fact_types: ["world", "experience", "observation"], + exclude_mental_models: true, +}; /** Seed max_tokens: keep inject lean (AMB cost; inject shares mentalModels.maxChars). */ const DEFAULT_MM_MAX_TOKENS_PREFS = 600; @@ -71,7 +80,7 @@ const CODING_BANK_GLOBAL_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for how coding assistants should plan, verify, commit, use tools, and communicate across repositories? Especially capture clarification style: whether questions are welcome, when to ask (up front vs mid-task), and that questions should be high-signal (not answerable by the agent alone). Exclude one-off probe/bait/test session constraints such as temporary 'do not ask questions; just execute' rules. Capture only stable cross-project habits.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, ]; @@ -84,7 +93,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What are the stable architecture boundaries, modules, and seams in this project?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PROJECT, }, { id: "project-conventions", @@ -93,7 +102,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What are this project's conventions for code style, build, testing, release, and review? Only include conventions explicit in the project or repeatedly enforced.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PROJECT, }, { id: "project-decisions", @@ -102,7 +111,7 @@ const CODING_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable architectural or product decisions have been made for this project, and what rationale or trade-offs were recorded? Exclude transient plans and active task state.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PROJECT, }, ]; @@ -115,7 +124,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What goals, commitments, deadlines, and open loops is the user tracking in this context? Prefer durable ongoing items over one-off chat filler.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, { id: "people-and-context", @@ -124,7 +133,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "Which people, roles, relationships, and recurring situations matter here? Capture only durable context the assistant needs, not sensitive private details.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, { id: "decisions-and-preferences", @@ -133,7 +142,7 @@ const CONVERSATION_PROJECT_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable decisions, preferences, and constraints has the user stated for this conversation or life domain? Exclude one-off requests.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PROJECT, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, ]; @@ -145,7 +154,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for collaboration, review, autonomy, and communication?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, { id: "coding-assistant-operating-preferences", @@ -154,7 +163,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for how coding assistants should plan, verify, commit, and use tools?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, { id: "cross-project-workflow-habits", @@ -163,7 +172,7 @@ const CODING_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What workflow habits recur across the user's repositories, issue tracking, PR review, and release process?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, ]; @@ -175,7 +184,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What durable preferences has the user shown for tone, length, language, and how the assistant should respond?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, { id: "life-workflow-habits", @@ -184,7 +193,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "What recurring real-life workflows, planning habits, and task-management preferences does the user express across conversations?", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, { id: "priority-and-scheduling-preferences", @@ -193,7 +202,7 @@ const CONVERSATION_USER_MENTAL_MODELS: BankTemplateMentalModel[] = [ "How does the user prefer to prioritize, schedule, and trade off time across personal and work tasks? Capture durable patterns only.", tags: [...RETAIN_COMPAT_TAGS], max_tokens: DEFAULT_MM_MAX_TOKENS_PREFS, - trigger: DEFAULT_MM_TRIGGER, + trigger: DEFAULT_MM_TRIGGER_PREFS, }, ]; diff --git a/skills/hindsight-memory-doctor/SKILL.md b/skills/hindsight-memory-doctor/SKILL.md index 7add9726..9aca320e 100644 --- a/skills/hindsight-memory-doctor/SKILL.md +++ b/skills/hindsight-memory-doctor/SKILL.md @@ -14,7 +14,7 @@ description: > When the user is **upset or skeptical about memory**, behave like a careful human under conflict: **stop, inspect, question the memory stack**, then propose a small fix. Do not argue from broken inject. Do not mutate banks silently. -Authoritative criteria: `docs/mission-and-mental-model-quality.md` (packaged; also docs-site *Mission and mental-model quality*). +Authoritative criteria: `docs/mission-and-mental-model-quality.md` (packaged; also docs-site _Mission and mental-model quality_). ## Trigger (yes) @@ -39,7 +39,7 @@ Authoritative criteria: `docs/mission-and-mental-model-quality.md` (packaged; al - `hindsight_mental_model` action=get for broken/suspicious ids - `hindsight_bank` action=get for coding (and life if enabled) — missions 3. **Score against the quality guide** (priority order): - 1. Broken/empty MM content (`#`, empty, Generating…) + 1. Broken/empty MM content (`#`, empty, Generating…) — prefs MMs may need `fact_types` world+experience+observation, not observation-only 2. Prefs contradict the user 3. Missing starters for **this** `project:` 4. Fat inject / truncation diff --git a/tests/bank-templates.test.ts b/tests/bank-templates.test.ts index 298cf10a..8d2e3b61 100644 --- a/tests/bank-templates.test.ts +++ b/tests/bank-templates.test.ts @@ -79,14 +79,31 @@ describe("bank templates", () => { }); it("uses delta refresh defaults on bundled mental models", () => { + const prefsFactTypes = ["world", "experience", "observation"]; + const projectFactTypes = ["observation"]; for (const template of BUILT_IN_BANK_TEMPLATES) { for (const model of template.manifest.mental_models ?? []) { expect(model.trigger).toMatchObject({ mode: "delta", refresh_after_consolidation: true, - fact_types: ["observation"], exclude_mental_models: true, }); + const ft = model.trigger?.fact_types ?? []; + const isPrefs = + /prefer|habit|operating|communication|collaboration|priority|goals|people|decision/i.test( + model.id ?? "", + ) || model.max_tokens === 600; + if (isPrefs && !model.id?.startsWith("project-")) { + expect(ft).toEqual(prefsFactTypes); + } else if ( + model.id?.startsWith("project-architecture") || + model.id?.startsWith("project-conventions") || + model.id?.startsWith("project-decisions") + ) { + expect(ft).toEqual(projectFactTypes); + } else { + expect([prefsFactTypes, projectFactTypes]).toContainEqual(ft); + } expect(model.max_tokens).toBeLessThanOrEqual(800); } }