memorio 5.1.3 → 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.
Files changed (48) hide show
  1. package/AGENTS.md +22 -13
  2. package/CHANGELOG.md +24 -9
  3. package/README.md +61 -19
  4. package/SECURITY-HARDENING.md +258 -0
  5. package/SECURITY.md +67 -3
  6. package/SUMMARY.md +55 -59
  7. package/adr/002-observer-semantics.md +1 -1
  8. package/adr/003-deep-mutation-semantics.md +1 -1
  9. package/adr/004-array-mutation-semantics.md +1 -1
  10. package/adr/010-logic-phase-0.md +42 -0
  11. package/adr/README.md +16 -11
  12. package/bin/cli.js +82 -60
  13. package/examples/acquired-knowledge.ts +174 -0
  14. package/examples/agent-memory-demo.ts +140 -0
  15. package/examples/sqlite-batched-writes.ts +60 -60
  16. package/examples/sync.ts +90 -90
  17. package/examples/useObserver.tsx +2 -2
  18. package/global.cjs +2026 -115
  19. package/global.js +2021 -116
  20. package/index.cjs +2026 -115
  21. package/index.d.ts +1 -0
  22. package/index.js +2021 -116
  23. package/llms.txt +135 -4
  24. package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
  25. package/markdown/LOGIC.md +100 -0
  26. package/markdown/MEMORY-ATTACHMENT.md +17 -10
  27. package/markdown/MEMORY.md +404 -14
  28. package/markdown/MEM_FORMAT.md +313 -0
  29. package/markdown/SQLITE.md +2 -2
  30. package/markdown/STATE.md +27 -3
  31. package/markdown/SYNC.md +19 -15
  32. package/markdown/TEMPORAL.md +297 -0
  33. package/markdown/USEOBSERVER.md +7 -4
  34. package/modules/redux.cjs +1188 -38
  35. package/modules/redux.cjs.map +1 -1
  36. package/modules/redux.js +1187 -38
  37. package/modules/redux.js.map +1 -1
  38. package/package.json +14 -5
  39. package/types/exports.d.ts +47 -3
  40. package/types/logic.d.ts +79 -0
  41. package/types/memorio.d.ts +60 -16
  42. package/types/memory.d.ts +118 -0
  43. package/types/session.d.ts +1 -4
  44. package/types/state.d.ts +19 -5
  45. package/types/store.d.ts +1 -4
  46. package/types/temporal.d.ts +95 -0
  47. package/types/useObserver.d.ts +6 -10
  48. 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)
@@ -1,60 +1,60 @@
1
- /**
2
- * sqlite-batched-writes.ts
3
- *
4
- * Scenario: importing or writing many rows into memorio's sqlite layer.
5
- *
6
- * With persistence enabled, each write is recorded in an append-only journal
7
- * (incremental). Periodic checkpoints — every 50 operations or 250 ms of
8
- * write-idle — take a full database snapshot. Calling `flush()` inside a
9
- * per-row loop triggers a checkpoint on every iteration, which is expensive.
10
- */
11
- import { memorio } from 'memorio'
12
-
13
- interface UserRow {
14
- id: number
15
- name: string
16
- role: string
17
- }
18
-
19
- export async function importUsers(users: UserRow[]) {
20
- await memorio.sqlite.ready
21
- await memorio.sqlite.db.create('app', { persistence: true })
22
-
23
- await memorio.sqlite.query.run(
24
- 'app',
25
- `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
26
- )
27
-
28
- // Bad: this would trigger a full checkpoint after every single insert.
29
- //
30
- // for (const user of users) {
31
- // await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
32
- // await memorio.sqlite.db.flush('app') // <- don't do this per row
33
- // }
34
-
35
- // Good: run every insert first, flush once after the batch. Writes are
36
- // journaled incrementally (cheap); the checkpoint (full snapshot) happens
37
- // once at the end.
38
- for (const user of users) {
39
- await memorio.sqlite.query.run(
40
- 'app',
41
- `INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
42
- [user.id, user.name, user.role]
43
- )
44
- }
45
-
46
- // Flush explicitly, once, after the whole batch — check the installed
47
- // version's API for the exact flush/persist call name if it differs from
48
- // automatic persistence-on-close.
49
- const admins = await memorio.sqlite.query.select(
50
- 'app',
51
- `SELECT * FROM users WHERE role = ?`,
52
- ['admin']
53
- )
54
-
55
- return admins
56
- }
57
-
58
- // For read-heavy or scratch-space use where durability doesn't matter,
59
- // skip { persistence: true } entirely - sqlite runs in memory by default,
60
- // which avoids journal and checkpoint overhead altogether.
1
+ /**
2
+ * sqlite-batched-writes.ts
3
+ *
4
+ * Scenario: importing or writing many rows into memorio's sqlite layer.
5
+ *
6
+ * With persistence enabled, each write is recorded in an append-only journal
7
+ * (incremental). Periodic checkpoints - every 50 operations or 250 ms of
8
+ * write-idle - take a full database snapshot. Calling `flush()` inside a
9
+ * per-row loop triggers a checkpoint on every iteration, which is expensive.
10
+ */
11
+ import { memorio } from 'memorio'
12
+
13
+ interface UserRow {
14
+ id: number
15
+ name: string
16
+ role: string
17
+ }
18
+
19
+ export async function importUsers(users: UserRow[]) {
20
+ await memorio.sqlite.ready
21
+ await memorio.sqlite.db.create('app', { persistence: true })
22
+
23
+ await memorio.sqlite.query.run(
24
+ 'app',
25
+ `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
26
+ )
27
+
28
+ // Bad: this would trigger a full checkpoint after every single insert.
29
+ //
30
+ // for (const user of users) {
31
+ // await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
32
+ // await memorio.sqlite.db.flush('app') // <- don't do this per row
33
+ // }
34
+
35
+ // Good: run every insert first, flush once after the batch. Writes are
36
+ // journaled incrementally (cheap); the checkpoint (full snapshot) happens
37
+ // once at the end.
38
+ for (const user of users) {
39
+ await memorio.sqlite.query.run(
40
+ 'app',
41
+ `INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
42
+ [user.id, user.name, user.role]
43
+ )
44
+ }
45
+
46
+ // Flush explicitly, once, after the whole batch - check the installed
47
+ // version's API for the exact flush/persist call name if it differs from
48
+ // automatic persistence-on-close.
49
+ const admins = await memorio.sqlite.query.select(
50
+ 'app',
51
+ `SELECT * FROM users WHERE role = ?`,
52
+ ['admin']
53
+ )
54
+
55
+ return admins
56
+ }
57
+
58
+ // For read-heavy or scratch-space use where durability doesn't matter,
59
+ // skip { persistence: true } entirely - sqlite runs in memory by default,
60
+ // which avoids journal and checkpoint overhead altogether.
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?: 'device' | 'user' | 'shared'
20
- }
21
-
22
- interface SyncProvider {
23
- push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
24
- pull?(since?: number): Promise<MemoryEntry[]>
25
- resolve?(op: 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
- resolve: async op => (op.confidence ?? 0) >= 0.8 ? 'local' : 'remote'
41
- }
42
-
43
- export async function configureSync(namespace: string) {
44
- // `scope: 'device'` data never syncs (sticky to this device) - only
45
- // 'user' and 'shared' scoped entries 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.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: 'user', 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()
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()
@@ -58,8 +58,8 @@ function AutoDiscovery() {
58
58
  function SyncedComponent() {
59
59
  const [localData, setLocalData] = useState(null)
60
60
 
61
- useObserver((newValue: unknown) => {
62
- setLocalData(newValue)
61
+ useObserver(() => {
62
+ setLocalData(state.data)
63
63
  }, [state.data])
64
64
 
65
65
  return <div>{localData}</div>