@illuma-ai/agents 1.1.25 → 1.3.0
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/cjs/agents/AgentContext.cjs +20 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/spawnPath.cjs +104 -0
- package/dist/cjs/common/spawnPath.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +87 -31
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/HandoffRegistry.cjs +143 -0
- package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -0
- package/dist/cjs/graphs/MultiAgentGraph.cjs +587 -184
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/phases/flushLoop.cjs +214 -0
- package/dist/cjs/graphs/phases/flushLoop.cjs.map +1 -0
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +102 -0
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/index.cjs +4 -3
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +115 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/memory/citations.cjs +69 -0
- package/dist/cjs/memory/citations.cjs.map +1 -0
- package/dist/cjs/memory/compositeBackend.cjs +60 -0
- package/dist/cjs/memory/compositeBackend.cjs.map +1 -0
- package/dist/cjs/memory/constants.cjs +232 -0
- package/dist/cjs/memory/constants.cjs.map +1 -0
- package/dist/cjs/memory/embeddings.cjs +151 -0
- package/dist/cjs/memory/embeddings.cjs.map +1 -0
- package/dist/cjs/memory/factory.cjs +95 -0
- package/dist/cjs/memory/factory.cjs.map +1 -0
- package/dist/cjs/memory/migrate.cjs +81 -0
- package/dist/cjs/memory/migrate.cjs.map +1 -0
- package/dist/cjs/memory/mmr.cjs +138 -0
- package/dist/cjs/memory/mmr.cjs.map +1 -0
- package/dist/cjs/memory/paths.cjs +217 -0
- package/dist/cjs/memory/paths.cjs.map +1 -0
- package/dist/cjs/memory/pgvectorStore.cjs +225 -0
- package/dist/cjs/memory/pgvectorStore.cjs.map +1 -0
- package/dist/cjs/memory/recallTracking.cjs +98 -0
- package/dist/cjs/memory/recallTracking.cjs.map +1 -0
- package/dist/cjs/memory/schema.sql +51 -0
- package/dist/cjs/memory/temporalDecay.cjs +118 -0
- package/dist/cjs/memory/temporalDecay.cjs.map +1 -0
- package/dist/cjs/nodes/ApprovalGateNode.cjs +1 -1
- package/dist/cjs/nodes/ApprovalGateNode.cjs.map +1 -1
- package/dist/cjs/prompts/memoryFlushPrompt.cjs +49 -0
- package/dist/cjs/prompts/memoryFlushPrompt.cjs.map +1 -0
- package/dist/cjs/run.cjs +16 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +4 -4
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/AskUser.cjs +6 -1
- package/dist/cjs/tools/AskUser.cjs.map +1 -1
- package/dist/cjs/tools/BrowserTools.cjs +1 -1
- package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +127 -10
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/approval/constants.cjs +2 -2
- package/dist/cjs/tools/approval/constants.cjs.map +1 -1
- package/dist/cjs/tools/memory/index.cjs +58 -0
- package/dist/cjs/tools/memory/index.cjs.map +1 -0
- package/dist/cjs/tools/memory/memoryAppendTool.cjs +69 -0
- package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/memoryGetTool.cjs +49 -0
- package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/memorySearchTool.cjs +65 -0
- package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/shared.cjs +106 -0
- package/dist/cjs/tools/memory/shared.cjs.map +1 -0
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/cjs/utils/childAgentContext.cjs +242 -0
- package/dist/cjs/utils/childAgentContext.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +36 -4
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/finishReasons.cjs +44 -0
- package/dist/cjs/utils/finishReasons.cjs.map +1 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/logging.cjs +34 -0
- package/dist/cjs/utils/logging.cjs.map +1 -0
- package/dist/cjs/utils/toolCallNormalization.cjs +250 -0
- package/dist/cjs/utils/toolCallNormalization.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +20 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/spawnPath.mjs +95 -0
- package/dist/esm/common/spawnPath.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +87 -31
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/HandoffRegistry.mjs +141 -0
- package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -0
- package/dist/esm/graphs/MultiAgentGraph.mjs +587 -184
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/phases/flushLoop.mjs +209 -0
- package/dist/esm/graphs/phases/flushLoop.mjs.map +1 -0
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs +99 -0
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -0
- package/dist/esm/llm/bedrock/index.mjs +4 -3
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +21 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/memory/citations.mjs +64 -0
- package/dist/esm/memory/citations.mjs.map +1 -0
- package/dist/esm/memory/compositeBackend.mjs +58 -0
- package/dist/esm/memory/compositeBackend.mjs.map +1 -0
- package/dist/esm/memory/constants.mjs +198 -0
- package/dist/esm/memory/constants.mjs.map +1 -0
- package/dist/esm/memory/embeddings.mjs +148 -0
- package/dist/esm/memory/embeddings.mjs.map +1 -0
- package/dist/esm/memory/factory.mjs +93 -0
- package/dist/esm/memory/factory.mjs.map +1 -0
- package/dist/esm/memory/migrate.mjs +78 -0
- package/dist/esm/memory/migrate.mjs.map +1 -0
- package/dist/esm/memory/mmr.mjs +130 -0
- package/dist/esm/memory/mmr.mjs.map +1 -0
- package/dist/esm/memory/paths.mjs +207 -0
- package/dist/esm/memory/paths.mjs.map +1 -0
- package/dist/esm/memory/pgvectorStore.mjs +223 -0
- package/dist/esm/memory/pgvectorStore.mjs.map +1 -0
- package/dist/esm/memory/recallTracking.mjs +94 -0
- package/dist/esm/memory/recallTracking.mjs.map +1 -0
- package/dist/esm/memory/schema.sql +51 -0
- package/dist/esm/memory/temporalDecay.mjs +110 -0
- package/dist/esm/memory/temporalDecay.mjs.map +1 -0
- package/dist/esm/nodes/ApprovalGateNode.mjs +1 -1
- package/dist/esm/nodes/ApprovalGateNode.mjs.map +1 -1
- package/dist/esm/prompts/memoryFlushPrompt.mjs +44 -0
- package/dist/esm/prompts/memoryFlushPrompt.mjs.map +1 -0
- package/dist/esm/run.mjs +16 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +4 -4
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/AskUser.mjs +6 -1
- package/dist/esm/tools/AskUser.mjs.map +1 -1
- package/dist/esm/tools/BrowserTools.mjs +1 -1
- package/dist/esm/tools/BrowserTools.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +128 -11
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/approval/constants.mjs +2 -2
- package/dist/esm/tools/approval/constants.mjs.map +1 -1
- package/dist/esm/tools/memory/index.mjs +46 -0
- package/dist/esm/tools/memory/index.mjs.map +1 -0
- package/dist/esm/tools/memory/memoryAppendTool.mjs +67 -0
- package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -0
- package/dist/esm/tools/memory/memoryGetTool.mjs +47 -0
- package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -0
- package/dist/esm/tools/memory/memorySearchTool.mjs +63 -0
- package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -0
- package/dist/esm/tools/memory/shared.mjs +98 -0
- package/dist/esm/tools/memory/shared.mjs.map +1 -0
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/esm/utils/childAgentContext.mjs +237 -0
- package/dist/esm/utils/childAgentContext.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +36 -5
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/finishReasons.mjs +41 -0
- package/dist/esm/utils/finishReasons.mjs.map +1 -0
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/logging.mjs +31 -0
- package/dist/esm/utils/logging.mjs.map +1 -0
- package/dist/esm/utils/toolCallNormalization.mjs +247 -0
- package/dist/esm/utils/toolCallNormalization.mjs.map +1 -0
- package/dist/types/common/index.d.ts +1 -0
- package/dist/types/common/spawnPath.d.ts +59 -0
- package/dist/types/graphs/HandoffRegistry.d.ts +97 -0
- package/dist/types/graphs/MultiAgentGraph.d.ts +58 -18
- package/dist/types/graphs/index.d.ts +1 -0
- package/dist/types/graphs/phases/flushLoop.d.ts +106 -0
- package/dist/types/graphs/phases/memoryFlushPhase.d.ts +100 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/memory/__tests__/mockBackend.d.ts +40 -0
- package/dist/types/memory/citations.d.ts +39 -0
- package/dist/types/memory/compositeBackend.d.ts +30 -0
- package/dist/types/memory/constants.d.ts +121 -0
- package/dist/types/memory/embeddings.d.ts +15 -0
- package/dist/types/memory/factory.d.ts +23 -0
- package/dist/types/memory/index.d.ts +21 -0
- package/dist/types/memory/migrate.d.ts +14 -0
- package/dist/types/memory/mmr.d.ts +50 -0
- package/dist/types/memory/paths.d.ts +107 -0
- package/dist/types/memory/pgvectorStore.d.ts +56 -0
- package/dist/types/memory/recallTracking.d.ts +30 -0
- package/dist/types/memory/temporalDecay.d.ts +53 -0
- package/dist/types/memory/types.d.ts +182 -0
- package/dist/types/prompts/memoryFlushPrompt.d.ts +54 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/AskUser.d.ts +1 -1
- package/dist/types/tools/BrowserTools.d.ts +2 -2
- package/dist/types/tools/approval/constants.d.ts +2 -2
- package/dist/types/tools/memory/index.d.ts +39 -0
- package/dist/types/tools/memory/memoryAppendTool.d.ts +27 -0
- package/dist/types/tools/memory/memoryGetTool.d.ts +22 -0
- package/dist/types/tools/memory/memorySearchTool.d.ts +22 -0
- package/dist/types/tools/memory/shared.d.ts +106 -0
- package/dist/types/types/graph.d.ts +16 -3
- package/dist/types/utils/childAgentContext.d.ts +99 -0
- package/dist/types/utils/events.d.ts +21 -0
- package/dist/types/utils/finishReasons.d.ts +32 -0
- package/dist/types/utils/logging.d.ts +2 -0
- package/dist/types/utils/toolCallNormalization.d.ts +44 -0
- package/package.json +6 -4
- package/src/agents/AgentContext.ts +26 -3
- package/src/common/__tests__/enum.test.ts +4 -2
- package/src/common/__tests__/spawnPath.test.ts +110 -0
- package/src/common/index.ts +1 -0
- package/src/common/spawnPath.ts +101 -0
- package/src/graphs/Graph.ts +94 -43
- package/src/graphs/HandoffRegistry.ts +199 -0
- package/src/graphs/MultiAgentGraph.ts +694 -226
- package/src/graphs/__tests__/HandoffRegistry.test.ts +410 -0
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +61 -16
- package/src/graphs/__tests__/multi-agent-edges.test.ts +4 -2
- package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +221 -0
- package/src/graphs/__tests__/structured-output.integration.test.ts +212 -118
- package/src/graphs/contextManagement.e2e.test.ts +1 -1
- package/src/graphs/index.ts +1 -0
- package/src/graphs/phases/__tests__/flushLoop.test.ts +264 -0
- package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +37 -0
- package/src/graphs/phases/__tests__/runMemoryFlush.test.ts +150 -0
- package/src/graphs/phases/flushLoop.ts +303 -0
- package/src/graphs/phases/memoryFlushPhase.ts +209 -0
- package/src/index.ts +30 -1
- package/src/llm/bedrock/index.ts +4 -5
- package/src/memory/__tests__/citations.test.ts +61 -0
- package/src/memory/__tests__/compositeBackend.test.ts +79 -0
- package/src/memory/__tests__/isolation.test.ts +206 -0
- package/src/memory/__tests__/mmr.test.ts +148 -0
- package/src/memory/__tests__/mockBackend.ts +161 -0
- package/src/memory/__tests__/paths.test.ts +168 -0
- package/src/memory/__tests__/recallTracking.test.ts +96 -0
- package/src/memory/__tests__/temporalDecay.test.ts +151 -0
- package/src/memory/citations.ts +80 -0
- package/src/memory/compositeBackend.ts +99 -0
- package/src/memory/constants.ts +229 -0
- package/src/memory/embeddings.ts +188 -0
- package/src/memory/factory.ts +111 -0
- package/src/memory/index.ts +46 -0
- package/src/memory/migrate.ts +116 -0
- package/src/memory/mmr.ts +161 -0
- package/src/memory/paths.ts +258 -0
- package/src/memory/pgvectorStore.ts +324 -0
- package/src/memory/recallTracking.ts +127 -0
- package/src/memory/schema.sql +51 -0
- package/src/memory/temporalDecay.ts +134 -0
- package/src/memory/types.ts +185 -0
- package/src/nodes/ApprovalGateNode.ts +4 -10
- package/src/nodes/__tests__/ApprovalGateNode.test.ts +11 -20
- package/src/prompts/memoryFlushPrompt.ts +78 -0
- package/src/run.ts +17 -6
- package/src/scripts/test-bedrock-handoff-autonomous.ts +56 -20
- package/src/specs/agent-handoffs-bedrock.integration.test.ts +8 -5
- package/src/specs/agent-handoffs.test.ts +8 -2
- package/src/stream.ts +4 -6
- package/src/tools/AskUser.ts +7 -2
- package/src/tools/BrowserTools.ts +3 -5
- package/src/tools/ToolNode.ts +150 -13
- package/src/tools/__tests__/ToolApproval.test.ts +22 -9
- package/src/tools/approval/__tests__/constants.test.ts +4 -4
- package/src/tools/approval/constants.ts +2 -2
- package/src/tools/memory/__tests__/memoryTools.test.ts +205 -0
- package/src/tools/memory/index.ts +96 -0
- package/src/tools/memory/memoryAppendTool.ts +101 -0
- package/src/tools/memory/memoryGetTool.ts +53 -0
- package/src/tools/memory/memorySearchTool.ts +80 -0
- package/src/tools/memory/shared.ts +169 -0
- package/src/tools/search/search.test.ts +6 -1
- package/src/types/graph.ts +16 -3
- package/src/utils/__tests__/childAgentContext.test.ts +217 -0
- package/src/utils/__tests__/finishReasons.test.ts +55 -0
- package/src/utils/__tests__/toolCallNormalization.test.ts +181 -0
- package/src/utils/childAgentContext.ts +259 -0
- package/src/utils/events.ts +37 -4
- package/src/utils/finishReasons.ts +40 -0
- package/src/utils/llm.ts +0 -1
- package/src/utils/logging.ts +45 -8
- package/src/utils/toolCallNormalization.ts +271 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var constants = require('./constants.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Autonomous memory — canonical path whitelist + tier utilities.
|
|
7
|
+
*
|
|
8
|
+
* Single source of truth for the 8 stable memory documents. Every write
|
|
9
|
+
* goes through {@link assertWritablePath}; every reader can ask
|
|
10
|
+
* {@link getTierForPath} what a row belongs to. Adding or removing a
|
|
11
|
+
* path means editing this file — and exactly this file.
|
|
12
|
+
*
|
|
13
|
+
* ## Why a whitelist?
|
|
14
|
+
*
|
|
15
|
+
* Earlier upstream-faithful designs used date-keyed files
|
|
16
|
+
* (`memory/YYYY-MM-DD.md`), which have three problems for a persistent
|
|
17
|
+
* multi-user agent:
|
|
18
|
+
*
|
|
19
|
+
* 1. **Unbounded growth** — one row per day per agent per user, forever.
|
|
20
|
+
* 2. **LLM routing ambiguity** — "which date file do I read?" has no
|
|
21
|
+
* good answer, so the model reads (and ranks against) all of them.
|
|
22
|
+
* 3. **No natural deduplication** — the same fact written across three
|
|
23
|
+
* days returns three near-identical hits.
|
|
24
|
+
*
|
|
25
|
+
* With 8 stable canonical documents the LLM knows *exactly* where to
|
|
26
|
+
* write ("is this a preference? → `memory/user/preferences.md`") and
|
|
27
|
+
* each row accumulates via UPSERT instead of piling up new rows.
|
|
28
|
+
*
|
|
29
|
+
* ## Two tiers
|
|
30
|
+
*
|
|
31
|
+
* - **Agent tier** (`memory/agent/*`) — operational knowledge that
|
|
32
|
+
* every caller of the agent benefits from. Stored with `user_id = NULL`.
|
|
33
|
+
* Shared across all users of a collaborative agent; the only tier that
|
|
34
|
+
* exists for isolated/autonomous agents with no invoker.
|
|
35
|
+
*
|
|
36
|
+
* - **User tier** (`memory/user/*`) — per-invoker personalization.
|
|
37
|
+
* Stored with `user_id = <caller>`. Read path filters so User A never
|
|
38
|
+
* sees User B's rows, even for the same agent. Not writable unless the
|
|
39
|
+
* flush scope carries a non-empty `userId`.
|
|
40
|
+
*
|
|
41
|
+
* The tier of a path is a function of its prefix (`memory/agent/` vs
|
|
42
|
+
* `memory/user/`), so adding a new document is one line here + the
|
|
43
|
+
* constant array entry; no store or route code has to change.
|
|
44
|
+
*/
|
|
45
|
+
/** Agent-tier documents — shared across all users of the agent. */
|
|
46
|
+
const MEMORY_AGENT_PATHS = Object.freeze([
|
|
47
|
+
Object.freeze({
|
|
48
|
+
path: 'memory/agent/playbook.md',
|
|
49
|
+
tier: 'agent',
|
|
50
|
+
label: 'Playbook',
|
|
51
|
+
description: 'Successful task patterns and workflows that have worked for this agent — ' +
|
|
52
|
+
'reusable recipes, known-good tool sequences, approaches that consistently ' +
|
|
53
|
+
'produce the right result.',
|
|
54
|
+
}),
|
|
55
|
+
Object.freeze({
|
|
56
|
+
path: 'memory/agent/pitfalls.md',
|
|
57
|
+
tier: 'agent',
|
|
58
|
+
label: 'Pitfalls',
|
|
59
|
+
description: 'Tool failures, error recoveries, schema mistakes, and invalid argument ' +
|
|
60
|
+
'shapes the agent has seen — so the same mistake is not repeated on future ' +
|
|
61
|
+
'turns. One note per distinct failure mode.',
|
|
62
|
+
}),
|
|
63
|
+
Object.freeze({
|
|
64
|
+
path: 'memory/agent/domain.md',
|
|
65
|
+
tier: 'agent',
|
|
66
|
+
label: 'Domain',
|
|
67
|
+
description: 'Durable facts about the systems, APIs, data models, and business rules ' +
|
|
68
|
+
'this agent operates on — things that are true independent of any one user ' +
|
|
69
|
+
'and that the agent repeatedly needs to know.',
|
|
70
|
+
}),
|
|
71
|
+
Object.freeze({
|
|
72
|
+
path: 'memory/agent/style.md',
|
|
73
|
+
tier: 'agent',
|
|
74
|
+
label: 'Style',
|
|
75
|
+
description: 'Tone, formatting, and response-shape conventions the agent has converged ' +
|
|
76
|
+
'on across the whole user base. Do NOT write user-specific preferences here ' +
|
|
77
|
+
'(those belong under the user tier).',
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
/** User-tier documents — per-invoker, never shared across users. */
|
|
81
|
+
const MEMORY_USER_PATHS = Object.freeze([
|
|
82
|
+
Object.freeze({
|
|
83
|
+
path: 'memory/user/profile.md',
|
|
84
|
+
tier: 'user',
|
|
85
|
+
label: 'Profile',
|
|
86
|
+
description: "This specific user's identity — name, role, team, sign-off name, " +
|
|
87
|
+
'responsibilities, stable facts about who they are. Only facts ' +
|
|
88
|
+
'volunteered by the user themselves.',
|
|
89
|
+
}),
|
|
90
|
+
Object.freeze({
|
|
91
|
+
path: 'memory/user/preferences.md',
|
|
92
|
+
tier: 'user',
|
|
93
|
+
label: 'Preferences',
|
|
94
|
+
description: 'How THIS user wants things done — preferred formats, verbosity, tone, ' +
|
|
95
|
+
'cadence, output length, language conventions. Explicit corrections they ' +
|
|
96
|
+
'have given count as preferences.',
|
|
97
|
+
}),
|
|
98
|
+
Object.freeze({
|
|
99
|
+
path: 'memory/user/projects.md',
|
|
100
|
+
tier: 'user',
|
|
101
|
+
label: 'Projects',
|
|
102
|
+
description: "This user's current initiatives, ongoing work, deadlines, and active " +
|
|
103
|
+
'project context. Include dates in the prose when relevant. Retire entries ' +
|
|
104
|
+
'by overwriting them on future flushes when the user indicates a project is done.',
|
|
105
|
+
}),
|
|
106
|
+
Object.freeze({
|
|
107
|
+
path: 'memory/user/references.md',
|
|
108
|
+
tier: 'user',
|
|
109
|
+
label: 'References',
|
|
110
|
+
description: 'External systems, dashboards, accounts, links, channels, and resources ' +
|
|
111
|
+
"THIS user has pointed the agent at — the 'where to look' pointers that " +
|
|
112
|
+
'make later turns more efficient.',
|
|
113
|
+
}),
|
|
114
|
+
]);
|
|
115
|
+
/** All 8 canonical documents in display order: agent tier first, user tier second. */
|
|
116
|
+
const MEMORY_ALL_PATHS = Object.freeze([
|
|
117
|
+
...MEMORY_AGENT_PATHS,
|
|
118
|
+
...MEMORY_USER_PATHS,
|
|
119
|
+
]);
|
|
120
|
+
/** Fast O(1) lookup map: path → descriptor. */
|
|
121
|
+
const PATH_INDEX = new Map(MEMORY_ALL_PATHS.map((p) => [p.path, p]));
|
|
122
|
+
/** Set of all whitelisted paths — for `has()` lookups. */
|
|
123
|
+
const MEMORY_WRITABLE_PATHS = new Set(MEMORY_ALL_PATHS.map((p) => p.path));
|
|
124
|
+
/** Returns the descriptor for a canonical path, or `undefined` if not on the whitelist. */
|
|
125
|
+
function getPathDescriptor(path) {
|
|
126
|
+
return PATH_INDEX.get(path);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns the tier a given path belongs to.
|
|
130
|
+
*
|
|
131
|
+
* For unknown paths (e.g. legacy date-keyed rows that predate this schema),
|
|
132
|
+
* falls back to inspecting the `memory/agent/` or `memory/user/` prefix.
|
|
133
|
+
* Anything that matches neither is classified as `agent` — it will surface
|
|
134
|
+
* in the admin UI under the agent-tier header and stay read-only there.
|
|
135
|
+
*/
|
|
136
|
+
function getTierForPath(path) {
|
|
137
|
+
const descriptor = PATH_INDEX.get(path);
|
|
138
|
+
if (descriptor)
|
|
139
|
+
return descriptor.tier;
|
|
140
|
+
if (path.startsWith('memory/user/'))
|
|
141
|
+
return 'user';
|
|
142
|
+
return 'agent';
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Paths that are legal to write **for this caller's scope**.
|
|
146
|
+
*
|
|
147
|
+
* If `scope.userId` is absent (isolated/autonomous agent), the user-tier
|
|
148
|
+
* paths are dropped. This is the list the flush prompt renders into its
|
|
149
|
+
* rubric — the LLM never sees paths it cannot write to.
|
|
150
|
+
*/
|
|
151
|
+
function getWritablePathsForScope(scope) {
|
|
152
|
+
if (scope.userId == null || scope.userId === '') {
|
|
153
|
+
return MEMORY_AGENT_PATHS;
|
|
154
|
+
}
|
|
155
|
+
return MEMORY_ALL_PATHS;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Validates a write path against the whitelist AND the caller's scope.
|
|
159
|
+
*
|
|
160
|
+
* Throws with an actionable message when:
|
|
161
|
+
* - the path is missing the `memory/` prefix
|
|
162
|
+
* - the path is not on the 8-doc whitelist
|
|
163
|
+
* - the path is user-tier but `scope.userId` is missing
|
|
164
|
+
*
|
|
165
|
+
* The store's append() is the single call site; other callers should
|
|
166
|
+
* never hit this directly. Kept as a standalone function for testability.
|
|
167
|
+
*/
|
|
168
|
+
function assertWritablePath(path, scope) {
|
|
169
|
+
if (!path || !path.startsWith(constants.MEMORY_PATH_PREFIX)) {
|
|
170
|
+
throw new Error(`memory_append path must start with "${constants.MEMORY_PATH_PREFIX}" (received "${path}")`);
|
|
171
|
+
}
|
|
172
|
+
const descriptor = PATH_INDEX.get(path);
|
|
173
|
+
if (!descriptor) {
|
|
174
|
+
throw new Error(`memory_append path "${path}" is not on the canonical whitelist. ` +
|
|
175
|
+
`Allowed: ${MEMORY_ALL_PATHS.map((p) => p.path).join(', ')}`);
|
|
176
|
+
}
|
|
177
|
+
if (descriptor.tier === 'user' &&
|
|
178
|
+
(scope.userId == null || scope.userId === '')) {
|
|
179
|
+
throw new Error(`memory_append to user-tier path "${path}" requires a caller userId in scope, ` +
|
|
180
|
+
`but scope.userId is missing. This path is private to a specific user and ` +
|
|
181
|
+
`cannot be written from an isolated/autonomous context.`);
|
|
182
|
+
}
|
|
183
|
+
return descriptor;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Renders the whitelist as a compact bullet list, used inside the flush
|
|
187
|
+
* prompt so the LLM sees the authoritative rubric at inference time.
|
|
188
|
+
*
|
|
189
|
+
* Output shape:
|
|
190
|
+
* - memory/agent/playbook.md [Playbook, shared] — <description>
|
|
191
|
+
* - memory/agent/pitfalls.md [Pitfalls, shared] — <description>
|
|
192
|
+
* ...
|
|
193
|
+
*
|
|
194
|
+
* The `[Label, shared|private]` tag tells the model whether the doc is
|
|
195
|
+
* visible to all users (agent tier) or only to the caller (user tier).
|
|
196
|
+
* Filtered to only the paths writable for the given scope.
|
|
197
|
+
*/
|
|
198
|
+
function renderPathsRubric(scope) {
|
|
199
|
+
const writable = getWritablePathsForScope(scope);
|
|
200
|
+
return writable
|
|
201
|
+
.map((p) => {
|
|
202
|
+
const visibility = p.tier === 'agent' ? 'shared' : 'private';
|
|
203
|
+
return `- ${p.path} [${p.label}, ${visibility}] — ${p.description}`;
|
|
204
|
+
})
|
|
205
|
+
.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
exports.MEMORY_AGENT_PATHS = MEMORY_AGENT_PATHS;
|
|
209
|
+
exports.MEMORY_ALL_PATHS = MEMORY_ALL_PATHS;
|
|
210
|
+
exports.MEMORY_USER_PATHS = MEMORY_USER_PATHS;
|
|
211
|
+
exports.MEMORY_WRITABLE_PATHS = MEMORY_WRITABLE_PATHS;
|
|
212
|
+
exports.assertWritablePath = assertWritablePath;
|
|
213
|
+
exports.getPathDescriptor = getPathDescriptor;
|
|
214
|
+
exports.getTierForPath = getTierForPath;
|
|
215
|
+
exports.getWritablePathsForScope = getWritablePathsForScope;
|
|
216
|
+
exports.renderPathsRubric = renderPathsRubric;
|
|
217
|
+
//# sourceMappingURL=paths.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.cjs","sources":["../../../src/memory/paths.ts"],"sourcesContent":["/**\n * Autonomous memory — canonical path whitelist + tier utilities.\n *\n * Single source of truth for the 8 stable memory documents. Every write\n * goes through {@link assertWritablePath}; every reader can ask\n * {@link getTierForPath} what a row belongs to. Adding or removing a\n * path means editing this file — and exactly this file.\n *\n * ## Why a whitelist?\n *\n * Earlier upstream-faithful designs used date-keyed files\n * (`memory/YYYY-MM-DD.md`), which have three problems for a persistent\n * multi-user agent:\n *\n * 1. **Unbounded growth** — one row per day per agent per user, forever.\n * 2. **LLM routing ambiguity** — \"which date file do I read?\" has no\n * good answer, so the model reads (and ranks against) all of them.\n * 3. **No natural deduplication** — the same fact written across three\n * days returns three near-identical hits.\n *\n * With 8 stable canonical documents the LLM knows *exactly* where to\n * write (\"is this a preference? → `memory/user/preferences.md`\") and\n * each row accumulates via UPSERT instead of piling up new rows.\n *\n * ## Two tiers\n *\n * - **Agent tier** (`memory/agent/*`) — operational knowledge that\n * every caller of the agent benefits from. Stored with `user_id = NULL`.\n * Shared across all users of a collaborative agent; the only tier that\n * exists for isolated/autonomous agents with no invoker.\n *\n * - **User tier** (`memory/user/*`) — per-invoker personalization.\n * Stored with `user_id = <caller>`. Read path filters so User A never\n * sees User B's rows, even for the same agent. Not writable unless the\n * flush scope carries a non-empty `userId`.\n *\n * The tier of a path is a function of its prefix (`memory/agent/` vs\n * `memory/user/`), so adding a new document is one line here + the\n * constant array entry; no store or route code has to change.\n */\n\nimport { MEMORY_PATH_PREFIX } from './constants';\nimport type { MemoryScope } from './types';\n\n/** Tier discriminator — the two kinds of memory a row can belong to. */\nexport type MemoryTier = 'agent' | 'user';\n\n/** Every canonical document the flush phase is allowed to write. */\nexport interface MemoryPathDescriptor {\n /** Full path including the `memory/` prefix. */\n path: string;\n /** Which tier the path lives under. */\n tier: MemoryTier;\n /** Short label shown in UI tier badges and the flush prompt rubric. */\n label: string;\n /** One-line description — fed to the LLM to make routing unambiguous. */\n description: string;\n}\n\n/** Agent-tier documents — shared across all users of the agent. */\nexport const MEMORY_AGENT_PATHS: readonly MemoryPathDescriptor[] =\n Object.freeze([\n Object.freeze({\n path: 'memory/agent/playbook.md',\n tier: 'agent' as const,\n label: 'Playbook',\n description:\n 'Successful task patterns and workflows that have worked for this agent — ' +\n 'reusable recipes, known-good tool sequences, approaches that consistently ' +\n 'produce the right result.',\n }),\n Object.freeze({\n path: 'memory/agent/pitfalls.md',\n tier: 'agent' as const,\n label: 'Pitfalls',\n description:\n 'Tool failures, error recoveries, schema mistakes, and invalid argument ' +\n 'shapes the agent has seen — so the same mistake is not repeated on future ' +\n 'turns. One note per distinct failure mode.',\n }),\n Object.freeze({\n path: 'memory/agent/domain.md',\n tier: 'agent' as const,\n label: 'Domain',\n description:\n 'Durable facts about the systems, APIs, data models, and business rules ' +\n 'this agent operates on — things that are true independent of any one user ' +\n 'and that the agent repeatedly needs to know.',\n }),\n Object.freeze({\n path: 'memory/agent/style.md',\n tier: 'agent' as const,\n label: 'Style',\n description:\n 'Tone, formatting, and response-shape conventions the agent has converged ' +\n 'on across the whole user base. Do NOT write user-specific preferences here ' +\n '(those belong under the user tier).',\n }),\n ]);\n\n/** User-tier documents — per-invoker, never shared across users. */\nexport const MEMORY_USER_PATHS: readonly MemoryPathDescriptor[] = Object.freeze(\n [\n Object.freeze({\n path: 'memory/user/profile.md',\n tier: 'user' as const,\n label: 'Profile',\n description:\n \"This specific user's identity — name, role, team, sign-off name, \" +\n 'responsibilities, stable facts about who they are. Only facts ' +\n 'volunteered by the user themselves.',\n }),\n Object.freeze({\n path: 'memory/user/preferences.md',\n tier: 'user' as const,\n label: 'Preferences',\n description:\n 'How THIS user wants things done — preferred formats, verbosity, tone, ' +\n 'cadence, output length, language conventions. Explicit corrections they ' +\n 'have given count as preferences.',\n }),\n Object.freeze({\n path: 'memory/user/projects.md',\n tier: 'user' as const,\n label: 'Projects',\n description:\n \"This user's current initiatives, ongoing work, deadlines, and active \" +\n 'project context. Include dates in the prose when relevant. Retire entries ' +\n 'by overwriting them on future flushes when the user indicates a project is done.',\n }),\n Object.freeze({\n path: 'memory/user/references.md',\n tier: 'user' as const,\n label: 'References',\n description:\n 'External systems, dashboards, accounts, links, channels, and resources ' +\n \"THIS user has pointed the agent at — the 'where to look' pointers that \" +\n 'make later turns more efficient.',\n }),\n ]\n);\n\n/** All 8 canonical documents in display order: agent tier first, user tier second. */\nexport const MEMORY_ALL_PATHS: readonly MemoryPathDescriptor[] = Object.freeze([\n ...MEMORY_AGENT_PATHS,\n ...MEMORY_USER_PATHS,\n]);\n\n/** Fast O(1) lookup map: path → descriptor. */\nconst PATH_INDEX: ReadonlyMap<string, MemoryPathDescriptor> = new Map(\n MEMORY_ALL_PATHS.map((p) => [p.path, p])\n);\n\n/** Set of all whitelisted paths — for `has()` lookups. */\nexport const MEMORY_WRITABLE_PATHS: ReadonlySet<string> = new Set(\n MEMORY_ALL_PATHS.map((p) => p.path)\n);\n\n/** Returns the descriptor for a canonical path, or `undefined` if not on the whitelist. */\nexport function getPathDescriptor(\n path: string\n): MemoryPathDescriptor | undefined {\n return PATH_INDEX.get(path);\n}\n\n/**\n * Returns the tier a given path belongs to.\n *\n * For unknown paths (e.g. legacy date-keyed rows that predate this schema),\n * falls back to inspecting the `memory/agent/` or `memory/user/` prefix.\n * Anything that matches neither is classified as `agent` — it will surface\n * in the admin UI under the agent-tier header and stay read-only there.\n */\nexport function getTierForPath(path: string): MemoryTier {\n const descriptor = PATH_INDEX.get(path);\n if (descriptor) return descriptor.tier;\n if (path.startsWith('memory/user/')) return 'user';\n return 'agent';\n}\n\n/**\n * Paths that are legal to write **for this caller's scope**.\n *\n * If `scope.userId` is absent (isolated/autonomous agent), the user-tier\n * paths are dropped. This is the list the flush prompt renders into its\n * rubric — the LLM never sees paths it cannot write to.\n */\nexport function getWritablePathsForScope(\n scope: Pick<MemoryScope, 'userId'>\n): readonly MemoryPathDescriptor[] {\n if (scope.userId == null || scope.userId === '') {\n return MEMORY_AGENT_PATHS;\n }\n return MEMORY_ALL_PATHS;\n}\n\n/**\n * Validates a write path against the whitelist AND the caller's scope.\n *\n * Throws with an actionable message when:\n * - the path is missing the `memory/` prefix\n * - the path is not on the 8-doc whitelist\n * - the path is user-tier but `scope.userId` is missing\n *\n * The store's append() is the single call site; other callers should\n * never hit this directly. Kept as a standalone function for testability.\n */\nexport function assertWritablePath(\n path: string,\n scope: Pick<MemoryScope, 'userId'>\n): MemoryPathDescriptor {\n if (!path || !path.startsWith(MEMORY_PATH_PREFIX)) {\n throw new Error(\n `memory_append path must start with \"${MEMORY_PATH_PREFIX}\" (received \"${path}\")`\n );\n }\n const descriptor = PATH_INDEX.get(path);\n if (!descriptor) {\n throw new Error(\n `memory_append path \"${path}\" is not on the canonical whitelist. ` +\n `Allowed: ${MEMORY_ALL_PATHS.map((p) => p.path).join(', ')}`\n );\n }\n if (\n descriptor.tier === 'user' &&\n (scope.userId == null || scope.userId === '')\n ) {\n throw new Error(\n `memory_append to user-tier path \"${path}\" requires a caller userId in scope, ` +\n `but scope.userId is missing. This path is private to a specific user and ` +\n `cannot be written from an isolated/autonomous context.`\n );\n }\n return descriptor;\n}\n\n/**\n * Renders the whitelist as a compact bullet list, used inside the flush\n * prompt so the LLM sees the authoritative rubric at inference time.\n *\n * Output shape:\n * - memory/agent/playbook.md [Playbook, shared] — <description>\n * - memory/agent/pitfalls.md [Pitfalls, shared] — <description>\n * ...\n *\n * The `[Label, shared|private]` tag tells the model whether the doc is\n * visible to all users (agent tier) or only to the caller (user tier).\n * Filtered to only the paths writable for the given scope.\n */\nexport function renderPathsRubric(scope: Pick<MemoryScope, 'userId'>): string {\n const writable = getWritablePathsForScope(scope);\n return writable\n .map((p) => {\n const visibility = p.tier === 'agent' ? 'shared' : 'private';\n return `- ${p.path} [${p.label}, ${visibility}] — ${p.description}`;\n })\n .join('\\n');\n}\n"],"names":["MEMORY_PATH_PREFIX"],"mappings":"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"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var constants = require('./constants.cjs');
|
|
4
|
+
var embeddings = require('./embeddings.cjs');
|
|
5
|
+
var mmr = require('./mmr.cjs');
|
|
6
|
+
var temporalDecay = require('./temporalDecay.cjs');
|
|
7
|
+
var citations = require('./citations.cjs');
|
|
8
|
+
var paths = require('./paths.cjs');
|
|
9
|
+
|
|
10
|
+
function assertScope(scope) {
|
|
11
|
+
if (!scope || !scope.agentId) {
|
|
12
|
+
throw new Error('MemoryScope { agentId } is required — agentId must be non-empty');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** pgvector literal format: "[0.1,0.2,...]". */
|
|
16
|
+
function toVectorLiteral(vec) {
|
|
17
|
+
return `[${vec.join(',')}]`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Normalize caller userId for the layered read filter.
|
|
21
|
+
*
|
|
22
|
+
* The SQL filter is `(user_id IS NULL OR user_id = $2)`, so an empty
|
|
23
|
+
* string from the caller must not match rows whose user_id was set.
|
|
24
|
+
* We coerce empty/null/undefined to `null`, and pg treats `$2 = null`
|
|
25
|
+
* as `false` — which is exactly what we want for isolated callers:
|
|
26
|
+
* they see only agent-tier rows and nothing in the user tier.
|
|
27
|
+
*/
|
|
28
|
+
function normalizeCallerId(scope) {
|
|
29
|
+
const raw = scope.userId;
|
|
30
|
+
if (raw == null || raw === '')
|
|
31
|
+
return null;
|
|
32
|
+
return String(raw);
|
|
33
|
+
}
|
|
34
|
+
class PgvectorMemoryStore {
|
|
35
|
+
kind = 'vector';
|
|
36
|
+
pool;
|
|
37
|
+
table;
|
|
38
|
+
embedder;
|
|
39
|
+
constructor(opts) {
|
|
40
|
+
this.pool = opts.pool;
|
|
41
|
+
this.table = opts.table ?? constants.DEFAULT_MEMORY_TABLE;
|
|
42
|
+
this.embedder = opts.embedder ?? embeddings.getMemoryEmbedder();
|
|
43
|
+
}
|
|
44
|
+
async search(scope, query, opts = {}) {
|
|
45
|
+
assertScope(scope);
|
|
46
|
+
const trimmed = query.trim();
|
|
47
|
+
if (!trimmed)
|
|
48
|
+
return [];
|
|
49
|
+
const maxResults = Math.max(1, opts.maxResults ?? constants.DEFAULT_MAX_SEARCH_RESULTS);
|
|
50
|
+
const minScore = opts.minScore ?? constants.DEFAULT_MIN_SCORE;
|
|
51
|
+
const vector = await this.embedder.embed(trimmed);
|
|
52
|
+
const vectorLiteral = toVectorLiteral(vector);
|
|
53
|
+
const callerId = normalizeCallerId(scope);
|
|
54
|
+
// [memory-layered-search] debug: layered scope filter
|
|
55
|
+
// agent-tier rows (user_id IS NULL) + this caller's user-tier rows.
|
|
56
|
+
// Another user's rows are invisible at the SQL layer.
|
|
57
|
+
const sql = `
|
|
58
|
+
WITH scored AS (
|
|
59
|
+
SELECT
|
|
60
|
+
id,
|
|
61
|
+
path,
|
|
62
|
+
content,
|
|
63
|
+
created_at,
|
|
64
|
+
user_id,
|
|
65
|
+
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
66
|
+
ts_rank(tsv, plainto_tsquery('english', $2)) AS text_score
|
|
67
|
+
FROM ${this.table}
|
|
68
|
+
WHERE agent_id = $3
|
|
69
|
+
AND (user_id IS NULL OR user_id = $4)
|
|
70
|
+
)
|
|
71
|
+
SELECT
|
|
72
|
+
id,
|
|
73
|
+
path,
|
|
74
|
+
content,
|
|
75
|
+
created_at,
|
|
76
|
+
user_id,
|
|
77
|
+
(${constants.HYBRID_VECTOR_WEIGHT} * vector_score + ${constants.HYBRID_TEXT_WEIGHT} * text_score) AS score
|
|
78
|
+
FROM scored
|
|
79
|
+
WHERE (${constants.HYBRID_VECTOR_WEIGHT} * vector_score + ${constants.HYBRID_TEXT_WEIGHT} * text_score) >= $5
|
|
80
|
+
ORDER BY score DESC
|
|
81
|
+
LIMIT $6
|
|
82
|
+
`;
|
|
83
|
+
const { rows } = await this.pool.query(sql, [
|
|
84
|
+
vectorLiteral,
|
|
85
|
+
trimmed,
|
|
86
|
+
scope.agentId,
|
|
87
|
+
callerId,
|
|
88
|
+
minScore,
|
|
89
|
+
maxResults,
|
|
90
|
+
]);
|
|
91
|
+
let hits = rows.map((row) => ({
|
|
92
|
+
id: String(row.id),
|
|
93
|
+
path: row.path,
|
|
94
|
+
content: row.content,
|
|
95
|
+
createdAt: new Date(row.created_at),
|
|
96
|
+
score: Number(row.score),
|
|
97
|
+
source: 'vector',
|
|
98
|
+
tier: paths.getTierForPath(row.path),
|
|
99
|
+
}));
|
|
100
|
+
// Phase 2: temporal decay (before MMR so diversity sees post-decay ranks)
|
|
101
|
+
if (opts.temporalDecay?.enabled) {
|
|
102
|
+
hits = temporalDecay.applyTemporalDecayToHits(hits, opts.temporalDecay);
|
|
103
|
+
hits.sort((a, b) => b.score - a.score);
|
|
104
|
+
}
|
|
105
|
+
// Phase 2: MMR reranking
|
|
106
|
+
if (opts.mmr?.enabled) {
|
|
107
|
+
hits = mmr.applyMMRToMemoryHits(hits, opts.mmr);
|
|
108
|
+
}
|
|
109
|
+
// Phase 2: citations (decorate last — mutates content with Source: trailer)
|
|
110
|
+
const citationsMode = opts.citations ?? 'auto';
|
|
111
|
+
if (citations.shouldIncludeCitations(citationsMode)) {
|
|
112
|
+
hits = citations.decorateCitations(hits, true);
|
|
113
|
+
}
|
|
114
|
+
return hits;
|
|
115
|
+
}
|
|
116
|
+
async get(scope, opts) {
|
|
117
|
+
assertScope(scope);
|
|
118
|
+
if (!opts.path)
|
|
119
|
+
return null;
|
|
120
|
+
const callerId = normalizeCallerId(scope);
|
|
121
|
+
const tier = paths.getTierForPath(opts.path);
|
|
122
|
+
// Agent-tier rows always live under user_id=NULL. User-tier rows
|
|
123
|
+
// always carry the caller's id. Querying with a precise predicate
|
|
124
|
+
// is faster than leaving it open AND guarantees a user cannot
|
|
125
|
+
// read another user's row even if they know the path by heart.
|
|
126
|
+
const userClause = tier === 'agent' ? 'user_id IS NULL' : 'user_id = $3';
|
|
127
|
+
const params = [scope.agentId, opts.path];
|
|
128
|
+
if (tier === 'user')
|
|
129
|
+
params.push(callerId);
|
|
130
|
+
const sql = `
|
|
131
|
+
SELECT content
|
|
132
|
+
FROM ${this.table}
|
|
133
|
+
WHERE agent_id = $1 AND path = $2 AND ${userClause}
|
|
134
|
+
ORDER BY updated_at DESC
|
|
135
|
+
LIMIT 1
|
|
136
|
+
`;
|
|
137
|
+
const { rows } = await this.pool.query(sql, params);
|
|
138
|
+
if (rows.length === 0)
|
|
139
|
+
return null;
|
|
140
|
+
const text = String(rows[0].content ?? '');
|
|
141
|
+
if (opts.from == null && opts.lines == null) {
|
|
142
|
+
return { path: opts.path, text };
|
|
143
|
+
}
|
|
144
|
+
const allLines = text.split('\n');
|
|
145
|
+
const fromIdx = Math.max(0, (opts.from ?? 1) - 1);
|
|
146
|
+
const count = Math.max(0, opts.lines ?? allLines.length - fromIdx);
|
|
147
|
+
const slice = allLines.slice(fromIdx, fromIdx + count).join('\n');
|
|
148
|
+
return { path: opts.path, text: slice };
|
|
149
|
+
}
|
|
150
|
+
async append(scope, input) {
|
|
151
|
+
assertScope(scope);
|
|
152
|
+
// Whitelist + tier + scope-compatibility check in one call. Throws
|
|
153
|
+
// with an actionable message for each failure mode.
|
|
154
|
+
const descriptor = paths.assertWritablePath(input.path, scope);
|
|
155
|
+
const content = input.content.trim();
|
|
156
|
+
if (!content) {
|
|
157
|
+
throw new Error('memory_append content must be non-empty');
|
|
158
|
+
}
|
|
159
|
+
// Tier determines the row's user_id:
|
|
160
|
+
// agent tier → NULL (shared across all users)
|
|
161
|
+
// user tier → the caller's id (assertWritablePath guarantees non-empty)
|
|
162
|
+
const rowUserId = descriptor.tier === 'agent' ? null : String(scope.userId);
|
|
163
|
+
const provenance = scope.userId != null ? String(scope.userId) : null;
|
|
164
|
+
// Read the existing row (if any) for THIS tier so we can embed the
|
|
165
|
+
// merged content. Agent-tier merges regardless of caller; user-tier
|
|
166
|
+
// merges only within the caller's own row.
|
|
167
|
+
const lookupSql = `
|
|
168
|
+
SELECT content FROM ${this.table}
|
|
169
|
+
WHERE agent_id = $1 AND path = $2 AND ${rowUserId === null ? 'user_id IS NULL' : 'user_id = $3'}
|
|
170
|
+
LIMIT 1
|
|
171
|
+
`;
|
|
172
|
+
const lookupParams = rowUserId === null
|
|
173
|
+
? [scope.agentId, input.path]
|
|
174
|
+
: [scope.agentId, input.path, rowUserId];
|
|
175
|
+
const existing = await this.pool.query(lookupSql, lookupParams);
|
|
176
|
+
const priorContent = existing.rows.length > 0 ? String(existing.rows[0].content ?? '') : '';
|
|
177
|
+
const mergedContent = priorContent
|
|
178
|
+
? `${priorContent.replace(/\s+$/, '')}\n\n${content}`
|
|
179
|
+
: content;
|
|
180
|
+
const vector = await this.embedder.embed(mergedContent);
|
|
181
|
+
const vectorLiteral = toVectorLiteral(vector);
|
|
182
|
+
// UPSERT on (agent_id, user_id, path) with NULLS NOT DISTINCT so
|
|
183
|
+
// two NULL user_ids collide on the same agent+path (exactly one
|
|
184
|
+
// agent-tier row) while per-user rows on the same path coexist.
|
|
185
|
+
//
|
|
186
|
+
// Provenance note: `last_user_id` always records WHO wrote the
|
|
187
|
+
// latest append, even for agent-tier rows where the row's own
|
|
188
|
+
// `user_id` stays NULL. That gives the admin UI an audit trail
|
|
189
|
+
// ("agent-tier row last updated by Alice") without changing the
|
|
190
|
+
// scoping semantics.
|
|
191
|
+
const upsertSql = `
|
|
192
|
+
INSERT INTO ${this.table} (agent_id, user_id, path, content, embedding, last_user_id, updated_at)
|
|
193
|
+
VALUES ($1, $2, $3, $4, $5::vector, $6, NOW())
|
|
194
|
+
ON CONFLICT (agent_id, user_id, path) DO UPDATE
|
|
195
|
+
SET content = EXCLUDED.content,
|
|
196
|
+
embedding = EXCLUDED.embedding,
|
|
197
|
+
last_user_id = EXCLUDED.last_user_id,
|
|
198
|
+
updated_at = NOW()
|
|
199
|
+
`;
|
|
200
|
+
await this.pool.query(upsertSql, [
|
|
201
|
+
scope.agentId,
|
|
202
|
+
rowUserId,
|
|
203
|
+
input.path,
|
|
204
|
+
mergedContent,
|
|
205
|
+
vectorLiteral,
|
|
206
|
+
provenance,
|
|
207
|
+
]);
|
|
208
|
+
}
|
|
209
|
+
async health() {
|
|
210
|
+
try {
|
|
211
|
+
await this.pool.query('SELECT 1');
|
|
212
|
+
return { ok: true, backend: 'vector' };
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
backend: 'vector',
|
|
218
|
+
error: err instanceof Error ? err.message : String(err),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
exports.PgvectorMemoryStore = PgvectorMemoryStore;
|
|
225
|
+
//# sourceMappingURL=pgvectorStore.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pgvectorStore.cjs","sources":["../../../src/memory/pgvectorStore.ts"],"sourcesContent":["/**\n * Postgres + pgvector implementation of {@link MemoryBackend}.\n *\n * ## Scoping model (two-tier, layered)\n *\n * Every read query applies the layered filter\n *\n * WHERE agent_id = $1 AND (user_id IS NULL OR user_id = $2)\n *\n * so the caller sees:\n * - agent-tier rows (`user_id IS NULL`) — shared operational knowledge,\n * visible to every user of the agent\n * - their own user-tier rows (`user_id = <caller>`) — private per-user\n * personalization\n *\n * Another user's user-tier rows are invisible — the privacy boundary is\n * enforced in SQL, not just in the UI or route layer.\n *\n * ## Writes\n *\n * `append()` routes to a row based on the path's tier, resolved via\n * {@link assertWritablePath}:\n *\n * - `memory/agent/*` → stored with `user_id = NULL` regardless of\n * what scope the caller passed. Agent-tier content is inherently\n * shared; scoping it per-user would defeat the point.\n * - `memory/user/*` → stored with `user_id = scope.userId`. A missing\n * `scope.userId` throws — user-tier paths cannot be written from\n * isolated/autonomous contexts.\n *\n * UPSERT key is `(agent_id, user_id, path)` with `NULLS NOT DISTINCT`,\n * so each user gets their own `user/preferences.md` row and there is\n * exactly one `agent/playbook.md` row shared across the whole user base.\n * Content accumulates via `\\n\\n` concatenation on conflict, with the\n * embedding regenerated over the merged content so search stays\n * consistent.\n */\nimport type { Pool } from 'pg';\nimport {\n DEFAULT_MAX_SEARCH_RESULTS,\n DEFAULT_MEMORY_TABLE,\n DEFAULT_MIN_SCORE,\n HYBRID_TEXT_WEIGHT,\n HYBRID_VECTOR_WEIGHT,\n} from './constants';\nimport { getMemoryEmbedder, type EmbeddingProvider } from './embeddings';\nimport { applyMMRToMemoryHits } from './mmr';\nimport { applyTemporalDecayToHits } from './temporalDecay';\nimport { decorateCitations, shouldIncludeCitations } from './citations';\nimport { assertWritablePath, getTierForPath } from './paths';\nimport type {\n MemoryAppendInput,\n MemoryBackend,\n MemoryEntry,\n MemoryGetOptions,\n MemoryHealth,\n MemoryReadResult,\n MemoryScope,\n MemorySearchOptions,\n} from './types';\n\nexport interface PgvectorStoreOptions {\n pool: Pool;\n table?: string;\n embedder?: EmbeddingProvider;\n}\n\nfunction assertScope(scope: MemoryScope): void {\n if (!scope || !scope.agentId) {\n throw new Error(\n 'MemoryScope { agentId } is required — agentId must be non-empty'\n );\n }\n}\n\n/** pgvector literal format: \"[0.1,0.2,...]\". */\nfunction toVectorLiteral(vec: number[]): string {\n return `[${vec.join(',')}]`;\n}\n\n/**\n * Normalize caller userId for the layered read filter.\n *\n * The SQL filter is `(user_id IS NULL OR user_id = $2)`, so an empty\n * string from the caller must not match rows whose user_id was set.\n * We coerce empty/null/undefined to `null`, and pg treats `$2 = null`\n * as `false` — which is exactly what we want for isolated callers:\n * they see only agent-tier rows and nothing in the user tier.\n */\nfunction normalizeCallerId(scope: MemoryScope): string | null {\n const raw = scope.userId;\n if (raw == null || raw === '') return null;\n return String(raw);\n}\n\nexport class PgvectorMemoryStore implements MemoryBackend {\n readonly kind = 'vector' as const;\n private pool: Pool;\n private table: string;\n private embedder: EmbeddingProvider;\n\n constructor(opts: PgvectorStoreOptions) {\n this.pool = opts.pool;\n this.table = opts.table ?? DEFAULT_MEMORY_TABLE;\n this.embedder = opts.embedder ?? getMemoryEmbedder();\n }\n\n async search(\n scope: MemoryScope,\n query: string,\n opts: MemorySearchOptions = {}\n ): Promise<MemoryEntry[]> {\n assertScope(scope);\n const trimmed = query.trim();\n if (!trimmed) return [];\n\n const maxResults = Math.max(\n 1,\n opts.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS\n );\n const minScore = opts.minScore ?? DEFAULT_MIN_SCORE;\n\n const vector = await this.embedder.embed(trimmed);\n const vectorLiteral = toVectorLiteral(vector);\n const callerId = normalizeCallerId(scope);\n\n // [memory-layered-search] debug: layered scope filter\n // agent-tier rows (user_id IS NULL) + this caller's user-tier rows.\n // Another user's rows are invisible at the SQL layer.\n const sql = `\n WITH scored AS (\n SELECT\n id,\n path,\n content,\n created_at,\n user_id,\n (1 - (embedding <=> $1::vector)) AS vector_score,\n ts_rank(tsv, plainto_tsquery('english', $2)) AS text_score\n FROM ${this.table}\n WHERE agent_id = $3\n AND (user_id IS NULL OR user_id = $4)\n )\n SELECT\n id,\n path,\n content,\n created_at,\n user_id,\n (${HYBRID_VECTOR_WEIGHT} * vector_score + ${HYBRID_TEXT_WEIGHT} * text_score) AS score\n FROM scored\n WHERE (${HYBRID_VECTOR_WEIGHT} * vector_score + ${HYBRID_TEXT_WEIGHT} * text_score) >= $5\n ORDER BY score DESC\n LIMIT $6\n `;\n\n const { rows } = await this.pool.query(sql, [\n vectorLiteral,\n trimmed,\n scope.agentId,\n callerId,\n minScore,\n maxResults,\n ]);\n\n let hits: MemoryEntry[] = rows.map(\n (row: {\n id: string | number;\n path: string;\n content: string;\n created_at: Date;\n user_id: string | null;\n score: string | number;\n }): MemoryEntry => ({\n id: String(row.id),\n path: row.path,\n content: row.content,\n createdAt: new Date(row.created_at),\n score: Number(row.score),\n source: 'vector',\n tier: getTierForPath(row.path),\n })\n );\n\n // Phase 2: temporal decay (before MMR so diversity sees post-decay ranks)\n if (opts.temporalDecay?.enabled) {\n hits = applyTemporalDecayToHits(hits, opts.temporalDecay);\n hits.sort((a, b) => b.score - a.score);\n }\n\n // Phase 2: MMR reranking\n if (opts.mmr?.enabled) {\n hits = applyMMRToMemoryHits(hits, opts.mmr);\n }\n\n // Phase 2: citations (decorate last — mutates content with Source: trailer)\n const citationsMode = opts.citations ?? 'auto';\n if (shouldIncludeCitations(citationsMode)) {\n hits = decorateCitations(hits, true);\n }\n\n return hits;\n }\n\n async get(\n scope: MemoryScope,\n opts: MemoryGetOptions\n ): Promise<MemoryReadResult | null> {\n assertScope(scope);\n if (!opts.path) return null;\n\n const callerId = normalizeCallerId(scope);\n const tier = getTierForPath(opts.path);\n\n // Agent-tier rows always live under user_id=NULL. User-tier rows\n // always carry the caller's id. Querying with a precise predicate\n // is faster than leaving it open AND guarantees a user cannot\n // read another user's row even if they know the path by heart.\n const userClause = tier === 'agent' ? 'user_id IS NULL' : 'user_id = $3';\n\n const params: unknown[] = [scope.agentId, opts.path];\n if (tier === 'user') params.push(callerId);\n\n const sql = `\n SELECT content\n FROM ${this.table}\n WHERE agent_id = $1 AND path = $2 AND ${userClause}\n ORDER BY updated_at DESC\n LIMIT 1\n `;\n const { rows } = await this.pool.query(sql, params);\n if (rows.length === 0) return null;\n\n const text = String(rows[0].content ?? '');\n if (opts.from == null && opts.lines == null) {\n return { path: opts.path, text };\n }\n\n const allLines = text.split('\\n');\n const fromIdx = Math.max(0, (opts.from ?? 1) - 1);\n const count = Math.max(0, opts.lines ?? allLines.length - fromIdx);\n const slice = allLines.slice(fromIdx, fromIdx + count).join('\\n');\n return { path: opts.path, text: slice };\n }\n\n async append(scope: MemoryScope, input: MemoryAppendInput): Promise<void> {\n assertScope(scope);\n // Whitelist + tier + scope-compatibility check in one call. Throws\n // with an actionable message for each failure mode.\n const descriptor = assertWritablePath(input.path, scope);\n const content = input.content.trim();\n if (!content) {\n throw new Error('memory_append content must be non-empty');\n }\n\n // Tier determines the row's user_id:\n // agent tier → NULL (shared across all users)\n // user tier → the caller's id (assertWritablePath guarantees non-empty)\n const rowUserId = descriptor.tier === 'agent' ? null : String(scope.userId);\n const provenance = scope.userId != null ? String(scope.userId) : null;\n\n // Read the existing row (if any) for THIS tier so we can embed the\n // merged content. Agent-tier merges regardless of caller; user-tier\n // merges only within the caller's own row.\n const lookupSql = `\n SELECT content FROM ${this.table}\n WHERE agent_id = $1 AND path = $2 AND ${rowUserId === null ? 'user_id IS NULL' : 'user_id = $3'}\n LIMIT 1\n `;\n const lookupParams: unknown[] =\n rowUserId === null\n ? [scope.agentId, input.path]\n : [scope.agentId, input.path, rowUserId];\n const existing = await this.pool.query(lookupSql, lookupParams);\n const priorContent: string =\n existing.rows.length > 0 ? String(existing.rows[0].content ?? '') : '';\n const mergedContent = priorContent\n ? `${priorContent.replace(/\\s+$/, '')}\\n\\n${content}`\n : content;\n\n const vector = await this.embedder.embed(mergedContent);\n const vectorLiteral = toVectorLiteral(vector);\n\n // UPSERT on (agent_id, user_id, path) with NULLS NOT DISTINCT so\n // two NULL user_ids collide on the same agent+path (exactly one\n // agent-tier row) while per-user rows on the same path coexist.\n //\n // Provenance note: `last_user_id` always records WHO wrote the\n // latest append, even for agent-tier rows where the row's own\n // `user_id` stays NULL. That gives the admin UI an audit trail\n // (\"agent-tier row last updated by Alice\") without changing the\n // scoping semantics.\n const upsertSql = `\n INSERT INTO ${this.table} (agent_id, user_id, path, content, embedding, last_user_id, updated_at)\n VALUES ($1, $2, $3, $4, $5::vector, $6, NOW())\n ON CONFLICT (agent_id, user_id, path) DO UPDATE\n SET content = EXCLUDED.content,\n embedding = EXCLUDED.embedding,\n last_user_id = EXCLUDED.last_user_id,\n updated_at = NOW()\n `;\n await this.pool.query(upsertSql, [\n scope.agentId,\n rowUserId,\n input.path,\n mergedContent,\n vectorLiteral,\n provenance,\n ]);\n }\n\n async health(): Promise<MemoryHealth> {\n try {\n await this.pool.query('SELECT 1');\n return { ok: true, backend: 'vector' };\n } catch (err) {\n return {\n ok: false,\n backend: 'vector',\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"],"names":["DEFAULT_MEMORY_TABLE","getMemoryEmbedder","DEFAULT_MAX_SEARCH_RESULTS","DEFAULT_MIN_SCORE","HYBRID_VECTOR_WEIGHT","HYBRID_TEXT_WEIGHT","getTierForPath","applyTemporalDecayToHits","applyMMRToMemoryHits","shouldIncludeCitations","decorateCitations","assertWritablePath"],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Recall tracking — Phase 2.
|
|
7
|
+
*
|
|
8
|
+
* Lightweight adaptation of upstream
|
|
9
|
+
* `extensions/memory-core/src/short-term-promotion.ts::recordShortTermRecalls`.
|
|
10
|
+
* Upstream stores recalls in a JSON file under `memory/.dreams/`; we store
|
|
11
|
+
* them in a Postgres table `agent_memory_recalls`. Schema captures what the
|
|
12
|
+
* future Phase 3 dreaming/promotion algorithm will need:
|
|
13
|
+
* - which memory row was surfaced (`memory_id`)
|
|
14
|
+
* - the query that surfaced it (raw + SHA-256 hash for dedupe)
|
|
15
|
+
* - hybrid score at the time of recall
|
|
16
|
+
* - the day bucket (for per-day dedupe / frequency counting)
|
|
17
|
+
* - the recorded timestamp
|
|
18
|
+
*
|
|
19
|
+
* Best-effort: failures never block memory_search. The caller fires
|
|
20
|
+
* {@link RecallTracker.record} without awaiting the result and ignores errors.
|
|
21
|
+
*/
|
|
22
|
+
const RECALL_TABLE = 'agent_memory_recalls';
|
|
23
|
+
function hashQuery(query) {
|
|
24
|
+
return crypto.createHash('sha256')
|
|
25
|
+
.update(query.trim().toLowerCase())
|
|
26
|
+
.digest('hex')
|
|
27
|
+
.slice(0, 32);
|
|
28
|
+
}
|
|
29
|
+
function dayBucket(nowMs) {
|
|
30
|
+
const d = new Date(nowMs);
|
|
31
|
+
const y = d.getUTCFullYear();
|
|
32
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
33
|
+
const day = String(d.getUTCDate()).padStart(2, '0');
|
|
34
|
+
return `${y}-${m}-${day}`;
|
|
35
|
+
}
|
|
36
|
+
class PgvectorRecallTracker {
|
|
37
|
+
pool;
|
|
38
|
+
table;
|
|
39
|
+
constructor(pool, table = RECALL_TABLE) {
|
|
40
|
+
this.pool = pool;
|
|
41
|
+
this.table = table;
|
|
42
|
+
}
|
|
43
|
+
async migrate() {
|
|
44
|
+
// [recall-tracking] debug: create table + indexes if missing
|
|
45
|
+
await this.pool.query(`
|
|
46
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
47
|
+
id BIGSERIAL PRIMARY KEY,
|
|
48
|
+
agent_id TEXT NOT NULL,
|
|
49
|
+
memory_id TEXT NOT NULL,
|
|
50
|
+
memory_path TEXT NOT NULL,
|
|
51
|
+
query TEXT NOT NULL,
|
|
52
|
+
query_hash TEXT NOT NULL,
|
|
53
|
+
score DOUBLE PRECISION NOT NULL,
|
|
54
|
+
day_bucket TEXT NOT NULL,
|
|
55
|
+
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
56
|
+
)
|
|
57
|
+
`);
|
|
58
|
+
await this.pool.query(`CREATE INDEX IF NOT EXISTS ${this.table}_agent_day_idx ON ${this.table} (agent_id, day_bucket)`);
|
|
59
|
+
await this.pool.query(`CREATE INDEX IF NOT EXISTS ${this.table}_memory_idx ON ${this.table} (agent_id, memory_id)`);
|
|
60
|
+
await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${this.table}_dedupe_idx
|
|
61
|
+
ON ${this.table} (agent_id, memory_id, query_hash, day_bucket)`);
|
|
62
|
+
}
|
|
63
|
+
async record(params) {
|
|
64
|
+
if (!params.agentId || !params.query.trim() || params.hits.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
67
|
+
const qhash = hashQuery(params.query);
|
|
68
|
+
const bucket = dayBucket(nowMs);
|
|
69
|
+
// [recall-tracking] debug: upsert one row per (agent, memory, query, day)
|
|
70
|
+
// Upstream dedupes per-day per-query so repeated searches don't inflate counts.
|
|
71
|
+
const values = [];
|
|
72
|
+
const args = [];
|
|
73
|
+
let i = 1;
|
|
74
|
+
for (const hit of params.hits) {
|
|
75
|
+
values.push(`($${i++}, $${i++}, $${i++}, $${i++}, $${i++}, $${i++}, $${i++}, NOW())`);
|
|
76
|
+
args.push(params.agentId, hit.id, hit.path, params.query, qhash, hit.score, bucket);
|
|
77
|
+
}
|
|
78
|
+
const sql = `
|
|
79
|
+
INSERT INTO ${this.table}
|
|
80
|
+
(agent_id, memory_id, memory_path, query, query_hash, score, day_bucket, recorded_at)
|
|
81
|
+
VALUES ${values.join(', ')}
|
|
82
|
+
ON CONFLICT (agent_id, memory_id, query_hash, day_bucket) DO UPDATE
|
|
83
|
+
SET score = GREATEST(${this.table}.score, EXCLUDED.score),
|
|
84
|
+
recorded_at = NOW()
|
|
85
|
+
`;
|
|
86
|
+
await this.pool.query(sql, args);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** No-op tracker — used when recall tracking is disabled or the backend isn't pgvector. */
|
|
90
|
+
class NullRecallTracker {
|
|
91
|
+
async record() { }
|
|
92
|
+
async migrate() { }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exports.NullRecallTracker = NullRecallTracker;
|
|
96
|
+
exports.PgvectorRecallTracker = PgvectorRecallTracker;
|
|
97
|
+
exports.RECALL_TABLE = RECALL_TABLE;
|
|
98
|
+
//# sourceMappingURL=recallTracking.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recallTracking.cjs","sources":["../../../src/memory/recallTracking.ts"],"sourcesContent":["/**\n * Recall tracking — Phase 2.\n *\n * Lightweight adaptation of upstream\n * `extensions/memory-core/src/short-term-promotion.ts::recordShortTermRecalls`.\n * Upstream stores recalls in a JSON file under `memory/.dreams/`; we store\n * them in a Postgres table `agent_memory_recalls`. Schema captures what the\n * future Phase 3 dreaming/promotion algorithm will need:\n * - which memory row was surfaced (`memory_id`)\n * - the query that surfaced it (raw + SHA-256 hash for dedupe)\n * - hybrid score at the time of recall\n * - the day bucket (for per-day dedupe / frequency counting)\n * - the recorded timestamp\n *\n * Best-effort: failures never block memory_search. The caller fires\n * {@link RecallTracker.record} without awaiting the result and ignores errors.\n */\nimport { createHash } from 'crypto';\nimport type { Pool } from 'pg';\n\nexport interface RecallTracker {\n /** Record that the given memory ids were surfaced to the model for a query. */\n record(params: RecallRecordParams): Promise<void>;\n /** Backend-specific schema migration. Idempotent. */\n migrate(): Promise<void>;\n}\n\nexport interface RecallRecordParams {\n agentId: string;\n query: string;\n hits: Array<{ id: string; path: string; score: number }>;\n nowMs?: number;\n}\n\nexport const RECALL_TABLE = 'agent_memory_recalls';\n\nfunction hashQuery(query: string): string {\n return createHash('sha256')\n .update(query.trim().toLowerCase())\n .digest('hex')\n .slice(0, 32);\n}\n\nfunction dayBucket(nowMs: number): string {\n const d = new Date(nowMs);\n const y = d.getUTCFullYear();\n const m = String(d.getUTCMonth() + 1).padStart(2, '0');\n const day = String(d.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${day}`;\n}\n\nexport class PgvectorRecallTracker implements RecallTracker {\n constructor(\n private readonly pool: Pool,\n private readonly table: string = RECALL_TABLE\n ) {}\n\n async migrate(): Promise<void> {\n // [recall-tracking] debug: create table + indexes if missing\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${this.table} (\n id BIGSERIAL PRIMARY KEY,\n agent_id TEXT NOT NULL,\n memory_id TEXT NOT NULL,\n memory_path TEXT NOT NULL,\n query TEXT NOT NULL,\n query_hash TEXT NOT NULL,\n score DOUBLE PRECISION NOT NULL,\n day_bucket TEXT NOT NULL,\n recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n )\n `);\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_agent_day_idx ON ${this.table} (agent_id, day_bucket)`\n );\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_memory_idx ON ${this.table} (agent_id, memory_id)`\n );\n await this.pool.query(\n `CREATE UNIQUE INDEX IF NOT EXISTS ${this.table}_dedupe_idx\n ON ${this.table} (agent_id, memory_id, query_hash, day_bucket)`\n );\n }\n\n async record(params: RecallRecordParams): Promise<void> {\n if (!params.agentId || !params.query.trim() || params.hits.length === 0)\n return;\n const nowMs = params.nowMs ?? Date.now();\n const qhash = hashQuery(params.query);\n const bucket = dayBucket(nowMs);\n\n // [recall-tracking] debug: upsert one row per (agent, memory, query, day)\n // Upstream dedupes per-day per-query so repeated searches don't inflate counts.\n const values: string[] = [];\n const args: unknown[] = [];\n let i = 1;\n for (const hit of params.hits) {\n values.push(\n `($${i++}, $${i++}, $${i++}, $${i++}, $${i++}, $${i++}, $${i++}, NOW())`\n );\n args.push(\n params.agentId,\n hit.id,\n hit.path,\n params.query,\n qhash,\n hit.score,\n bucket\n );\n }\n const sql = `\n INSERT INTO ${this.table}\n (agent_id, memory_id, memory_path, query, query_hash, score, day_bucket, recorded_at)\n VALUES ${values.join(', ')}\n ON CONFLICT (agent_id, memory_id, query_hash, day_bucket) DO UPDATE\n SET score = GREATEST(${this.table}.score, EXCLUDED.score),\n recorded_at = NOW()\n `;\n await this.pool.query(sql, args);\n }\n}\n\n/** No-op tracker — used when recall tracking is disabled or the backend isn't pgvector. */\nexport class NullRecallTracker implements RecallTracker {\n async record(): Promise<void> {}\n async migrate(): Promise<void> {}\n}\n"],"names":["createHash"],"mappings":"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"}
|