feat(mnemopi): exposed polyphonic and enhanced recall via config.yml
- Added `mnemopi.polyphonicRecall` and `mnemopi.enhancedRecall` boolean settings (default off) to `settings-schema.ts` and `loadMnemopiConfig`, applied via the new `configureRecallFeatures()` in `createScopedResources`. - Made `polyphonicRecallEnabled()`, `enhancedRecallEnabled()`, and `isEnhancedRecallEnabled()` fall back to the configured defaults while `MNEMOPI_POLYPHONIC_RECALL` / `MNEMOPI_ENHANCED_RECALL` env vars still win when set. - Exported `configureRecallFeatures`/`RecallFeatureFlags` from the package root and `core` barrels; documented the settings in `docs/mnemosyne-memory-backend.md`. - Added `recall-feature-flags.test.ts` covering defaults, config enablement, and env precedence; the mnemopi changelog hunk also carries the adjacent #2322 entry (same contiguous run). Fixes #2323: Mnemopi: MNEMOPI_POLYPHONIC_RECALL and MNEMOPI_ENHANCED_RECALL not configurable via config.yml
This commit is contained in:
@@ -38,6 +38,8 @@ Recalled memory is background context, not instructions. Current user messages a
|
||||
| `mnemopi.scoping` | `per-project` | Memory visibility mode: `global` = one shared bank, `per-project` = isolated project memory, `per-project-tagged` = project-local writes plus global recall visibility. |
|
||||
| `mnemopi.autoRecall` | `true` | Recall memory on the first turn of a session. |
|
||||
| `mnemopi.autoRetain` | `true` | Retain completed turns automatically. |
|
||||
| `mnemopi.polyphonicRecall` | `false` | Enable 4-voice polyphonic recall (vector, graph, fact, temporal) with reciprocal rank fusion; `MNEMOPI_POLYPHONIC_RECALL` overrides when set. |
|
||||
| `mnemopi.enhancedRecall` | `false` | Enable the tiered query result cache for repeated/similar recall queries; `MNEMOPI_ENHANCED_RECALL` overrides when set. |
|
||||
| `mnemopi.retainEveryNTurns` | `4` | Minimum user turns between automatic retain writes. |
|
||||
| `mnemopi.recallLimit` | `8` | Maximum recalled memories in the prompt block. |
|
||||
| `mnemopi.recallContextTurns` | `3` | Prior user-bounded turns included in recall queries. |
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- `ModelRegistry.resolver` now accepts a model directly — `resolver(model, sessionId)` — deriving `provider`, `baseUrl`, and `modelId` from it; all model-scoped call sites migrated from the verbose `resolver(model.provider, { sessionId, baseUrl, modelId })` form.
|
||||
- Added experimental `snapcompact.systemPrompt` and `snapcompact.toolResults` settings (off by default, `/settings` → Context → Experimental) that render the system prompt and large historical tool results as dense snapcompact PNG frames on vision-capable models to cut token cost. Frames are built per-request in the provider-context transform, cached across turns, capped by a per-provider image budget, and gated on a token-savings estimate — they never reach `session.jsonl`.
|
||||
- Added a Personality selector to `/settings` (Model → Prompt): `default` (the previous built-in reply style), `friendly`, `pragmatic`, or `none`. The selected spec renders into a dedicated `<personality>` system-prompt block (extracted from the former `<reply-guidelines>` section) and applies to the live session immediately; subagents always omit the block.
|
||||
- Added `mnemopi.polyphonicRecall` and `mnemopi.enhancedRecall` config.yml settings (off by default, `/settings` → Memory → Mnemopi) that enable the mnemopi 4-voice polyphonic recall engine and the tiered query result cache without environment variables; `MNEMOPI_POLYPHONIC_RECALL` / `MNEMOPI_ENHANCED_RECALL` still override the configured values when set ([#2323](https://github.com/can1357/oh-my-pi/issues/2323)).
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -1748,6 +1748,28 @@ export const SETTINGS_SCHEMA = {
|
||||
condition: "mnemopiActive",
|
||||
},
|
||||
},
|
||||
"mnemopi.polyphonicRecall": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
ui: {
|
||||
tab: "memory",
|
||||
group: "Mnemopi",
|
||||
label: "Mnemopi Polyphonic Recall",
|
||||
description: "Enable 4-voice recall (vector, graph, fact, temporal) fused with reciprocal rank fusion",
|
||||
condition: "mnemopiActive",
|
||||
},
|
||||
},
|
||||
"mnemopi.enhancedRecall": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
ui: {
|
||||
tab: "memory",
|
||||
group: "Mnemopi",
|
||||
label: "Mnemopi Enhanced Recall",
|
||||
description: "Enable the tiered query result cache for repeated and similar recall queries",
|
||||
condition: "mnemopiActive",
|
||||
},
|
||||
},
|
||||
"mnemopi.noEmbeddings": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface MnemopiBackendConfig {
|
||||
scoping?: MnemopiScoping;
|
||||
autoRecall: boolean;
|
||||
autoRetain: boolean;
|
||||
polyphonicRecall: boolean;
|
||||
enhancedRecall: boolean;
|
||||
retainEveryNTurns: number;
|
||||
recallLimit: number;
|
||||
recallContextTurns: number;
|
||||
@@ -52,6 +54,8 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
|
||||
scoping,
|
||||
autoRecall: settings.get("mnemopi.autoRecall"),
|
||||
autoRetain: settings.get("mnemopi.autoRetain"),
|
||||
polyphonicRecall: settings.get("mnemopi.polyphonicRecall"),
|
||||
enhancedRecall: settings.get("mnemopi.enhancedRecall"),
|
||||
retainEveryNTurns: Math.max(1, Math.floor(settings.get("mnemopi.retainEveryNTurns"))),
|
||||
recallLimit: Math.max(1, Math.floor(settings.get("mnemopi.recallLimit"))),
|
||||
recallContextTurns: Math.max(1, Math.floor(settings.get("mnemopi.recallContextTurns"))),
|
||||
|
||||
@@ -421,6 +421,12 @@ export class MnemopiSessionState {
|
||||
// `per-project-tagged` is implemented by opening both the project bank and the
|
||||
// shared bank, then merging recall results while keeping writes project-local.
|
||||
function createScopedResources(config: MnemopiBackendConfig): MnemopiScopedResources {
|
||||
// Env vars (MNEMOPI_POLYPHONIC_RECALL / MNEMOPI_ENHANCED_RECALL) still override
|
||||
// these config-driven defaults inside the core gates.
|
||||
requireMnemopi().configureRecallFeatures({
|
||||
polyphonicRecall: config.polyphonicRecall,
|
||||
enhancedRecall: config.enhancedRecall,
|
||||
});
|
||||
const banks = resolveScopedBanks(config);
|
||||
const memories = new Map<string, MnemopiScopedMemory>();
|
||||
const open = (bank: string): MnemopiScopedMemory => {
|
||||
|
||||
@@ -126,6 +126,8 @@ function makeMnemopiConfig(
|
||||
bank: "test-bank",
|
||||
autoRecall: true,
|
||||
autoRetain: true,
|
||||
polyphonicRecall: false,
|
||||
enhancedRecall: false,
|
||||
retainEveryNTurns: 3,
|
||||
recallLimit: 10,
|
||||
recallContextTurns: 1,
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `configureRecallFeatures()` (exported from the package root, `core`, and `config`) so hosts can enable the polyphonic recall engine and the enhanced recall query cache programmatically. `polyphonicRecallEnabled()`, `enhancedRecallEnabled()`, and `isEnhancedRecallEnabled()` now fall back to these configured defaults, with the `MNEMOPI_POLYPHONIC_RECALL` / `MNEMOPI_ENHANCED_RECALL` environment variables still taking precedence whenever they are set. ([#2323](https://github.com/can1357/oh-my-pi/issues/2323))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the embedding pipeline's silent `catch {}` blocks (`runEmbedding()`, `getLocalModel()`, and the local-model path of `embed()`) swallowing failures with zero diagnostics. These best-effort paths still degrade gracefully (return `null` / skip the write), but now emit structured `logger.debug` entries with the error and per-site context (item count, model name). The `mnemopi.debug` config flag now propagates into the core library via runtime options (`MnemopiOptions.debug` → `ResolvedMnemopiRuntimeOptions.debug`) and escalates these logs to `warn` so they surface at the default log level. ([#2322](https://github.com/can1357/oh-my-pi/issues/2322))
|
||||
|
||||
### Changed
|
||||
|
||||
- Extraction, embedding, and remote-LLM clients now accept an `ApiKey` (static string or resolver) and resolve it per request through `withAuth`, so 401s force-refresh and rotate credentials via the central auth-retry policy instead of failing with a stale key. Empty-key setups (local/proxy endpoints without `Authorization`) and pinned literal keys behave exactly as before.
|
||||
|
||||
@@ -248,8 +248,28 @@ export function proactiveLinkingEnabled(env: Env = process.env): boolean {
|
||||
return envString("MNEMOPI_PROACTIVE_LINKING", "0", env) === "1";
|
||||
}
|
||||
|
||||
export interface RecallFeatureFlags {
|
||||
polyphonicRecall?: boolean;
|
||||
enhancedRecall?: boolean;
|
||||
}
|
||||
|
||||
let polyphonicRecallDefault = false;
|
||||
let enhancedRecallDefault = false;
|
||||
|
||||
/**
|
||||
* Sets process-wide defaults for the env-gated recall features. Host configuration
|
||||
* (e.g. the coding-agent `mnemopi.polyphonicRecall` / `mnemopi.enhancedRecall`
|
||||
* settings) lands here; the `MNEMOPI_POLYPHONIC_RECALL` / `MNEMOPI_ENHANCED_RECALL`
|
||||
* environment variables still win whenever they are set.
|
||||
*/
|
||||
export function configureRecallFeatures(flags: RecallFeatureFlags): void {
|
||||
if (flags.polyphonicRecall !== undefined) polyphonicRecallDefault = flags.polyphonicRecall;
|
||||
if (flags.enhancedRecall !== undefined) enhancedRecallDefault = flags.enhancedRecall;
|
||||
}
|
||||
|
||||
export function polyphonicRecallEnabled(env: Env = process.env): boolean {
|
||||
return envString("MNEMOPI_POLYPHONIC_RECALL", "0", env) === "1";
|
||||
const value = envOptionalString("MNEMOPI_POLYPHONIC_RECALL", env);
|
||||
return value === undefined ? polyphonicRecallDefault : value === "1";
|
||||
}
|
||||
|
||||
export function temporalHalflifeHours(env: Env = process.env): number {
|
||||
@@ -257,7 +277,8 @@ export function temporalHalflifeHours(env: Env = process.env): number {
|
||||
}
|
||||
|
||||
export function enhancedRecallEnabled(env: Env = process.env): boolean {
|
||||
return envString("MNEMOPI_ENHANCED_RECALL", "0", env) === "1";
|
||||
const value = envOptionalString("MNEMOPI_ENHANCED_RECALL", env);
|
||||
return value === undefined ? enhancedRecallDefault : value === "1";
|
||||
}
|
||||
|
||||
export function llmEnabled(env: Env = process.env): boolean {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { configureRecallFeatures, type RecallFeatureFlags } from "../config";
|
||||
export * from "./banks";
|
||||
export * from "./beam/index";
|
||||
export * from "./memory";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { type Env, enhancedRecallEnabled } from "../config";
|
||||
import { cosineSimilarity } from "./vector-math";
|
||||
|
||||
export type QueryCacheResult = Record<string, unknown>;
|
||||
@@ -39,10 +40,8 @@ interface CacheRow {
|
||||
readonly results_json: string;
|
||||
}
|
||||
|
||||
type Env = Readonly<Record<string, string | undefined>>;
|
||||
|
||||
export function isEnhancedRecallEnabled(env: Env = process.env): boolean {
|
||||
return env.MNEMOPI_ENHANCED_RECALL === "1";
|
||||
return enhancedRecallEnabled(env);
|
||||
}
|
||||
|
||||
export function isQueryCacheEnabled(useCache = true, env: Env = process.env): boolean {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { configureRecallFeatures, type RecallFeatureFlags } from "./config";
|
||||
export * from "./core/beam/index";
|
||||
export * from "./core/embeddings";
|
||||
export * from "./core/llm-backends";
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { afterEach, describe, expect, it } from "bun:test";
|
||||
import { configureRecallFeatures, enhancedRecallEnabled, polyphonicRecallEnabled } from "@oh-my-pi/pi-mnemopi/config";
|
||||
import { polyphonicRecallIsEnabled } from "@oh-my-pi/pi-mnemopi/core/polyphonic-recall";
|
||||
import { isEnhancedRecallEnabled, isQueryCacheEnabled } from "@oh-my-pi/pi-mnemopi/core/query-cache";
|
||||
|
||||
afterEach(() => {
|
||||
configureRecallFeatures({ polyphonicRecall: false, enhancedRecall: false });
|
||||
});
|
||||
|
||||
describe("configureRecallFeatures", () => {
|
||||
it("keeps both recall gates off by default", () => {
|
||||
expect(polyphonicRecallEnabled({})).toBe(false);
|
||||
expect(enhancedRecallEnabled({})).toBe(false);
|
||||
expect(isEnhancedRecallEnabled({})).toBe(false);
|
||||
expect(isQueryCacheEnabled(true, {})).toBe(false);
|
||||
});
|
||||
|
||||
it("enables the gates from host configuration when the env vars are unset", () => {
|
||||
configureRecallFeatures({ polyphonicRecall: true, enhancedRecall: true });
|
||||
expect(polyphonicRecallEnabled({})).toBe(true);
|
||||
expect(polyphonicRecallIsEnabled({})).toBe(true);
|
||||
expect(enhancedRecallEnabled({})).toBe(true);
|
||||
expect(isEnhancedRecallEnabled({})).toBe(true);
|
||||
expect(isQueryCacheEnabled(true, {})).toBe(true);
|
||||
expect(isQueryCacheEnabled(false, {})).toBe(false);
|
||||
});
|
||||
|
||||
it("lets the env vars override the configured value in both directions", () => {
|
||||
configureRecallFeatures({ polyphonicRecall: true, enhancedRecall: true });
|
||||
expect(polyphonicRecallEnabled({ MNEMOPI_POLYPHONIC_RECALL: "0" })).toBe(false);
|
||||
expect(enhancedRecallEnabled({ MNEMOPI_ENHANCED_RECALL: "0" })).toBe(false);
|
||||
expect(isQueryCacheEnabled(true, { MNEMOPI_ENHANCED_RECALL: "0" })).toBe(false);
|
||||
|
||||
configureRecallFeatures({ polyphonicRecall: false, enhancedRecall: false });
|
||||
expect(polyphonicRecallEnabled({ MNEMOPI_POLYPHONIC_RECALL: "1" })).toBe(true);
|
||||
expect(enhancedRecallEnabled({ MNEMOPI_ENHANCED_RECALL: "1" })).toBe(true);
|
||||
expect(isQueryCacheEnabled(true, { MNEMOPI_ENHANCED_RECALL: "1" })).toBe(true);
|
||||
});
|
||||
|
||||
it("updates only the flags that are present", () => {
|
||||
configureRecallFeatures({ polyphonicRecall: true });
|
||||
expect(polyphonicRecallEnabled({})).toBe(true);
|
||||
expect(enhancedRecallEnabled({})).toBe(false);
|
||||
configureRecallFeatures({ enhancedRecall: true });
|
||||
expect(polyphonicRecallEnabled({})).toBe(true);
|
||||
expect(enhancedRecallEnabled({})).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user