memorio 5.1.4 → 5.2.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/AGENTS.md +21 -12
- package/CHANGELOG.md +18 -6
- package/README.md +44 -7
- package/SECURITY-HARDENING.md +258 -0
- package/SECURITY.md +67 -3
- package/SUMMARY.md +55 -59
- package/adr/002-observer-semantics.md +1 -1
- package/adr/003-deep-mutation-semantics.md +1 -1
- package/adr/004-array-mutation-semantics.md +1 -1
- package/adr/010-logic-phase-0.md +42 -0
- package/adr/README.md +16 -11
- package/bin/cli.js +82 -60
- package/examples/acquired-knowledge.ts +174 -0
- package/examples/agent-memory-demo.ts +140 -0
- package/examples/sync.ts +90 -90
- package/examples/useObserver.tsx +2 -2
- package/global.cjs +1995 -124
- package/global.js +1990 -125
- package/index.cjs +1995 -124
- package/index.d.ts +1 -0
- package/index.js +1990 -125
- package/llms.txt +122 -4
- package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
- package/markdown/LOGIC.md +100 -0
- package/markdown/MEMORY-ATTACHMENT.md +17 -10
- package/markdown/MEMORY.md +378 -15
- package/markdown/MEM_FORMAT.md +313 -0
- package/markdown/STATE.md +27 -3
- package/markdown/SYNC.md +18 -14
- package/markdown/TEMPORAL.md +297 -0
- package/markdown/USEOBSERVER.md +7 -4
- package/modules/redux.cjs +1159 -32
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +1158 -32
- package/modules/redux.js.map +1 -1
- package/package.json +14 -5
- package/types/exports.d.ts +47 -3
- package/types/logic.d.ts +79 -0
- package/types/memorio.d.ts +60 -16
- package/types/memory.d.ts +118 -0
- package/types/session.d.ts +1 -4
- package/types/store.d.ts +1 -4
- package/types/temporal.d.ts +95 -0
- package/types/useObserver.d.ts +6 -10
- package/vsix/memorio.vsix +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-memory-demo.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: An AI agent that remembers user preferences across conversations.
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates:
|
|
7
|
+
* - Conversation 1: agent remembers a user preference
|
|
8
|
+
* - Conversation 2: agent recalls the preference
|
|
9
|
+
* - Context generation: agent gets relevant context for a query
|
|
10
|
+
* - Temporal history: agent queries how state evolved
|
|
11
|
+
* - Provenance: agent explains why it knows what it knows
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createAgentMemory } from '../../agent'
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const agent = createAgentMemory('support-agent')
|
|
18
|
+
|
|
19
|
+
// Clear any prior state for a clean demo
|
|
20
|
+
await agent.clear()
|
|
21
|
+
|
|
22
|
+
// ─── Conversation 1 ───────────────────────────────────────────
|
|
23
|
+
console.log('=== Conversation 1 ===')
|
|
24
|
+
console.log('User: "I prefer PostgreSQL for databases."')
|
|
25
|
+
|
|
26
|
+
// Agent remembers the preference with metadata
|
|
27
|
+
await agent.remember('user.pref.database', 'PostgreSQL', {
|
|
28
|
+
type: 'preference',
|
|
29
|
+
confidence: 0.91,
|
|
30
|
+
source: 'conversation-1',
|
|
31
|
+
tags: ['database', 'infrastructure'],
|
|
32
|
+
ttl: null // persistent memory
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
console.log('Agent: "Got it. PostgreSQL it is."')
|
|
36
|
+
console.log('')
|
|
37
|
+
|
|
38
|
+
// ─── Conversation 2 ─────────────────────────────────────────
|
|
39
|
+
console.log('=== Conversation 2 (later) ===')
|
|
40
|
+
console.log('User: "What database did I say I preferred?"')
|
|
41
|
+
|
|
42
|
+
// Agent recalls the memory
|
|
43
|
+
const preference = await agent.recall('user.pref.database')
|
|
44
|
+
console.log(`Agent: "You said ${preference?.value}."`)
|
|
45
|
+
console.log('')
|
|
46
|
+
|
|
47
|
+
// ─── Context Generation ─────────────────────────────────────
|
|
48
|
+
console.log('=== Context for Agent Query ===')
|
|
49
|
+
console.log('User: "What databases do you know about for this project?"')
|
|
50
|
+
|
|
51
|
+
const context = await agent.context({
|
|
52
|
+
tags: ['database'],
|
|
53
|
+
types: ['preference', 'decision', 'fact'],
|
|
54
|
+
minConfidence: 0.5,
|
|
55
|
+
maxEntries: 10
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
console.log('Agent Context (relevant memories):')
|
|
59
|
+
for (const entry of context) {
|
|
60
|
+
console.log(` - ${entry.key}: ${entry.value}`)
|
|
61
|
+
console.log(` type: ${entry.type}, confidence: ${entry.confidence}, age: ${Math.round(entry.age / 1000)}s`)
|
|
62
|
+
console.log(` tags: ${entry.tags.join(', ')}`)
|
|
63
|
+
}
|
|
64
|
+
console.log('')
|
|
65
|
+
|
|
66
|
+
// ─── Memory Evolution (supersedence) ─────────────────────────
|
|
67
|
+
console.log('=== Memory Evolution ===')
|
|
68
|
+
|
|
69
|
+
// Update the preference (creates superseded entries)
|
|
70
|
+
await agent.remember('user.pref.database', 'MySQL', {
|
|
71
|
+
type: 'preference',
|
|
72
|
+
confidence: 0.85,
|
|
73
|
+
source: 'conversation-3',
|
|
74
|
+
tags: ['database', 'infrastructure']
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Small delay to ensure unique timestamps for superseded entries
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
79
|
+
|
|
80
|
+
await agent.remember('user.pref.database', 'SQLite', {
|
|
81
|
+
type: 'preference',
|
|
82
|
+
confidence: 0.92,
|
|
83
|
+
source: 'conversation-4',
|
|
84
|
+
tags: ['database', 'infrastructure']
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Query the latest state
|
|
88
|
+
const latest = await agent.recall('user.pref.database')
|
|
89
|
+
console.log(`Latest preference: ${latest?.value} (confidence: ${latest?.confidence})`)
|
|
90
|
+
|
|
91
|
+
// Query all entries including superseded
|
|
92
|
+
const mem = (globalThis as any).memorio.memory
|
|
93
|
+
const allEntries = await mem._loadAll()
|
|
94
|
+
const keyHistory = allEntries
|
|
95
|
+
.filter((e: any) => e.key === `agent:support-agent:user.pref.database`)
|
|
96
|
+
.sort((a: any, b: any) => a.createdAt - b.createdAt)
|
|
97
|
+
|
|
98
|
+
console.log('History of this decision:')
|
|
99
|
+
for (const entry of keyHistory) {
|
|
100
|
+
const statusIcon = entry.status === 'superseded' ? ' (superseded)' : ' (current)'
|
|
101
|
+
console.log(` - ${new Date(entry.createdAt).toISOString()}: ${entry.value}${statusIcon}`)
|
|
102
|
+
console.log(` source: ${entry.source}, confidence: ${entry.confidence}`)
|
|
103
|
+
}
|
|
104
|
+
console.log('')
|
|
105
|
+
|
|
106
|
+
// ─── Provenance / Explain ───────────────────────────────────
|
|
107
|
+
console.log('=== Provenance ===')
|
|
108
|
+
console.log('User: "Why do you know SQLite?"')
|
|
109
|
+
|
|
110
|
+
const current = await agent.recall('user.pref.database')
|
|
111
|
+
console.log(`Agent: "I know SQLite because:"`)
|
|
112
|
+
console.log(` - Source: ${current?.source}`)
|
|
113
|
+
console.log(` - Confidence: ${current?.confidence}`)
|
|
114
|
+
console.log(` - Stored at: ${new Date(current!.createdAt).toISOString()}`)
|
|
115
|
+
console.log(` - Tags: ${current?.tags.join(', ')}`)
|
|
116
|
+
console.log('')
|
|
117
|
+
|
|
118
|
+
// Show why we know about PostgreSQL (even though it was superseded)
|
|
119
|
+
const postgresqlEntry = keyHistory.find((e: any) => e.value === 'PostgreSQL')
|
|
120
|
+
if (postgresqlEntry) {
|
|
121
|
+
console.log(`Agent: "PostgreSQL was previously known because:"`)
|
|
122
|
+
console.log(` - Source: ${postgresqlEntry.source}`)
|
|
123
|
+
console.log(` - Confidence: ${postgresqlEntry.confidence}`)
|
|
124
|
+
console.log(` - Status: ${postgresqlEntry.status}`)
|
|
125
|
+
console.log(` - Stored at: ${new Date(postgresqlEntry.createdAt).toISOString()}`)
|
|
126
|
+
console.log(` - Tags: ${postgresqlEntry.tags.join(', ')}`)
|
|
127
|
+
}
|
|
128
|
+
console.log('')
|
|
129
|
+
|
|
130
|
+
// ─── Stats ─────────────────────────────────────────────────
|
|
131
|
+
console.log('=== Memory Stats ===')
|
|
132
|
+
const stats = await agent.stats()
|
|
133
|
+
console.log(`Total memories: ${stats.total}`)
|
|
134
|
+
console.log(`By scope:`, stats.byScope)
|
|
135
|
+
console.log(`By type:`, stats.byType)
|
|
136
|
+
|
|
137
|
+
console.log('\n=== Demo Complete ===')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main().catch(console.error)
|
package/examples/sync.ts
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sync.ts
|
|
3
|
-
*
|
|
4
|
-
* Scenario: an app using `memorio.memory` that wants to survive being
|
|
5
|
-
* offline (data is born local, in the journal, and always readable) and
|
|
6
|
-
* mirror itself to a backend when a connection is available.
|
|
7
|
-
*
|
|
8
|
-
* The cloud is a transport/persistence provider here, never the source of
|
|
9
|
-
* truth - sync mirrors the local journal, it doesn't replace it. Provide
|
|
10
|
-
* your own `SyncProvider`; memorio doesn't ship one.
|
|
11
|
-
*/
|
|
12
|
-
import { memorio } from 'memorio'
|
|
13
|
-
|
|
14
|
-
interface MemoryEntry {
|
|
15
|
-
key: string
|
|
16
|
-
value: unknown
|
|
17
|
-
confidence?: number
|
|
18
|
-
source?: string
|
|
19
|
-
scope?: '
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface SyncProvider {
|
|
23
|
-
push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
|
|
24
|
-
pull?(since?: number): Promise<MemoryEntry[]>
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const backendProvider: SyncProvider = {
|
|
29
|
-
push: ops =>
|
|
30
|
-
fetch('/api/sync', {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
body: JSON.stringify(ops),
|
|
33
|
-
headers: { 'Content-Type': 'application/json' }
|
|
34
|
-
}).then(r => r.json()),
|
|
35
|
-
|
|
36
|
-
pull: since => fetch(`/api/sync?since=${since ?? 0}`).then(r => r.json()),
|
|
37
|
-
|
|
38
|
-
// Higher-confidence entry wins locally; for 'shared' scope the provider
|
|
39
|
-
// (i.e. your backend) makes the final call instead.
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function configureSync(namespace: string) {
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
memorio.memory.configure({
|
|
47
|
-
namespace, // e.g. `user:123:device:abc` - partitions the local journal
|
|
48
|
-
provider: backendProvider,
|
|
49
|
-
auto: true // auto-replay the journal on focus/online (default: true)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
await memorio.memory.ready
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function rememberAndSync(key: string, value: unknown) {
|
|
56
|
-
// Local-first: this resolves immediately from the local store, no
|
|
57
|
-
// network round-trip required.
|
|
58
|
-
await memorio.memory.remember(key, value, { scope: '
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// The journal: the durable op log that drives cloud reconciliation
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
export async function inspectPendingOps() {
|
|
66
|
-
const pending = await memorio.memory.journal.pending()
|
|
67
|
-
console.debug('Operations waiting to sync:', pending)
|
|
68
|
-
return pending
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function forceReplay() {
|
|
72
|
-
// Pushes pending() to the provider, marks synced entries, and pulls if
|
|
73
|
-
// the provider implements it. Normally triggered automatically (`auto:
|
|
74
|
-
// true`) on focus/online - call this manually to sync on demand instead.
|
|
75
|
-
const ack = await memorio.memory.journal.replay()
|
|
76
|
-
console.debug('Sync result:', ack)
|
|
77
|
-
return ack
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function resetJournal() {
|
|
81
|
-
// Wipes the current namespace's journal only - does not touch the
|
|
82
|
-
// memory entries themselves, just the pending sync log.
|
|
83
|
-
await memorio.memory.journal.clear()
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Usage:
|
|
87
|
-
// await configureSync('user:123:device:abc')
|
|
88
|
-
// await rememberAndSync('user.language', 'Italian')
|
|
89
|
-
// await inspectPendingOps()
|
|
90
|
-
// await forceReplay()
|
|
1
|
+
/**
|
|
2
|
+
* sync.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: an app using `memorio.memory` that wants to survive being
|
|
5
|
+
* offline (data is born local, in the journal, and always readable) and
|
|
6
|
+
* mirror itself to a backend when a connection is available.
|
|
7
|
+
*
|
|
8
|
+
* The cloud is a transport/persistence provider here, never the source of
|
|
9
|
+
* truth - sync mirrors the local journal, it doesn't replace it. Provide
|
|
10
|
+
* your own `SyncProvider`; memorio doesn't ship one.
|
|
11
|
+
*/
|
|
12
|
+
import { memorio } from 'memorio'
|
|
13
|
+
|
|
14
|
+
interface MemoryEntry {
|
|
15
|
+
key: string
|
|
16
|
+
value: unknown
|
|
17
|
+
confidence?: number
|
|
18
|
+
source?: string
|
|
19
|
+
scope?: 'hot' | 'session' | 'local' | 'durable'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SyncProvider {
|
|
23
|
+
push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
|
|
24
|
+
pull?(since?: number): Promise<MemoryEntry[]>
|
|
25
|
+
resolveConflict?(local: MemoryEntry, remote: MemoryEntry): Promise<'local' | 'remote' | 'merge'>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const backendProvider: SyncProvider = {
|
|
29
|
+
push: ops =>
|
|
30
|
+
fetch('/api/sync', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
body: JSON.stringify(ops),
|
|
33
|
+
headers: { 'Content-Type': 'application/json' }
|
|
34
|
+
}).then(r => r.json()),
|
|
35
|
+
|
|
36
|
+
pull: since => fetch(`/api/sync?since=${since ?? 0}`).then(r => r.json()),
|
|
37
|
+
|
|
38
|
+
// Higher-confidence entry wins locally; for 'shared' scope the provider
|
|
39
|
+
// (i.e. your backend) makes the final call instead.
|
|
40
|
+
resolveConflict: async (local, remote) => (local.confidence ?? 0) >= 0.8 ? 'local' : 'remote'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function configureSync(namespace: string) {
|
|
44
|
+
// Namespace partitions the journal; only entries written while sync is
|
|
45
|
+
// configured (via configure()) flow through the provider below.
|
|
46
|
+
memorio.memory.configure({
|
|
47
|
+
namespace, // e.g. `user:123:device:abc` - partitions the local journal
|
|
48
|
+
provider: backendProvider,
|
|
49
|
+
auto: true // auto-replay the journal on focus/online (default: true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await memorio.memory.journal.status() // substrate is 'store'; journal is ready
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function rememberAndSync(key: string, value: unknown) {
|
|
56
|
+
// Local-first: this resolves immediately from the local store, no
|
|
57
|
+
// network round-trip required.
|
|
58
|
+
await memorio.memory.remember(key, value, { scope: 'local', source: 'app' })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// The journal: the durable op log that drives cloud reconciliation
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export async function inspectPendingOps() {
|
|
66
|
+
const pending = await memorio.memory.journal.pending()
|
|
67
|
+
console.debug('Operations waiting to sync:', pending)
|
|
68
|
+
return pending
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function forceReplay() {
|
|
72
|
+
// Pushes pending() to the provider, marks synced entries, and pulls if
|
|
73
|
+
// the provider implements it. Normally triggered automatically (`auto:
|
|
74
|
+
// true`) on focus/online - call this manually to sync on demand instead.
|
|
75
|
+
const ack = await memorio.memory.journal.replay()
|
|
76
|
+
console.debug('Sync result:', ack)
|
|
77
|
+
return ack
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function resetJournal() {
|
|
81
|
+
// Wipes the current namespace's journal only - does not touch the
|
|
82
|
+
// memory entries themselves, just the pending sync log.
|
|
83
|
+
await memorio.memory.journal.clear()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Usage:
|
|
87
|
+
// await configureSync('user:123:device:abc')
|
|
88
|
+
// await rememberAndSync('user.language', 'Italian')
|
|
89
|
+
// await inspectPendingOps()
|
|
90
|
+
// await forceReplay()
|
package/examples/useObserver.tsx
CHANGED
|
@@ -58,8 +58,8 @@ function AutoDiscovery() {
|
|
|
58
58
|
function SyncedComponent() {
|
|
59
59
|
const [localData, setLocalData] = useState(null)
|
|
60
60
|
|
|
61
|
-
useObserver((
|
|
62
|
-
setLocalData(
|
|
61
|
+
useObserver(() => {
|
|
62
|
+
setLocalData(state.data)
|
|
63
63
|
}, [state.data])
|
|
64
64
|
|
|
65
65
|
return <div>{localData}</div>
|