-
Notifications
You must be signed in to change notification settings - Fork 0
fix(memory,mcp): add migration 111 + real Zod schemas for substrate dispatch tools #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| -- TV6 — Typed memory decay: track access frequency so decay can grant access-based immunity. | ||
| -- | ||
| -- `access_count` increments each time a memory is injected into a prompt; `last_accessed_at` | ||
| -- records the most recent injection (and re-bases the decay clock so recently-used memories | ||
| -- survive). Both default to a never-accessed baseline, so every pre-existing row behaves as | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: Comment says "Both default" but Line 5-8 states both columns "default to a never-accessed baseline", but migration line 10 adds |
||
| -- freshly-created: its decay clock falls back to `created_at` and its access count starts at 0. | ||
| -- These columns only feed the OPT-IN, default-off decay sweep (`MEMORY_TYPED_DECAY_ENABLED`); | ||
| -- with the sweep disabled they are pure, harmless telemetry. | ||
| ALTER TABLE memories ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0; | ||
| ALTER TABLE memories ADD COLUMN last_accessed_at TEXT; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,8 @@ interface MemoryRow { | |
| created_at: string; | ||
| updated_at: string; | ||
| expires_at: string | null; | ||
| access_count?: number | null; | ||
| last_accessed_at?: string | null; | ||
| } | ||
|
|
||
| // Memory cache configuration | ||
|
|
@@ -79,6 +81,8 @@ function rowToMemory(row: MemoryRow): Memory { | |
| createdAt: new Date(String(row.created_at)), | ||
| updatedAt: new Date(String(row.updated_at)), | ||
| expiresAt: row.expires_at ? new Date(String(row.expires_at)) : null, | ||
| accessCount: typeof row.access_count === "number" ? row.access_count : 0, | ||
| lastAccessedAt: row.last_accessed_at ? new Date(String(row.last_accessed_at)) : null, | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -155,7 +159,7 @@ function scheduleVectorUpsert(id: string, content: string): void { | |
| * Create a new memory entry (UPSERT: updates existing if same apiKeyId + key) | ||
| */ | ||
| export async function createMemory( | ||
| memory: Omit<Memory, "id" | "createdAt" | "updatedAt"> | ||
| memory: Omit<Memory, "id" | "createdAt" | "updatedAt" | "accessCount" | "lastAccessedAt"> | ||
| ): Promise<Memory> { | ||
| const db = getDbInstance(); | ||
| const now = new Date().toISOString(); | ||
|
|
@@ -190,6 +194,10 @@ export async function createMemory( | |
| createdAt: new Date(String(existing.created_at)), | ||
| updatedAt: new Date(now), | ||
| expiresAt: memory.expiresAt ?? null, | ||
| accessCount: typeof existing.access_count === "number" ? existing.access_count : 0, | ||
| lastAccessedAt: existing.last_accessed_at | ||
| ? new Date(String(existing.last_accessed_at)) | ||
| : null, | ||
| }; | ||
|
|
||
| // Invalidate and update cache | ||
|
|
@@ -259,6 +267,8 @@ export async function createMemory( | |
| createdAt: new Date(now), | ||
| updatedAt: new Date(now), | ||
| expiresAt: memory.expiresAt ?? null, | ||
| accessCount: 0, | ||
| lastAccessedAt: null, | ||
| }; | ||
|
|
||
| // Cache the newly created memory | ||
|
|
@@ -543,3 +553,28 @@ export function getMemoryTokensUsed(apiKeyId?: string): number { | |
| const row = stmt.get(...(apiKeyId ? [apiKeyId] : [])) as { tokensUsed: number } | undefined; | ||
| return row?.tokensUsed ?? 0; | ||
| } | ||
|
|
||
| /** | ||
| * TV6: record that the given memories were injected into a prompt. Increments | ||
| * `access_count` and stamps `last_accessed_at` so the decay clock re-bases and access | ||
| * immunity can accrue. Best-effort and non-blocking by contract — callers fire-and-forget; | ||
| * any error (DB closed in test teardown, missing columns pre-migration) is swallowed so | ||
| * retrieval is never impacted. | ||
| */ | ||
| export function recordMemoryAccess(ids: string[]): void { | ||
| if (!Array.isArray(ids) || ids.length === 0) return; | ||
| const unique = Array.from(new Set(ids.filter((id) => typeof id === "string" && id))); | ||
| if (unique.length === 0) return; | ||
| try { | ||
| const db = getDbInstance(); | ||
| const placeholders = unique.map(() => "?").join(", "); | ||
| const stmt = db.prepare( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: Unbounded
|
||
| `UPDATE memories SET access_count = access_count + 1, last_accessed_at = ? ` + | ||
| `WHERE id IN (${placeholders})` | ||
| ); | ||
| stmt.run(new Date().toISOString(), ...unique); | ||
| for (const id of unique) invalidateMemoryCache(id); | ||
| } catch { | ||
| // intentional swallow — access tracking is opportunistic telemetry, never load-bearing | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this migration as
111while the highest existing migration before it is107introduces new unexplained gaps108,109, and110. I checkedscripts/check/check-migration-numbering.mjs: its frozen gap allowlist only permits026and055, so this new file adds fresh migration-numbering anomalies that the repo's migration-numbering test/gate will report. Please either use the next contiguous slot or add documented compatibility/allowlist handling for the skipped versions.Useful? React with 👍 / 👎.