om-memory-system 6.1.0-next.237 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config-template.d.ts +1 -1
- package/dist/config-template.js +3 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/core/memorisation-operations.d.ts +5 -4
- package/dist/core/memorisation-operations.js +22 -12
- package/dist/core/memorisation-settings.d.ts +2 -2
- package/dist/core/memorisation-settings.js +11 -2
- package/dist/core/memorisation.d.ts +6 -0
- package/dist/core/memorisation.js +10 -0
- package/dist/core/memory-operations.js +1 -1
- package/dist/services/client.js +8 -5
- package/dist/services/global-config-writer.js +3 -0
- package/dist/services/linked-search.d.ts +4 -0
- package/dist/services/memorisation-routes.d.ts +7 -6
- package/dist/services/memorisation-routes.js +18 -27
- package/dist/services/memorisation-store.d.ts +9 -7
- package/dist/services/memorisation-store.js +11 -7
- package/dist/services/reranker.d.ts +6 -0
- package/dist/services/reranker.js +12 -0
- package/dist/services/retrieval-rerank.d.ts +2 -0
- package/dist/services/retrieval-rerank.js +4 -1
- package/dist/services/settings-snapshot.js +2 -0
- package/dist/services/web-route-modules.js +1 -1
- package/dist/web/assets/{index-DBSeQOs6.js → index-jbZuE9HV.js} +32 -32
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* src/config.ts until that file reached its size ceiling; the text is
|
|
4
4
|
* unchanged. Shared by config.ts and the global settings writer.
|
|
5
5
|
*/
|
|
6
|
-
export declare const CONFIG_TEMPLATE = "{\n // ============================================\n // omms (Opinionated Modular Memory System) Configuration\n // ============================================\n \n // Storage location for the vector database. Leave unset to use the\n // default ~/.omms/data (a legacy ~/.opencode-mem/data store is migrated\n // there automatically on first start; see docs/omms-migration.md).\n // \"storagePath\": \"~/.omms/data\",\n\n \"userEmailOverride\": \"\",\n \"userNameOverride\": \"\",\n \n // ============================================\n // Embedding Model (for similarity search)\n // ============================================\n // Local = Hugging Face / ONNX via @huggingface/transformers (not Apple MLX).\n // Remote = set embeddingApiUrl (OpenAI-compatible /embeddings); embeddingApiKey is optional.\n \n // Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)\n \"embeddingModel\": \"Xenova/nomic-embed-text-v1\",\n\n // Opt-in Nomic task prefixes (search_document: / search_query:). After enabling,\n // re-index existing memories so store and query vectors stay aligned.\n // \"embeddingUseTaskPrefixes\": true,\n \n // Auto-detected dimensions (no need to set manually)\n // \"embeddingDimensions\": 768,\n \n // Other recommended local models:\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-base-en\", // 768 dims, English-only, 8192 context\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-small-en\", // 512 dims, faster, 8192 context\n // \"embeddingModel\": \"Xenova/all-MiniLM-L6-v2\", // 384 dims, very fast, 512 context\n // \"embeddingModel\": \"Xenova/all-mpnet-base-v2\", // 768 dims, good quality, 512 context\n \n // Optional: OpenAI-compatible API for embeddings (URL required; key only if the server needs one)\n // \"embeddingApiUrl\": \"https://api.openai.com/v1\",\n // \"embeddingApiKey\": \"env://OPENAI_API_KEY\", // or \"sk-...\" / \"file:///path/to/key\"\n // \"embeddingModel\": \"text-embedding-3-small\", // 1536 dims, auto-detected\n \n // ============================================\n // Web Server Settings\n // ============================================\n \n // Start a background import of past chats on each host's next start.\n \"autoBackfill\": true,\n // \"piBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // \"opencodeBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // Directory maps for history recorded in moved or deleted directories.\n // \"importPathMaps\": [{ \"from\": \"~/code/app-feat-x\", \"to\": \"~/code/app\" }],\n // Unresolved directories to hide on the Directory maps page. They stay unimported.\n // \"importIgnoredDirectories\": [\"/private/tmp/scratch\"],\n // Claude Code's folder, when it is not ~/.claude (like CLAUDE_CONFIG_DIR).\n // \"claudeConfigDir\": \"~/.claude-work\",\n\n // Register the web app to start when you log in.\n \"webServerAutoStart\": true,\n // Enable web UI for managing memories (accessible at http://localhost:4747)\n \"webServerEnabled\": true,\n \n // Port for web UI server\n \"webServerPort\": 4747,\n \n // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)\n \"webServerHost\": \"127.0.0.1\",\n\n // Optional HTTP Basic Auth for the web UI. Accepts literal, env://, or file:// secrets.\n // \"webServerAuthPassword\": \"\",\n // \"webServerAuthUsername\": \"\",\n\n // Deprecated: imported once into the API token table, then no longer read.\n // Create API tokens on the Settings page (Keys and access) instead.\n // \"webServerApiToken\": \"env://OMMS_WEB_TOKEN\",\n \n // ============================================\n // Database Settings\n // ============================================\n \n // Maximum vectors per database shard (auto-creates new shard when limit reached)\n \"maxVectorsPerShard\": 50000,\n \n // Automatically delete old memories based on retention period\n \"autoCleanupEnabled\": true,\n \n // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)\n \"autoCleanupRetentionDays\": 30,\n \n // Automatically detect and remove duplicate memories\n \"deduplicationEnabled\": true,\n \n // Similarity threshold (0-1) for detecting duplicates (higher = stricter)\n \"deduplicationSimilarityThreshold\": 0.90,\n \n // ============================================\n // Memory Scope Settings\n // ============================================\n\n // Default scope for memory list/search queries\n // \"project\" keeps queries within the current project, \"all-projects\" searches across all project shards\n \"memory\": {\n \"defaultScope\": \"project\"\n },\n\n // ============================================\n // Model for auto-capture and profile learning (same rule in OpenCode and Pi)\n // ============================================\n\n // Which model summarises your work, in this order:\n // 1. The host model below: opencodeProvider/opencodeModel in OpenCode,\n // piProvider/piModel in Pi. Set the model to \"inherit\" to follow\n // whatever model the session is using, or to \"external\" to send\n // every call to the external API below.\n // 2. If no host model is set: the external API (memoryModel/memoryApiUrl/\n // memoryApiKey further down).\n // 3. If neither is set: the session's own model.\n // If the host model fails and the external API is configured, the external\n // API is used instead.\n //\n // Host models go through OpenCode's or Pi's own sign-in (OAuth like Claude\n // Pro/Max, GitHub Copilot, ChatGPT, or your own key there), so no separate API\n // key is needed here.\n //\n // Examples (OpenCode names from 'opencode providers list'; Pi names from its model list):\n // \"opencodeProvider\": \"anthropic\", \"opencodeModel\": \"claude-haiku-4-5-20251001\"\n // \"opencodeProvider\": \"openai\", \"opencodeModel\": \"inherit\"\n // \"piProvider\": \"openai-codex\", \"piModel\": \"gpt-5.6-luna\"\n //\n // \"opencodeProvider\": \"anthropic\",\n // \"opencodeModel\": \"claude-haiku-4-5-20251001\",\n // \"piProvider\": \"openai-codex\",\n // \"piModel\": \"gpt-5.6-luna\",\n\n // ============================================\n // Auto-Capture Settings\n // ============================================\n \n // Auto-capture runs in the background without blocking your main session.\n // Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.\n \n \"autoCaptureEnabled\": true,\n \n // Provider type: \"openai-chat\" | \"openai-responses\" | \"anthropic\" | \"minimax\" | \"orcarouter\" | \"google-gemini\"\n // Note: \"openai-chat\" is a generic OpenAI API-compatible mode.\n // Any service that follows the OpenAI Chat Completions API can use it via custom \"memoryApiUrl\".\n \"memoryProvider\": \"openai-chat\",\n \n // External API (step 2 above, and the fallback when a host model fails).\n // Uncomment all 3 lines and replace memoryApiKey before use:\n // \"memoryModel\": \"gpt-4o-mini\",\n // \"memoryApiUrl\": \"https://api.openai.com/v1\",\n // \"memoryApiKey\": \"sk-...\",\n\n // API Key Formats:\n // Direct value: \"sk-...\"\n // From file: \"file://~/.config/litellm-key.txt\"\n // From env variable: \"env://LITELLM_API_KEY\"\n \n // Examples for different providers:\n // Any OpenAI-compatible endpoint can use the \"openai-chat\" provider pattern below.\n // Common examples: DeepSeek, Qwen (via Alibaba Cloud ModelStudio),\n // Zhipu GLM (BigModel platform), and Kimi (Moonshot AI platform).\n\n // OpenAI Chat Completion (default, backward compatible):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"gpt-4o-mini\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n\n // DeepSeek (OpenAI-compatible example):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"deepseek-chat\"\n // \"memoryApiUrl\": \"https://api.deepseek.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // OpenAI Responses API (recommended, with session support):\n // \"memoryProvider\": \"openai-responses\"\n // \"memoryModel\": \"gpt-4o\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // Anthropic (with session support):\n // \"memoryProvider\": \"anthropic\"\n // \"memoryModel\": \"claude-3-5-haiku-20241022\"\n // \"memoryApiUrl\": \"https://api.anthropic.com/v1\"\n // \"memoryApiKey\": \"sk-ant-...\"\n\n // MiniMax (Anthropic Messages-compatible endpoint, with session support):\n // \"memoryProvider\": \"minimax\"\n // \"memoryModel\": \"MiniMax-M3\"\n // \"memoryApiUrl\": \"https://api.minimax.io\" // global endpoint\n // \"memoryApiKey\": \"<MiniMax API key>\"\n // // China endpoint: \"memoryApiUrl\": \"https://api.minimaxi.com\"\n // // Optional adaptive thinking for MiniMax-M3:\n // \"memoryExtraParams\": { \"thinking\": { \"type\": \"adaptive\" } }\n\n // OrcaRouter (OpenAI-compatible gateway, namespaced model IDs, with session support):\n // \"memoryProvider\": \"orcarouter\"\n // \"memoryApiKey\": \"<OrcaRouter API key>\"\n // // memoryApiUrl and memoryModel are optional \u2014 they default to\n // // https://api.orcarouter.ai/v1 and \"orcarouter/auto\" (a routing alias).\n // // OrcaRouter rejects bare model names, so if you set memoryModel, use a\n // // namespaced ID such as \"openai/gpt-5.5\" or \"deepseek/deepseek-v4-flash\".\n // \"memoryModel\": \"openai/gpt-5.5\"\n\n // Groq (OpenAI-compatible, use openai-chat provider):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"llama-3.3-70b-versatile\"\n // \"memoryApiUrl\": \"https://api.groq.com/openai/v1\"\n // \"memoryApiKey\": \"gsk_...\"\n \n // Maximum iterations for multi-turn AI analysis (for openai-responses, anthropic, and minimax)\n \"autoCaptureMaxIterations\": 5,\n \n // Timeout per iteration in milliseconds (30 seconds default)\n \"autoCaptureIterationTimeout\": 30000,\n\n // Tries for one turn's capture, on Pi and OpenCode, 2 and 4 seconds apart. After the\n // last one fails, a turn the model could not reach goes to the capture retry queue.\n \"autoCaptureMaxRetries\": 3,\n\n // Maximum UTF-8 bytes for the auto-capture markdown context sent to the summary model.\n // Prevents HTTP 400 context overflows on models with ~131K token windows (e.g. Groq Llama).\n // Rough guide: tokens \u2248 bytes / 4 for mixed code/prose.\n \"autoCaptureMaxContextBytes\": 131072,\n \n // Days to keep AI session history before cleanup\n \"aiSessionRetentionDays\": 7,\n\n // Capture diagnostics: every capture attempt always writes one metadata line\n // (model, stop reason, sizes, outcome) to ~/.omms/omms.log, with no\n // conversation text. Set captureTrace to true to also write each attempt's\n // prompt and raw reply to ~/.omms/traces/ for debugging. Traces can contain\n // conversation content: <private> text and common API key formats are\n // redacted, files are readable only by you, and they are deleted after\n // captureTraceRetentionDays. Only this global file can turn tracing on.\n // \"captureTrace\": false,\n // \"captureTraceRetentionDays\": 7,\n // \"captureAttemptRetentionDays\": 30,\n\n // Skill learning: record each full turn (prompt, replies, tool calls with\n // their outcome, and the skills used) in ~/.omms/data/skills.db, so OMMS can\n // rank your skills and draft better versions. Off by default. A project config\n // can turn it off but never on. No model call is made to record a turn.\n // \"skillLearning\": false,\n // Remove <private> text and common API key formats before a turn is stored.\n // Global config only. Turning this off stores text as written.\n // \"skillLearningRedact\": true,\n // Folders OMMS reads skills from. Absolute or starting with ~/. Global config\n // only. Unset: ~/.agents/skills and the Claude Code folder's skills folder.\n // \"skillFolders\": [\"~/.agents/skills\"],\n\n // Visible recall: the OpenCode terminal TUI and Pi's interactive terminal draw\n // a row for each memory recall. On by default. A project config can turn it\n // off but never on.\n // \"visibleRecall\": true,\n\n // Memory injection, on OpenCode V2, Pi and Claude Code. Each prompt gets the\n // memories that match it. openingContext also searches memory with\n // openingContextQuery and adds the matches to the\n // first message of a new session. The query is global only, trimmed, and\n // 500 characters at most. subagentMemory gives each subagent the memories\n // that match its task. For both switches, a project config can only turn these off.\n // \"openingContext\": false,\n // \"openingContextQuery\": \"project architecture, setup, conventions and key decisions\",\n // \"subagentMemory\": true,\n\n // Capture retry queue: when a live capture fails because the model cannot be\n // reached (network error, timeout, HTTP 408, 429 or 5xx), OMMS keeps a copy of\n // the turn in ~/.omms/data and tries it again later. The copy has <private>\n // text and common API key formats removed. Turns are deleted when the retry\n // ends or after captureRetryRetentionHours (0 to 720). 0 turns the queue off.\n // Only this global file can set it.\n // \"captureRetryRetentionHours\": 72,\n\n // Temperature for AI API requests (set to false to omit parameter for models that don't support it)\n // Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter\n // Set to false and add \"memoryTemperature\": false in config when using such models\n \"memoryTemperature\": 0.3,\n\n // Extra parameters to include in API request body\n // Useful for local inference servers (e.g. llama-server with --jinja) that support\n // additional parameters like disabling thinking/reasoning mode\n // Example for Qwen3 models: { \"enable_thinking\": false }\n // \"memoryExtraParams\": {},\n\n // Language for auto-capture summaries (default: \"auto\" for auto-detection)\n // Options: \"auto\", \"en\", \"id\", \"zh\", \"ja\", \"es\", \"fr\", \"de\", \"ru\", \"pt\", \"ar\", \"ko\"\n // \"autoCaptureLanguage\": \"auto\",\n\n // ============================================\n // Toast Notifications\n // ============================================\n\n // Show toast when memory is auto-captured\n \"showAutoCaptureToasts\": true,\n\n // Show toast when user profile is updated\n \"showUserProfileToasts\": true,\n\n // Show toast for error messages\n \"showErrorToasts\": true,\n\n // ============================================\n // User Profile System\n // ============================================\n\n // Analyze user prompts every N prompts to build/update your user profile\n // When N uncaptured prompts accumulate, AI will analyze them to identify:\n // - User preferences (code style, communication style, tool preferences)\n // - User patterns (recurring topics, problem domains, technical interests)\n // - User workflows (development habits, sequences, learning style)\n // - Skill level (overall and per-domain assessment)\n \"userProfileAnalysisInterval\": 10,\n\n // Days before inactive items (all types) are eligible for removal\n \"userProfileStaleDays\": 2,\n\n // Days before an inactive workflow is eligible for removal. A workflow seen\n // userProfileMinEvidenceForRetention times or more is kept.\n \"userProfileWorkflowStaleDays\": 30,\n\n // Number of preferences shown in UI\n \"userProfileDisplayPreferences\": 20,\n \n // Number of patterns shown in UI\n \"userProfileDisplayPatterns\": 15,\n \n // Number of workflows shown in UI\n \"userProfileDisplayWorkflows\": 10,\n \n // Number of preferences injected into LLM conversation context\n // Keep this small \u2014 the strongest signals are enough; more dilute LLM attention\n \"userProfileInjectPreferences\": 5,\n \n // Number of patterns injected into LLM conversation context\n \"userProfileInjectPatterns\": 5,\n \n // Number of workflows injected into LLM conversation context\n \"userProfileInjectWorkflows\": 3,\n \n // Days before preference confidence starts to decay (if not reinforced)\n // Preferences that aren't seen again will gradually lose confidence and be removed\n \"userProfileConfidenceDecayDays\": 30,\n \n // Number of profile versions to keep in changelog (for rollback/debugging)\n // Older versions are automatically cleaned up\n \"userProfileChangelogRetentionCount\": 5,\n\n // Minimum evidence count for a preference/pattern to survive confidence decay\n // Items confirmed fewer times are more likely to be pruned when confidence decays\n \"userProfileMinEvidenceForRetention\": 3,\n\n // Merge duplicate or vague profile items every N analysed prompts (about 1 model call per 100 items)\n \"userProfileAutoCleanupEnabled\": true,\n \"userProfileAutoCleanupInterval\": 500,\n\n // Enable LLM validation of existing preferences against recent behavior.\n // When enabled, each analysis round checks if top-5 preferences still match recent prompts.\n // Experimental \u2014 disabled by default.\n \"userProfileValidationEnabled\": false,\n\n // ============================================\n // Search Settings\n // ============================================\n \n // Minimum similarity score (0-1) for memory search results. Leave unset to\n // follow the default for your embedding model: 0.5 for qwen3-embedding:0.6b\n // (the only model measured), 0.6 for every other embedder.\n // \"similarityThreshold\": 0.5,\n\n // Maximum number of memories to return in search results.\n // Manual searches can ask for fewer; prompt retrieval uses this ceiling.\n \"maxMemories\": 10,\n\n // ============================================\n // Memory Context Controls\n // ============================================\n\n // Per-prompt memory retrieval on every host. \"enabled\": false turns it off;\n // \"excludeCurrentSession\" leaves out memories captured in the current session.\n \"chatMessage\": {\n \"enabled\": true,\n \"excludeCurrentSession\": true,\n },\n\n // OpenCode profile-learning input limit, in UTF-8 bytes. Smaller values can\n // omit prompts from that input. Does not limit Pi or Claude Code profile input.\n \"userProfileMaxContextBytes\": 32768,\n\n // Shared budget for automatic memory context (profile text, memories, and\n // formatting) across all hosts, in approximate tokens: ceil(UTF-8 bytes / 4).\n // Covers prompt retrieval, recent-memory injection, and session-memory\n // restoration. Not an exact provider token limit or a spending limit.\n \"retrievalMaxTokens\": 2000,\n\n // Memorisation settings (global config only).\n // Repeat for a session snapshot: \"add\" or \"replace\" (default \"add\").\n // \"memorisationRepeat\": \"add\",\n // Maximum stored text: 100 to 20,000 characters or 25 to 5,000 tokens.\n // Tokens are estimated with the same rule as retrievalMaxTokens.\n // \"memorisationMaxSize\": 2000,\n // \"memorisationSizeUnit\": \"characters\", // or \"tokens\"\n\n // Restore a session's own memories after compaction and after resume, on\n // OpenCode V2, Pi and Claude Code. memoryLimit is the most memories to\n // restore. A project config can turn it on or off.\n // \"compaction\": { \"enabled\": true, \"memoryLimit\": 10 },\n\n // ============================================\n // Advanced Settings\n // ============================================\n \n // Inject user profile into AI context (preferences, patterns, workflows)\n \"injectProfile\": true\n}\n";
|
|
6
|
+
export declare const CONFIG_TEMPLATE = "{\n // ============================================\n // omms (Opinionated Modular Memory System) Configuration\n // ============================================\n \n // Storage location for the vector database. Leave unset to use the\n // default ~/.omms/data (a legacy ~/.opencode-mem/data store is migrated\n // there automatically on first start; see docs/omms-migration.md).\n // \"storagePath\": \"~/.omms/data\",\n\n \"userEmailOverride\": \"\",\n \"userNameOverride\": \"\",\n \n // ============================================\n // Embedding Model (for similarity search)\n // ============================================\n // Local = Hugging Face / ONNX via @huggingface/transformers (not Apple MLX).\n // Remote = set embeddingApiUrl (OpenAI-compatible /embeddings); embeddingApiKey is optional.\n \n // Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)\n \"embeddingModel\": \"Xenova/nomic-embed-text-v1\",\n\n // Opt-in Nomic task prefixes (search_document: / search_query:). After enabling,\n // re-index existing memories so store and query vectors stay aligned.\n // \"embeddingUseTaskPrefixes\": true,\n \n // Auto-detected dimensions (no need to set manually)\n // \"embeddingDimensions\": 768,\n \n // Other recommended local models:\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-base-en\", // 768 dims, English-only, 8192 context\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-small-en\", // 512 dims, faster, 8192 context\n // \"embeddingModel\": \"Xenova/all-MiniLM-L6-v2\", // 384 dims, very fast, 512 context\n // \"embeddingModel\": \"Xenova/all-mpnet-base-v2\", // 768 dims, good quality, 512 context\n \n // Optional: OpenAI-compatible API for embeddings (URL required; key only if the server needs one)\n // \"embeddingApiUrl\": \"https://api.openai.com/v1\",\n // \"embeddingApiKey\": \"env://OPENAI_API_KEY\", // or \"sk-...\" / \"file:///path/to/key\"\n // \"embeddingModel\": \"text-embedding-3-small\", // 1536 dims, auto-detected\n \n // ============================================\n // Web Server Settings\n // ============================================\n \n // Start a background import of past chats on each host's next start.\n \"autoBackfill\": true,\n // \"piBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // \"opencodeBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // Directory maps for history recorded in moved or deleted directories.\n // \"importPathMaps\": [{ \"from\": \"~/code/app-feat-x\", \"to\": \"~/code/app\" }],\n // Unresolved directories to hide on the Directory maps page. They stay unimported.\n // \"importIgnoredDirectories\": [\"/private/tmp/scratch\"],\n // Claude Code's folder, when it is not ~/.claude (like CLAUDE_CONFIG_DIR).\n // \"claudeConfigDir\": \"~/.claude-work\",\n\n // Register the web app to start when you log in.\n \"webServerAutoStart\": true,\n // Enable web UI for managing memories (accessible at http://localhost:4747)\n \"webServerEnabled\": true,\n \n // Port for web UI server\n \"webServerPort\": 4747,\n \n // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)\n \"webServerHost\": \"127.0.0.1\",\n\n // Optional HTTP Basic Auth for the web UI. Accepts literal, env://, or file:// secrets.\n // \"webServerAuthPassword\": \"\",\n // \"webServerAuthUsername\": \"\",\n\n // Deprecated: imported once into the API token table, then no longer read.\n // Create API tokens on the Settings page (Keys and access) instead.\n // \"webServerApiToken\": \"env://OMMS_WEB_TOKEN\",\n \n // ============================================\n // Database Settings\n // ============================================\n \n // Maximum vectors per database shard (auto-creates new shard when limit reached)\n \"maxVectorsPerShard\": 50000,\n \n // Automatically delete old memories based on retention period\n \"autoCleanupEnabled\": true,\n \n // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)\n \"autoCleanupRetentionDays\": 30,\n \n // Automatically detect and remove duplicate memories\n \"deduplicationEnabled\": true,\n \n // Similarity threshold (0-1) for detecting duplicates (higher = stricter)\n \"deduplicationSimilarityThreshold\": 0.90,\n \n // ============================================\n // Memory Scope Settings\n // ============================================\n\n // Default scope for memory list/search queries\n // \"project\" keeps queries within the current project, \"all-projects\" searches across all project shards\n \"memory\": {\n \"defaultScope\": \"project\"\n },\n\n // ============================================\n // Model for auto-capture and profile learning (same rule in OpenCode and Pi)\n // ============================================\n\n // Which model summarises your work, in this order:\n // 1. The host model below: opencodeProvider/opencodeModel in OpenCode,\n // piProvider/piModel in Pi. Set the model to \"inherit\" to follow\n // whatever model the session is using, or to \"external\" to send\n // every call to the external API below.\n // 2. If no host model is set: the external API (memoryModel/memoryApiUrl/\n // memoryApiKey further down).\n // 3. If neither is set: the session's own model.\n // If the host model fails and the external API is configured, the external\n // API is used instead.\n //\n // Host models go through OpenCode's or Pi's own sign-in (OAuth like Claude\n // Pro/Max, GitHub Copilot, ChatGPT, or your own key there), so no separate API\n // key is needed here.\n //\n // Examples (OpenCode names from 'opencode providers list'; Pi names from its model list):\n // \"opencodeProvider\": \"anthropic\", \"opencodeModel\": \"claude-haiku-4-5-20251001\"\n // \"opencodeProvider\": \"openai\", \"opencodeModel\": \"inherit\"\n // \"piProvider\": \"openai-codex\", \"piModel\": \"gpt-5.6-luna\"\n //\n // \"opencodeProvider\": \"anthropic\",\n // \"opencodeModel\": \"claude-haiku-4-5-20251001\",\n // \"piProvider\": \"openai-codex\",\n // \"piModel\": \"gpt-5.6-luna\",\n\n // ============================================\n // Auto-Capture Settings\n // ============================================\n \n // Auto-capture runs in the background without blocking your main session.\n // Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.\n \n \"autoCaptureEnabled\": true,\n \n // Provider type: \"openai-chat\" | \"openai-responses\" | \"anthropic\" | \"minimax\" | \"orcarouter\" | \"google-gemini\"\n // Note: \"openai-chat\" is a generic OpenAI API-compatible mode.\n // Any service that follows the OpenAI Chat Completions API can use it via custom \"memoryApiUrl\".\n \"memoryProvider\": \"openai-chat\",\n \n // External API (step 2 above, and the fallback when a host model fails).\n // Uncomment all 3 lines and replace memoryApiKey before use:\n // \"memoryModel\": \"gpt-4o-mini\",\n // \"memoryApiUrl\": \"https://api.openai.com/v1\",\n // \"memoryApiKey\": \"sk-...\",\n\n // API Key Formats:\n // Direct value: \"sk-...\"\n // From file: \"file://~/.config/litellm-key.txt\"\n // From env variable: \"env://LITELLM_API_KEY\"\n \n // Examples for different providers:\n // Any OpenAI-compatible endpoint can use the \"openai-chat\" provider pattern below.\n // Common examples: DeepSeek, Qwen (via Alibaba Cloud ModelStudio),\n // Zhipu GLM (BigModel platform), and Kimi (Moonshot AI platform).\n\n // OpenAI Chat Completion (default, backward compatible):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"gpt-4o-mini\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n\n // DeepSeek (OpenAI-compatible example):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"deepseek-chat\"\n // \"memoryApiUrl\": \"https://api.deepseek.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // OpenAI Responses API (recommended, with session support):\n // \"memoryProvider\": \"openai-responses\"\n // \"memoryModel\": \"gpt-4o\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // Anthropic (with session support):\n // \"memoryProvider\": \"anthropic\"\n // \"memoryModel\": \"claude-3-5-haiku-20241022\"\n // \"memoryApiUrl\": \"https://api.anthropic.com/v1\"\n // \"memoryApiKey\": \"sk-ant-...\"\n\n // MiniMax (Anthropic Messages-compatible endpoint, with session support):\n // \"memoryProvider\": \"minimax\"\n // \"memoryModel\": \"MiniMax-M3\"\n // \"memoryApiUrl\": \"https://api.minimax.io\" // global endpoint\n // \"memoryApiKey\": \"<MiniMax API key>\"\n // // China endpoint: \"memoryApiUrl\": \"https://api.minimaxi.com\"\n // // Optional adaptive thinking for MiniMax-M3:\n // \"memoryExtraParams\": { \"thinking\": { \"type\": \"adaptive\" } }\n\n // OrcaRouter (OpenAI-compatible gateway, namespaced model IDs, with session support):\n // \"memoryProvider\": \"orcarouter\"\n // \"memoryApiKey\": \"<OrcaRouter API key>\"\n // // memoryApiUrl and memoryModel are optional \u2014 they default to\n // // https://api.orcarouter.ai/v1 and \"orcarouter/auto\" (a routing alias).\n // // OrcaRouter rejects bare model names, so if you set memoryModel, use a\n // // namespaced ID such as \"openai/gpt-5.5\" or \"deepseek/deepseek-v4-flash\".\n // \"memoryModel\": \"openai/gpt-5.5\"\n\n // Groq (OpenAI-compatible, use openai-chat provider):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"llama-3.3-70b-versatile\"\n // \"memoryApiUrl\": \"https://api.groq.com/openai/v1\"\n // \"memoryApiKey\": \"gsk_...\"\n \n // Maximum iterations for multi-turn AI analysis (for openai-responses, anthropic, and minimax)\n \"autoCaptureMaxIterations\": 5,\n \n // Timeout per iteration in milliseconds (30 seconds default)\n \"autoCaptureIterationTimeout\": 30000,\n\n // Tries for one turn's capture, on Pi and OpenCode, 2 and 4 seconds apart. After the\n // last one fails, a turn the model could not reach goes to the capture retry queue.\n \"autoCaptureMaxRetries\": 3,\n\n // Maximum UTF-8 bytes for the auto-capture markdown context sent to the summary model.\n // Prevents HTTP 400 context overflows on models with ~131K token windows (e.g. Groq Llama).\n // Rough guide: tokens \u2248 bytes / 4 for mixed code/prose.\n \"autoCaptureMaxContextBytes\": 131072,\n \n // Days to keep AI session history before cleanup\n \"aiSessionRetentionDays\": 7,\n\n // Capture diagnostics: every capture attempt always writes one metadata line\n // (model, stop reason, sizes, outcome) to ~/.omms/omms.log, with no\n // conversation text. Set captureTrace to true to also write each attempt's\n // prompt and raw reply to ~/.omms/traces/ for debugging. Traces can contain\n // conversation content: <private> text and common API key formats are\n // redacted, files are readable only by you, and they are deleted after\n // captureTraceRetentionDays. Only this global file can turn tracing on.\n // \"captureTrace\": false,\n // \"captureTraceRetentionDays\": 7,\n // \"captureAttemptRetentionDays\": 30,\n\n // Skill learning: record each full turn (prompt, replies, tool calls with\n // their outcome, and the skills used) in ~/.omms/data/skills.db, so OMMS can\n // rank your skills and draft better versions. Off by default. A project config\n // can turn it off but never on. No model call is made to record a turn.\n // \"skillLearning\": false,\n // Remove <private> text and common API key formats before a turn is stored.\n // Global config only. Turning this off stores text as written.\n // \"skillLearningRedact\": true,\n // Folders OMMS reads skills from. Absolute or starting with ~/. Global config\n // only. Unset: ~/.agents/skills and the Claude Code folder's skills folder.\n // \"skillFolders\": [\"~/.agents/skills\"],\n\n // Visible recall: the OpenCode terminal TUI and Pi's interactive terminal draw\n // a row for each memory recall. On by default. A project config can turn it\n // off but never on.\n // \"visibleRecall\": true,\n\n // Memory injection, on OpenCode V2, Pi and Claude Code. Each prompt gets the\n // memories that match it. openingContext also searches memory with\n // openingContextQuery and adds the matches to the\n // first message of a new session. The query is global only, trimmed, and\n // 500 characters at most. subagentMemory gives each subagent the memories\n // that match its task. For both switches, a project config can only turn these off.\n // \"openingContext\": false,\n // \"openingContextQuery\": \"project architecture, setup, conventions and key decisions\",\n // \"subagentMemory\": true,\n\n // Capture retry queue: when a live capture fails because the model cannot be\n // reached (network error, timeout, HTTP 408, 429 or 5xx), OMMS keeps a copy of\n // the turn in ~/.omms/data and tries it again later. The copy has <private>\n // text and common API key formats removed. Turns are deleted when the retry\n // ends or after captureRetryRetentionHours (0 to 720). 0 turns the queue off.\n // Only this global file can set it.\n // \"captureRetryRetentionHours\": 72,\n\n // Temperature for AI API requests (set to false to omit parameter for models that don't support it)\n // Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter\n // Set to false and add \"memoryTemperature\": false in config when using such models\n \"memoryTemperature\": 0.3,\n\n // Extra parameters to include in API request body\n // Useful for local inference servers (e.g. llama-server with --jinja) that support\n // additional parameters like disabling thinking/reasoning mode\n // Example for Qwen3 models: { \"enable_thinking\": false }\n // \"memoryExtraParams\": {},\n\n // Language for auto-capture summaries (default: \"auto\" for auto-detection)\n // Options: \"auto\", \"en\", \"id\", \"zh\", \"ja\", \"es\", \"fr\", \"de\", \"ru\", \"pt\", \"ar\", \"ko\"\n // \"autoCaptureLanguage\": \"auto\",\n\n // ============================================\n // Toast Notifications\n // ============================================\n\n // Show toast when memory is auto-captured\n \"showAutoCaptureToasts\": true,\n\n // Show toast when user profile is updated\n \"showUserProfileToasts\": true,\n\n // Show toast for error messages\n \"showErrorToasts\": true,\n\n // ============================================\n // User Profile System\n // ============================================\n\n // Analyze user prompts every N prompts to build/update your user profile\n // When N uncaptured prompts accumulate, AI will analyze them to identify:\n // - User preferences (code style, communication style, tool preferences)\n // - User patterns (recurring topics, problem domains, technical interests)\n // - User workflows (development habits, sequences, learning style)\n // - Skill level (overall and per-domain assessment)\n \"userProfileAnalysisInterval\": 10,\n\n // Days before inactive items (all types) are eligible for removal\n \"userProfileStaleDays\": 2,\n\n // Days before an inactive workflow is eligible for removal. A workflow seen\n // userProfileMinEvidenceForRetention times or more is kept.\n \"userProfileWorkflowStaleDays\": 30,\n\n // Number of preferences shown in UI\n \"userProfileDisplayPreferences\": 20,\n \n // Number of patterns shown in UI\n \"userProfileDisplayPatterns\": 15,\n \n // Number of workflows shown in UI\n \"userProfileDisplayWorkflows\": 10,\n \n // Number of preferences injected into LLM conversation context\n // Keep this small \u2014 the strongest signals are enough; more dilute LLM attention\n \"userProfileInjectPreferences\": 5,\n \n // Number of patterns injected into LLM conversation context\n \"userProfileInjectPatterns\": 5,\n \n // Number of workflows injected into LLM conversation context\n \"userProfileInjectWorkflows\": 3,\n \n // Days before preference confidence starts to decay (if not reinforced)\n // Preferences that aren't seen again will gradually lose confidence and be removed\n \"userProfileConfidenceDecayDays\": 30,\n \n // Number of profile versions to keep in changelog (for rollback/debugging)\n // Older versions are automatically cleaned up\n \"userProfileChangelogRetentionCount\": 5,\n\n // Minimum evidence count for a preference/pattern to survive confidence decay\n // Items confirmed fewer times are more likely to be pruned when confidence decays\n \"userProfileMinEvidenceForRetention\": 3,\n\n // Merge duplicate or vague profile items every N analysed prompts (about 1 model call per 100 items)\n \"userProfileAutoCleanupEnabled\": true,\n \"userProfileAutoCleanupInterval\": 500,\n\n // Enable LLM validation of existing preferences against recent behavior.\n // When enabled, each analysis round checks if top-5 preferences still match recent prompts.\n // Experimental \u2014 disabled by default.\n \"userProfileValidationEnabled\": false,\n\n // ============================================\n // Search Settings\n // ============================================\n \n // Minimum similarity score (0-1) for memory search results. Leave unset to\n // follow the default for your embedding model: 0.5 for qwen3-embedding:0.6b\n // (the only model measured), 0.6 for every other embedder.\n // \"similarityThreshold\": 0.5,\n\n // Maximum number of memories to return in search results.\n // Manual searches can ask for fewer; prompt retrieval uses this ceiling.\n \"maxMemories\": 10,\n\n // ============================================\n // Memory Context Controls\n // ============================================\n\n // Per-prompt memory retrieval on every host. \"enabled\": false turns it off;\n // \"excludeCurrentSession\" leaves out memories captured in the current session.\n \"chatMessage\": {\n \"enabled\": true,\n \"excludeCurrentSession\": true,\n },\n\n // OpenCode profile-learning input limit, in UTF-8 bytes. Smaller values can\n // omit prompts from that input. Does not limit Pi or Claude Code profile input.\n \"userProfileMaxContextBytes\": 32768,\n\n // Shared budget for automatic memory context (profile text, memories, and\n // formatting) across all hosts, in approximate tokens: ceil(UTF-8 bytes / 4).\n // Covers prompt retrieval, recent-memory injection, and session-memory\n // restoration. Not an exact provider token limit or a spending limit.\n \"retrievalMaxTokens\": 2000,\n\n // Memorisation settings (global config only).\n // Repeat for a session snapshot: \"add\" or \"replace\" (default \"add\").\n // \"memorisationRepeat\": \"add\",\n // Maximum stored text: 100 to 20,000 characters or 25 to 5,000 tokens.\n // Tokens are estimated with the same rule as retrievalMaxTokens.\n // \"memorisationMaxSize\": 2000,\n // \"memorisationSizeUnit\": \"characters\", // or \"tokens\"\n // Experimental: rerank text recall with the reranker server of the Memory\n // settings (needs its URL and model; default false).\n // \"memorisationRerank\": false,\n\n // Restore a session's own memories after compaction and after resume, on\n // OpenCode V2, Pi and Claude Code. memoryLimit is the most memories to\n // restore. A project config can turn it on or off.\n // \"compaction\": { \"enabled\": true, \"memoryLimit\": 10 },\n\n // ============================================\n // Advanced Settings\n // ============================================\n \n // Inject user profile into AI context (preferences, patterns, workflows)\n \"injectProfile\": true\n}\n";
|
package/dist/config-template.js
CHANGED
|
@@ -405,6 +405,9 @@ export const CONFIG_TEMPLATE = `{
|
|
|
405
405
|
// Tokens are estimated with the same rule as retrievalMaxTokens.
|
|
406
406
|
// "memorisationMaxSize": 2000,
|
|
407
407
|
// "memorisationSizeUnit": "characters", // or "tokens"
|
|
408
|
+
// Experimental: rerank text recall with the reranker server of the Memory
|
|
409
|
+
// settings (needs its URL and model; default false).
|
|
410
|
+
// "memorisationRerank": false,
|
|
408
411
|
|
|
409
412
|
// Restore a session's own memories after compaction and after resume, on
|
|
410
413
|
// OpenCode V2, Pi and Claude Code. memoryLimit is the most memories to
|
package/dist/config.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ interface OmmsConfig {
|
|
|
33
33
|
memorisationMaxSize?: number;
|
|
34
34
|
memorisationSizeUnit?: memorisation.MemorisationSettings["memorisationSizeUnit"];
|
|
35
35
|
memorisationRepeat?: memorisation.MemorisationSettings["memorisationRepeat"];
|
|
36
|
+
/** Experimental: rerank text recall with the reranker server. Global config only. */
|
|
37
|
+
memorisationRerank?: boolean;
|
|
36
38
|
maxProfileItems?: number;
|
|
37
39
|
injectProfile?: boolean;
|
|
38
40
|
containerTagPrefix?: string;
|
|
@@ -223,6 +225,7 @@ declare function buildConfig(fileConfig: OmmsConfig): {
|
|
|
223
225
|
memorisationMaxSize: number;
|
|
224
226
|
memorisationSizeUnit: import("./core/memorisation.js").MemorisationSizeUnit;
|
|
225
227
|
memorisationRepeat: import("./core/memorisation.js").MemorisationRepeat;
|
|
228
|
+
memorisationRerank: boolean;
|
|
226
229
|
storagePath: string;
|
|
227
230
|
/** The global `storagePath` when a project moved `storagePath`; `skills.db` stays there. */
|
|
228
231
|
skillStoragePath: string | undefined;
|
|
@@ -337,6 +340,7 @@ export declare let CONFIG: {
|
|
|
337
340
|
memorisationMaxSize: number;
|
|
338
341
|
memorisationSizeUnit: import("./core/memorisation.js").MemorisationSizeUnit;
|
|
339
342
|
memorisationRepeat: import("./core/memorisation.js").MemorisationRepeat;
|
|
343
|
+
memorisationRerank: boolean;
|
|
340
344
|
storagePath: string;
|
|
341
345
|
/** The global `storagePath` when a project moved `storagePath`; `skills.db` stays there. */
|
|
342
346
|
skillStoragePath: string | undefined;
|
package/dist/config.js
CHANGED
|
@@ -41,6 +41,7 @@ const DEFAULTS = {
|
|
|
41
41
|
memorisationMaxSize: 2000,
|
|
42
42
|
memorisationSizeUnit: "characters",
|
|
43
43
|
memorisationRepeat: "add",
|
|
44
|
+
memorisationRerank: false,
|
|
44
45
|
maxProfileItems: 5,
|
|
45
46
|
injectProfile: true,
|
|
46
47
|
// omms since the tag prefix migration: new memories carry the fork's
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type MemoryScope } from "../services/client.js";
|
|
2
1
|
import type { LinkedSearchSettings } from "../services/linked-search.js";
|
|
3
2
|
import type { getTags } from "../services/tags.js";
|
|
4
3
|
import type { MemoryHost } from "../types/index.js";
|
|
@@ -16,7 +15,6 @@ export interface RecallArgs {
|
|
|
16
15
|
/** Comma-separated labels. Only memorisations that carry all of them match. */
|
|
17
16
|
tags?: string;
|
|
18
17
|
limit?: number;
|
|
19
|
-
scope?: MemoryScope;
|
|
20
18
|
}
|
|
21
19
|
interface MemorisationContext {
|
|
22
20
|
host?: MemoryHost;
|
|
@@ -27,7 +25,10 @@ export declare function runMemorise(args: MemoriseArgs, context: MemorisationCon
|
|
|
27
25
|
/**
|
|
28
26
|
* Fetches memorisations by exact ID, or by text across memorisations only. `tags` keeps only
|
|
29
27
|
* memorisations that carry every label. With `tags` and no text it returns the newest ones.
|
|
30
|
-
* An exact ID ignores `tags`.
|
|
28
|
+
* An exact ID ignores `tags`. Every read covers the whole active space and its linked spaces,
|
|
29
|
+
* so a memorisation is found from any folder. `rerank` (the `memorisationRerank` setting)
|
|
30
|
+
* lets the reranker server reorder the text matches; each match keeps its content-only
|
|
31
|
+
* confidence, and the one-strong-match rule reads the first two in that order.
|
|
31
32
|
*/
|
|
32
|
-
export declare function runRecall(args: RecallArgs,
|
|
33
|
+
export declare function runRecall(args: RecallArgs, linked: LinkedSearchSettings, rerank?: boolean): Promise<Record<string, unknown>>;
|
|
33
34
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { memoryClient } from "../services/client.js";
|
|
2
2
|
import { findSnapshotByHostSession, getMemorisationById, listMemorisationsByTags, memorisationIdTaken, } from "../services/memorisation-store.js";
|
|
3
3
|
import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
|
|
4
|
-
import { hasAllTags, isMemorisationId, MEMORISATION_TAG, memorisationId, memorisationSizeError, parseTagList, pickRecallMatches, snapshotText, visibleTags, } from "./memorisation.js";
|
|
4
|
+
import { hasAllTags, isMemorisationId, MEMORISATION_TAG, memorisationId, memorisationSizeError, parseTagList, pickRecallMatches, snapshotText, visibleTags, withProjectLabel, } from "./memorisation.js";
|
|
5
5
|
const SNAPSHOT_FIELDS = ["topic", "done", "doing", "next"];
|
|
6
6
|
const BLOCKED = { success: false, error: "Private content blocked" };
|
|
7
7
|
/** Strips `<private>` text. A field that is fully private is dropped. */
|
|
@@ -54,7 +54,9 @@ export async function runMemorise(args, context, tags, settings, linked) {
|
|
|
54
54
|
const wantsReplace = built.kind === "snapshot" && settings.memorisationRepeat === "replace";
|
|
55
55
|
const earlier = wantsReplace && sessionId ? await findSnapshotByHostSession(sessionId, tags.project.tag) : null;
|
|
56
56
|
const id = earlier?.id ?? (await memorisationId((candidate) => memorisationIdTaken(candidate, linked)));
|
|
57
|
-
|
|
57
|
+
// The project name is one more label, so a label filter narrows by project. It comes after
|
|
58
|
+
// the fixed tag and the caller's labels and is never doubled.
|
|
59
|
+
const recordTags = withProjectLabel(callerTags(args.tags), tags.project.projectName);
|
|
58
60
|
const info = tags.project;
|
|
59
61
|
const result = await memoryClient.addMemory(built.text, info.tag, {
|
|
60
62
|
id,
|
|
@@ -96,30 +98,36 @@ const LABEL_SEARCH_POOL = 200;
|
|
|
96
98
|
/**
|
|
97
99
|
* Fetches memorisations by exact ID, or by text across memorisations only. `tags` keeps only
|
|
98
100
|
* memorisations that carry every label. With `tags` and no text it returns the newest ones.
|
|
99
|
-
* An exact ID ignores `tags`.
|
|
101
|
+
* An exact ID ignores `tags`. Every read covers the whole active space and its linked spaces,
|
|
102
|
+
* so a memorisation is found from any folder. `rerank` (the `memorisationRerank` setting)
|
|
103
|
+
* lets the reranker server reorder the text matches; each match keeps its content-only
|
|
104
|
+
* confidence, and the one-strong-match rule reads the first two in that order.
|
|
100
105
|
*/
|
|
101
|
-
export async function runRecall(args,
|
|
106
|
+
export async function runRecall(args, linked, rerank = false) {
|
|
102
107
|
const input = (args.query ?? args.memoryId ?? args.content)?.trim();
|
|
103
108
|
const wanted = parseTagList(args.tags);
|
|
104
109
|
if (!input && wanted.length === 0) {
|
|
105
110
|
return { success: false, error: "query or tags required: a memorisation ID, text or labels" };
|
|
106
111
|
}
|
|
107
112
|
if (!input)
|
|
108
|
-
return recallByLabels(wanted,
|
|
113
|
+
return recallByLabels(wanted, args.limit);
|
|
109
114
|
if (isMemorisationId(input)) {
|
|
110
|
-
const found = await getMemorisationById(input,
|
|
115
|
+
const found = await getMemorisationById(input, null, linked);
|
|
111
116
|
if (!found)
|
|
112
117
|
return { success: false, error: `No memorisation has the ID ${input}` };
|
|
113
118
|
return { success: true, count: 1, matches: [found] };
|
|
114
119
|
}
|
|
115
|
-
const result = await memoryClient.searchMemories(input,
|
|
120
|
+
const result = await memoryClient.searchMemories(input, "", "all-projects", {
|
|
116
121
|
includeLinked: true,
|
|
117
122
|
kind: "memorisation",
|
|
123
|
+
reranker: rerank,
|
|
124
|
+
// A reranked recall still answers "no memorisation matched" when nothing clears the threshold.
|
|
125
|
+
thresholdAfterRerank: true,
|
|
118
126
|
...(wanted.length > 0 ? { limit: LABEL_SEARCH_POOL } : {}),
|
|
119
127
|
});
|
|
120
128
|
if (!result.success)
|
|
121
129
|
return { success: false, error: result.error };
|
|
122
|
-
const
|
|
130
|
+
const mapped = (result.results ?? [])
|
|
123
131
|
.filter((hit) => hasAllTags(hit.tags, wanted))
|
|
124
132
|
.map((hit) => ({
|
|
125
133
|
id: hit.id,
|
|
@@ -128,8 +136,10 @@ export async function runRecall(args, tags, scope, linked) {
|
|
|
128
136
|
createdAt: toIso(hit.createdAt),
|
|
129
137
|
tags: visibleTags(hit.tags),
|
|
130
138
|
...(hit.spaceName ? { space: hit.spaceName } : {}),
|
|
131
|
-
}))
|
|
132
|
-
|
|
139
|
+
}));
|
|
140
|
+
// With the reranker on, the client already ordered the own-space rows and the sort would
|
|
141
|
+
// undo that order. On a reranker failure the client falls back to the confidence order.
|
|
142
|
+
const candidates = rerank ? mapped : mapped.sort((a, b) => b.confidence - a.confidence);
|
|
133
143
|
const matches = pickRecallMatches(candidates, args.limit);
|
|
134
144
|
return {
|
|
135
145
|
success: true,
|
|
@@ -140,8 +150,8 @@ export async function runRecall(args, tags, scope, linked) {
|
|
|
140
150
|
};
|
|
141
151
|
}
|
|
142
152
|
/** The newest memorisations that carry every label. They are not ranked, so they have no confidence. */
|
|
143
|
-
async function recallByLabels(wanted,
|
|
144
|
-
const listed = await listMemorisationsByTags(
|
|
153
|
+
async function recallByLabels(wanted, limit) {
|
|
154
|
+
const listed = await listMemorisationsByTags("", wanted, "all-projects", Math.max(1, limit ?? LABEL_RECALL_LIMIT));
|
|
145
155
|
if (!listed.success)
|
|
146
156
|
return listed;
|
|
147
157
|
const matches = listed.memories.map((memory) => ({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type MemorisationSettings } from "./memorisation.js";
|
|
2
2
|
export type { MemorisationSettings } from "./memorisation.js";
|
|
3
|
-
export declare const MEMORISATION_KEYS: readonly ["memorisationMaxSize", "memorisationSizeUnit", "memorisationRepeat"];
|
|
3
|
+
export declare const MEMORISATION_KEYS: readonly ["memorisationMaxSize", "memorisationSizeUnit", "memorisationRepeat", "memorisationRerank"];
|
|
4
4
|
export declare const MEMORISATION_DEFAULTS: MemorisationSettings;
|
|
5
5
|
/** Global-only memorisation settings, with defaults for an older config file. */
|
|
6
6
|
export declare function settingsFrom(config: Partial<MemorisationSettings>): MemorisationSettings;
|
|
7
7
|
/** The web writer validates the pair, so a unit edit cannot strand an invalid number. */
|
|
8
8
|
export declare function checkMemorisationSettingsEdits(edits: Record<string, unknown>, current: MemorisationSettings): void;
|
|
9
9
|
export { settingsFrom as memorisationSettingsFrom };
|
|
10
|
-
/** The
|
|
10
|
+
/** The four settings are global-only; a project config cannot replace them. */
|
|
11
11
|
export declare function removeMemorisationProjectOverrides(config: Record<string, unknown>): void;
|
|
@@ -3,11 +3,13 @@ export const MEMORISATION_KEYS = [
|
|
|
3
3
|
"memorisationMaxSize",
|
|
4
4
|
"memorisationSizeUnit",
|
|
5
5
|
"memorisationRepeat",
|
|
6
|
+
"memorisationRerank",
|
|
6
7
|
];
|
|
7
8
|
export const MEMORISATION_DEFAULTS = {
|
|
8
9
|
memorisationMaxSize: 2000,
|
|
9
10
|
memorisationSizeUnit: "characters",
|
|
10
11
|
memorisationRepeat: "add",
|
|
12
|
+
memorisationRerank: false,
|
|
11
13
|
};
|
|
12
14
|
/** Global-only memorisation settings, with defaults for an older config file. */
|
|
13
15
|
export function settingsFrom(config) {
|
|
@@ -20,7 +22,11 @@ export function settingsFrom(config) {
|
|
|
20
22
|
if (memorisationRepeat !== "add" && memorisationRepeat !== "replace") {
|
|
21
23
|
throw new Error("Invalid memorisationRepeat config");
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
if (config.memorisationRerank !== undefined && typeof config.memorisationRerank !== "boolean") {
|
|
26
|
+
throw new Error("Invalid memorisationRerank config");
|
|
27
|
+
}
|
|
28
|
+
const memorisationRerank = config.memorisationRerank ?? MEMORISATION_DEFAULTS.memorisationRerank;
|
|
29
|
+
return { memorisationMaxSize, memorisationSizeUnit, memorisationRepeat, memorisationRerank };
|
|
24
30
|
}
|
|
25
31
|
/** The web writer validates the pair, so a unit edit cannot strand an invalid number. */
|
|
26
32
|
export function checkMemorisationSettingsEdits(edits, current) {
|
|
@@ -30,6 +36,9 @@ export function checkMemorisationSettingsEdits(edits, current) {
|
|
|
30
36
|
throw new Error("memorisationRepeat must be add or replace");
|
|
31
37
|
}
|
|
32
38
|
}
|
|
39
|
+
if (edits.memorisationRerank !== undefined && typeof edits.memorisationRerank !== "boolean") {
|
|
40
|
+
throw new Error("memorisationRerank must be true or false");
|
|
41
|
+
}
|
|
33
42
|
const hasSize = Object.hasOwn(edits, "memorisationMaxSize");
|
|
34
43
|
const hasUnit = Object.hasOwn(edits, "memorisationSizeUnit");
|
|
35
44
|
if (!hasSize && !hasUnit)
|
|
@@ -41,7 +50,7 @@ export function checkMemorisationSettingsEdits(edits, current) {
|
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
52
|
export { settingsFrom as memorisationSettingsFrom };
|
|
44
|
-
/** The
|
|
53
|
+
/** The four settings are global-only; a project config cannot replace them. */
|
|
45
54
|
export function removeMemorisationProjectOverrides(config) {
|
|
46
55
|
for (const key of MEMORISATION_KEYS)
|
|
47
56
|
delete config[key];
|
|
@@ -4,6 +4,7 @@ export interface MemorisationSettings {
|
|
|
4
4
|
memorisationMaxSize: number;
|
|
5
5
|
memorisationSizeUnit: MemorisationSizeUnit;
|
|
6
6
|
memorisationRepeat: MemorisationRepeat;
|
|
7
|
+
memorisationRerank: boolean;
|
|
7
8
|
}
|
|
8
9
|
export interface MemorisationSnapshot {
|
|
9
10
|
topic?: string;
|
|
@@ -41,3 +42,8 @@ export declare function parseTagList(text: string | undefined): string[];
|
|
|
41
42
|
export declare function visibleTags(tags: string[] | undefined): string[];
|
|
42
43
|
/** True when the record carries every wanted tag. Case and surrounding spaces do not matter. */
|
|
43
44
|
export declare function hasAllTags(recordTags: string[] | undefined, wanted: string[]): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The project name as one more label, after the fixed tag and the caller's labels. The root
|
|
47
|
+
* folder name `/` is not a usable label, so a caller there gets no project label.
|
|
48
|
+
*/
|
|
49
|
+
export declare function withProjectLabel(labels: string[], projectName: string | undefined): string[];
|
|
@@ -74,3 +74,13 @@ export function hasAllTags(recordTags, wanted) {
|
|
|
74
74
|
const have = new Set((recordTags ?? []).map(normaliseTag));
|
|
75
75
|
return wanted.every((tag) => have.has(normaliseTag(tag)));
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The project name as one more label, after the fixed tag and the caller's labels. The root
|
|
79
|
+
* folder name `/` is not a usable label, so a caller there gets no project label.
|
|
80
|
+
*/
|
|
81
|
+
export function withProjectLabel(labels, projectName) {
|
|
82
|
+
const name = projectName?.trim().toLowerCase();
|
|
83
|
+
if (!name || name === "/")
|
|
84
|
+
return labels;
|
|
85
|
+
return labels.includes(name) ? labels : [...labels, name];
|
|
86
|
+
}
|
|
@@ -176,7 +176,7 @@ export async function executeMemoryOperation(args, context) {
|
|
|
176
176
|
case "memorize":
|
|
177
177
|
return await runMemorise(args, context, tags, memorisationSettingsFrom(CONFIG), linkedSettingsFrom(CONFIG));
|
|
178
178
|
case "recall":
|
|
179
|
-
return await runRecall(args,
|
|
179
|
+
return await runRecall(args, linkedSettingsFrom(CONFIG), CONFIG.memorisationRerank);
|
|
180
180
|
case "search": {
|
|
181
181
|
if (!args.query)
|
|
182
182
|
return { success: false, error: "query required" };
|
package/dist/services/client.js
CHANGED
|
@@ -5,7 +5,7 @@ import { tursoConnectionManager } from "./turso/connection-manager.js";
|
|
|
5
5
|
import { ensureTursoReady } from "./turso/ready.js";
|
|
6
6
|
import { formatTagsForEmbedding } from "./turso/vector-utils.js";
|
|
7
7
|
import { extractScopeFromContainerTag, resolveMemoryScope } from "./memory-scope.js";
|
|
8
|
-
import {
|
|
8
|
+
import { rerankSettingsFor } from "./reranker.js";
|
|
9
9
|
import { rerankedSearch } from "./retrieval-rerank.js";
|
|
10
10
|
import { findLinkedMemoryOwner, linkedRefusal, linkedSettingsFrom, listLinkedSpaces, mapListedMemory, searchLinkedSpaces, } from "./linked-search.js";
|
|
11
11
|
import { CONFIG } from "../config.js";
|
|
@@ -68,12 +68,14 @@ export class LocalMemoryClient {
|
|
|
68
68
|
// not change this search's limit or reranker target.
|
|
69
69
|
const maxMemories = options.limit ?? CONFIG.maxMemories;
|
|
70
70
|
const keywordBoost = CONFIG.keywordBoost;
|
|
71
|
-
const reranker =
|
|
71
|
+
const reranker = rerankSettingsFor(CONFIG, options.reranker);
|
|
72
72
|
// The reranker path below always uses the boost: it needs a full pool on the similarity scale.
|
|
73
73
|
const keywordMode = CONFIG.keywordMode;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
// On but incomplete, through the general switch or a search's own override:
|
|
75
|
+
// search must keep working, so treat the reranker as off and say so once.
|
|
76
|
+
if (!reranker &&
|
|
77
|
+
(options.reranker === true || (CONFIG.rerankerEnabled && options.reranker !== false))) {
|
|
78
|
+
log("Reranker asked for without a URL or model; searching without it", {
|
|
77
79
|
code: "reranker_not_configured",
|
|
78
80
|
});
|
|
79
81
|
}
|
|
@@ -99,6 +101,7 @@ export class LocalMemoryClient {
|
|
|
99
101
|
maxMemories,
|
|
100
102
|
similarityThreshold: CONFIG.similarityThreshold,
|
|
101
103
|
kind: options.kind ?? "normal",
|
|
104
|
+
thresholdAfterRerank: options.thresholdAfterRerank,
|
|
102
105
|
}, reranker, {
|
|
103
106
|
search: (shardList, vector, tag, limit, threshold, queryText, kind) => tursoVectorSearch.searchAcrossShards(shardList, vector, tag, limit, threshold, queryText, keywordBoost, "boost", undefined, kind),
|
|
104
107
|
log: (message, meta) => log(message, meta),
|
|
@@ -56,6 +56,7 @@ const keys = new Set([
|
|
|
56
56
|
"memorisationRepeat",
|
|
57
57
|
"memorisationMaxSize",
|
|
58
58
|
"memorisationSizeUnit",
|
|
59
|
+
"memorisationRerank",
|
|
59
60
|
]);
|
|
60
61
|
function isFolder(path) {
|
|
61
62
|
try {
|
|
@@ -117,6 +118,8 @@ function isValidEdit(key, value) {
|
|
|
117
118
|
return value === null || KEYWORD_MODES.includes(value);
|
|
118
119
|
if (key === "memorisationSizeUnit")
|
|
119
120
|
return value === "characters" || value === "tokens";
|
|
121
|
+
if (key === "memorisationRerank")
|
|
122
|
+
return typeof value === "boolean";
|
|
120
123
|
if (key === "memorisationRepeat" || key === "memorisationMaxSize")
|
|
121
124
|
return true;
|
|
122
125
|
// The opening context query: null removes it; an empty query means no opening search.
|
|
@@ -19,6 +19,10 @@ export interface MemoryReadOptions {
|
|
|
19
19
|
kind?: MemoryKind;
|
|
20
20
|
/** Search only: the result limit for this read, instead of `maxMemories`. */
|
|
21
21
|
limit?: number;
|
|
22
|
+
/** Search only: rerank this read whatever the general `rerankerEnabled` switch says. */
|
|
23
|
+
reranker?: boolean;
|
|
24
|
+
/** Search only: with the reranker on, still drop hits below `similarityThreshold`. */
|
|
25
|
+
thresholdAfterRerank?: boolean;
|
|
22
26
|
}
|
|
23
27
|
/** The settings a linked search needs; the caller snapshots them from CONFIG. */
|
|
24
28
|
export interface LinkedSearchSettings {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import type { MemoryScope } from "./client.js";
|
|
2
1
|
import type { RouteResult } from "./chat/chat-routes.js";
|
|
3
2
|
import type { LinkedSearchSettings } from "./linked-search.js";
|
|
4
3
|
/** The CONFIG values recall needs. The route registration builds them, so this module has no CONFIG import. */
|
|
5
4
|
export interface MemorisationRouteConfig {
|
|
6
|
-
defaultScope: MemoryScope | undefined;
|
|
7
5
|
linked: LinkedSearchSettings;
|
|
6
|
+
/** The global `memorisationRerank` setting, so text recall may use the reranker. */
|
|
7
|
+
rerank: boolean;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Routes for the Memorisation page. Returns undefined for paths it does not own.
|
|
11
11
|
* `GET /api/memorisations?tags=a,b` lists, `GET /api/memorisations/recall?q=&tags=` recalls
|
|
12
|
-
* and `POST /api/memorisations/bulk-delete` deletes.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* and `POST /api/memorisations/bulk-delete` deletes. Every read covers the whole active
|
|
13
|
+
* space and its linked spaces, so the page works from any folder. The general `/api/memories`
|
|
14
|
+
* delete routes refuse memorisations by design, so this module owns the bulk delete.
|
|
15
|
+
* Memorisation text is returned to the page and never written to the log.
|
|
15
16
|
*/
|
|
16
|
-
export declare function routeMemorisations(path: string, method: string, url: URL,
|
|
17
|
+
export declare function routeMemorisations(path: string, method: string, url: URL, config: MemorisationRouteConfig, readBody?: () => Promise<any>): Promise<RouteResult | undefined>;
|
|
@@ -11,28 +11,26 @@ const fail = (status, error) => ({
|
|
|
11
11
|
/**
|
|
12
12
|
* Routes for the Memorisation page. Returns undefined for paths it does not own.
|
|
13
13
|
* `GET /api/memorisations?tags=a,b` lists, `GET /api/memorisations/recall?q=&tags=` recalls
|
|
14
|
-
* and `POST /api/memorisations/bulk-delete` deletes.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* and `POST /api/memorisations/bulk-delete` deletes. Every read covers the whole active
|
|
15
|
+
* space and its linked spaces, so the page works from any folder. The general `/api/memories`
|
|
16
|
+
* delete routes refuse memorisations by design, so this module owns the bulk delete.
|
|
17
|
+
* Memorisation text is returned to the page and never written to the log.
|
|
17
18
|
*/
|
|
18
|
-
export async function routeMemorisations(path, method, url,
|
|
19
|
+
export async function routeMemorisations(path, method, url, config, readBody) {
|
|
19
20
|
const wanted = parseTagList(url.searchParams.get("tags") ?? undefined);
|
|
20
21
|
if (method === "GET" && path === "/api/memorisations")
|
|
21
|
-
return listMemorisations(
|
|
22
|
+
return listMemorisations(wanted);
|
|
22
23
|
if (method === "GET" && path === "/api/memorisations/recall") {
|
|
23
|
-
return recall(url.searchParams, wanted,
|
|
24
|
+
return recall(url.searchParams, wanted, config);
|
|
24
25
|
}
|
|
25
26
|
if (method === "POST" && path === "/api/memorisations/bulk-delete" && readBody) {
|
|
26
|
-
return bulkDelete(await readBody(),
|
|
27
|
+
return bulkDelete(await readBody(), config);
|
|
27
28
|
}
|
|
28
29
|
return undefined;
|
|
29
30
|
}
|
|
30
|
-
async function listMemorisations(
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
import("./tags.js"),
|
|
34
|
-
]);
|
|
35
|
-
const result = await listMemorisationsByTags(getTags(cwd).project.tag, wanted, "project", LIST_LIMIT);
|
|
31
|
+
async function listMemorisations(wanted) {
|
|
32
|
+
const { listMemorisationsByTags } = await import("./memorisation-store.js");
|
|
33
|
+
const result = await listMemorisationsByTags("", wanted, "all-projects", LIST_LIMIT);
|
|
36
34
|
if (!result.success)
|
|
37
35
|
return fail(500, result.error);
|
|
38
36
|
const memorisations = result.memories.map((memory) => ({
|
|
@@ -46,20 +44,17 @@ async function listMemorisations(cwd, wanted) {
|
|
|
46
44
|
}));
|
|
47
45
|
return ok({ memorisations });
|
|
48
46
|
}
|
|
49
|
-
async function recall(params, wanted,
|
|
47
|
+
async function recall(params, wanted, config) {
|
|
50
48
|
const text = params.get("q")?.trim();
|
|
51
49
|
if (!text && wanted.length === 0)
|
|
52
50
|
return fail(400, "q or tags required: an ID, text or labels");
|
|
53
51
|
const limit = Number(params.get("limit"));
|
|
54
|
-
const
|
|
55
|
-
import("../core/memorisation-operations.js"),
|
|
56
|
-
import("./tags.js"),
|
|
57
|
-
]);
|
|
52
|
+
const { runRecall } = await import("../core/memorisation-operations.js");
|
|
58
53
|
const result = await runRecall({
|
|
59
54
|
query: text,
|
|
60
55
|
tags: wanted.join(","),
|
|
61
56
|
...(Number.isInteger(limit) && limit > 0 ? { limit } : {}),
|
|
62
|
-
},
|
|
57
|
+
}, config.linked, config.rerank);
|
|
63
58
|
if (result.success !== true) {
|
|
64
59
|
// An unknown ID is a missing record. Any other refusal is a server problem.
|
|
65
60
|
const error = String(result.error ?? "Recall failed");
|
|
@@ -67,8 +62,8 @@ async function recall(params, wanted, cwd, config) {
|
|
|
67
62
|
}
|
|
68
63
|
return ok({ count: result.count, matches: result.matches });
|
|
69
64
|
}
|
|
70
|
-
/** Deletes memorisations of
|
|
71
|
-
async function bulkDelete(body,
|
|
65
|
+
/** Deletes memorisations of any project from the active space. Linked spaces are never touched. */
|
|
66
|
+
async function bulkDelete(body, config) {
|
|
72
67
|
const ids = body?.ids;
|
|
73
68
|
if (!Array.isArray(ids) ||
|
|
74
69
|
ids.length < 1 ||
|
|
@@ -76,15 +71,11 @@ async function bulkDelete(body, cwd, config) {
|
|
|
76
71
|
!ids.every((id) => typeof id === "string" && isMemorisationId(id))) {
|
|
77
72
|
return fail(400, `ids must be 1 to ${BULK_DELETE_MAX} memorisation IDs (ms-xxxxxx)`);
|
|
78
73
|
}
|
|
79
|
-
const
|
|
80
|
-
import("./memorisation-store.js"),
|
|
81
|
-
import("./tags.js"),
|
|
82
|
-
]);
|
|
83
|
-
const containerTag = getTags(cwd).project.tag;
|
|
74
|
+
const { deleteMemorisation } = await import("./memorisation-store.js");
|
|
84
75
|
const deleted = [];
|
|
85
76
|
const skipped = [];
|
|
86
77
|
for (const id of new Set(ids)) {
|
|
87
|
-
const outcome = await deleteMemorisation(id,
|
|
78
|
+
const outcome = await deleteMemorisation(id, null, config.linked);
|
|
88
79
|
if (outcome === "deleted")
|
|
89
80
|
deleted.push(id);
|
|
90
81
|
else
|
|
@@ -10,11 +10,12 @@ export interface StoredMemorisation {
|
|
|
10
10
|
space?: string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Looks a memorisation up by exact ID in the active space, then in the linked spaces.
|
|
14
|
-
*
|
|
13
|
+
* Looks a memorisation up by exact ID in the active space, then in the linked spaces. A
|
|
14
|
+
* project tag narrows the lookup to that project; `null` matches any project, the rule
|
|
15
|
+
* recall and bulk delete use.
|
|
15
16
|
* The caller snapshots `linked` from CONFIG, so this module never imports it.
|
|
16
17
|
*/
|
|
17
|
-
export declare function getMemorisationById(id: string, containerTag: string, linked: LinkedSearchSettings): Promise<StoredMemorisation | null>;
|
|
18
|
+
export declare function getMemorisationById(id: string, containerTag: string | null, linked: LinkedSearchSettings): Promise<StoredMemorisation | null>;
|
|
18
19
|
/**
|
|
19
20
|
* True when any project in the active or a linked space already uses this memorisation ID.
|
|
20
21
|
* A new ID must be free everywhere, so this check ignores the project.
|
|
@@ -38,8 +39,9 @@ export declare function listMemorisationsByTags(containerTag: string, tags: stri
|
|
|
38
39
|
}>;
|
|
39
40
|
export type MemorisationDeleteOutcome = "deleted" | "not-found" | "linked" | "not-memorisation";
|
|
40
41
|
/**
|
|
41
|
-
* Deletes one memorisation
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Deletes one memorisation from the active space. A project tag narrows the delete to that
|
|
43
|
+
* project; `null` matches any project, the rule bulk delete uses. No row is "not-found".
|
|
44
|
+
* A row of another type is kept ("not-memorisation"). A row that lives only in a linked,
|
|
45
|
+
* read-only space is "linked" and is never touched.
|
|
44
46
|
*/
|
|
45
|
-
export declare function deleteMemorisation(id: string, containerTag: string, linked: LinkedSearchSettings): Promise<MemorisationDeleteOutcome>;
|
|
47
|
+
export declare function deleteMemorisation(id: string, containerTag: string | null, linked: LinkedSearchSettings): Promise<MemorisationDeleteOutcome>;
|