Browse Source

Memory: recall accounting in getInjection, op stats (used/dead/top)

master
Serge Shutov 1 week ago
parent
commit
52bed55fc7
  1. 45
      bridge/memory.js
  2. 2
      bridge/tools.js

45
bridge/memory.js

@ -113,7 +113,8 @@ function opGet(state, args) { @@ -113,7 +113,8 @@ function opGet(state, args) {
for (const e of state.entries) {
if (ids.indexOf(e.id) !== -1) { found.push(e); totalBytes += e.bytes || 0; }
}
for (const e of found) { e.lastRecalled = Date.now(); e.recallCount = (e.recallCount || 0) + 1; }
const nowTs = nowIso();
for (const e of found) { e.lastRecalled = nowTs; e.recallCount = (e.recallCount || 0) + 1; }
return { items: found.map(function (e) { return { id: e.id, key: e.key, track: e.track, scope: e.scope, importance: e.importance, tags: e.tags || [], text: e.text, bytes: e.bytes, version: e.version }; }), totalBytes: totalBytes, requested: ids.length, found: found.length };
}
@ -144,6 +145,41 @@ function opSearch(state, args) { @@ -144,6 +145,41 @@ function opSearch(state, args) {
return { items: hits.map(function (e) { return { id: e.id, key: e.key, track: e.track, importance: e.importance, state: e.state || null, tags: e.tags || [], bytes: e.bytes, preview: preview(e.text, pv) }; }), totalBytes: totalBytes, matched: hits.length };
}
function opStats(state, args) {
const now = Date.now();
const staleDays = Math.max(1, Number(args.staleDays) || 30);
const staleMs = staleDays * 24 * 3600 * 1000;
const all = state.entries.map(function (e) {
const created = e.createdAt ? Date.parse(e.createdAt) : now;
const recalled = e.lastRecalled ? Date.parse(e.lastRecalled) : 0;
return {
id: e.id, key: e.key, track: e.track, scope: e.scope,
importance: e.importance, bytes: e.bytes || 0,
recallCount: e.recallCount || 0,
lastRecalled: e.lastRecalled || null,
ageDays: Math.round((now - created) / 86400000),
idleDays: recalled ? Math.round((now - recalled) / 86400000) : Math.round((now - created) / 86400000),
};
});
const used = all.filter(function (e) { return e.recallCount > 0; }).sort(function (a, b) { return b.recallCount - a.recallCount; });
const dead = all.filter(function (e) { return e.recallCount === 0 && e.idleDays >= staleDays; }).sort(function (a, b) { return b.bytes - a.bytes; });
const fresh = all.filter(function (e) { return e.recallCount === 0 && e.idleDays < staleDays; });
const neverAlways = all.filter(function (e) { return e.importance === 'always' && e.recallCount === 0; });
let totalBytes = 0; for (const e of all) totalBytes += e.bytes;
return {
total: all.length,
totalBytes: totalBytes,
usedCount: used.length,
neverUsedCount: all.length - used.length,
staleDays: staleDays,
top: used.slice(0, Number(args.limit) || 15),
dead: dead.slice(0, Number(args.limit) || 15),
freshCount: fresh.length,
neverAlwaysCount: neverAlways.length,
neverAlways: neverAlways.slice(0, 20),
};
}
function opBudgets(state) {
const out = [];
const groups = {};
@ -255,6 +291,7 @@ function query(state, args) { @@ -255,6 +291,7 @@ function query(state, args) {
if (op === 'unread') return opUnread(state, args);
if (op === 'search') return opSearch(state, args);
if (op === 'budgets') return opBudgets(state);
if (op === 'stats') return opStats(state, args);
if (op === 'write') return opWrite(state, Object.assign({}, args, { op: args.subop || 'add' }));
// note: add/replace/remove handled above
throw new Error('unknown op: ' + op);
@ -271,6 +308,12 @@ function getInjection(state, chatId, workspaceKey) { @@ -271,6 +308,12 @@ function getInjection(state, chatId, workspaceKey) {
if (e.importance !== 'always') continue;
if (e.track === 'agent' && e.scope === 'user-global') { entries.push(e); continue; }
}
// Recall accounting: every entry that goes into the injected block counts.
const nowTs = nowIso();
for (const e of entries) {
e.lastRecalled = nowTs;
e.recallCount = (e.recallCount || 0) + 1;
}
const texts = entries.map(function (e) { return String(e.text); });
const fpBase = entries.map(function (e) { return e.id + ':' + (e.version || 1); }).sort().join('|');
const fp = crypto.createHash('sha256').update(fpBase).digest('hex').slice(0, 16);

2
bridge/tools.js

@ -53,7 +53,7 @@ const TOOL_DEFS = [ @@ -53,7 +53,7 @@ const TOOL_DEFS = [
{ name: 'dsh_git_diff', description: 'git diff (read-only, dumps to file if too large)', params: { path: 'string?', staged: 'bool?', maxBytes: 'int? default 48000' } },
{ name: 'dsh_handoff', description: '[Recommended for multi-step tasks] Local agent (dsh headless) runs the full plan autonomously: reads/searches/edits/runs commands with no step-by-step driving, then returns only the final result. planText must be a self-contained full plan (with acceptance criteria). After calling, end your reply immediately; do not output further tool calls', params: { planText: 'string full plan text', workspace: 'string?', taskNote: 'string? task title (used for log names)', timeoutMs: 'int? default 300000, max 1800000' } },
{ name: 'dsh_log', description: 'Read tail of execution log (.dsh-memory/logs/execution-log.jsonl, one entry per tool call)', params: { lines: 'int? default 100, max 1000' } },
{ name: 'dsh_memory', description: 'Persistent memory. op: list|get|recent|unread|search|budgets|directives|add|replace|remove. list/get/recent/unread/search return items with bytes and totalBytes in meta; always check totalBytes before get. write op: add|replace|remove.', params: { op: 'string', track: 'string?', scope: 'string?', importance: 'string?', since: 'string?', limit: 'int?', ids: 'string[]?', id: 'string?', query: 'string?', entry: 'object?', text: 'string?', tags: 'string[]?', state: 'string?', chatId: 'string?' } },
{ name: 'dsh_memory', description: 'Persistent memory. op: list|get|recent|unread|search|budgets|stats|directives|add|replace|remove. list/get/recent/unread/search return items with bytes and totalBytes in meta; always check totalBytes before get. write op: add|replace|remove.', params: { op: 'string', track: 'string?', scope: 'string?', importance: 'string?', since: 'string?', limit: 'int?', ids: 'string[]?', id: 'string?', query: 'string?', entry: 'object?', text: 'string?', tags: 'string[]?', state: 'string?', chatId: 'string?' } },
];
// ---------------------------------------------------------------------------

Loading…
Cancel
Save