@ai2070/memex 0.9.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/.github/workflows/ci.yml +31 -0
- package/.github/workflows/release.yml +35 -0
- package/API.md +1078 -0
- package/LICENSE +190 -0
- package/README.md +574 -0
- package/package.json +30 -0
- package/src/bulk.ts +128 -0
- package/src/envelope.ts +52 -0
- package/src/errors.ts +27 -0
- package/src/graph.ts +15 -0
- package/src/helpers.ts +51 -0
- package/src/index.ts +142 -0
- package/src/integrity.ts +378 -0
- package/src/intent.ts +311 -0
- package/src/query.ts +357 -0
- package/src/reducer.ts +177 -0
- package/src/replay.ts +32 -0
- package/src/retrieval.ts +306 -0
- package/src/serialization.ts +34 -0
- package/src/stats.ts +62 -0
- package/src/task.ts +373 -0
- package/src/transplant.ts +488 -0
- package/src/types.ts +248 -0
- package/tests/bugfix-and-coverage.test.ts +958 -0
- package/tests/bugfix-holes.test.ts +856 -0
- package/tests/bulk.test.ts +256 -0
- package/tests/edge-cases-v2.test.ts +355 -0
- package/tests/edge-cases.test.ts +661 -0
- package/tests/envelope.test.ts +92 -0
- package/tests/graph.test.ts +41 -0
- package/tests/helpers.test.ts +120 -0
- package/tests/integrity.test.ts +371 -0
- package/tests/intent.test.ts +276 -0
- package/tests/query-advanced.test.ts +252 -0
- package/tests/query.test.ts +623 -0
- package/tests/reducer.test.ts +342 -0
- package/tests/replay.test.ts +145 -0
- package/tests/retrieval.test.ts +691 -0
- package/tests/serialization.test.ts +118 -0
- package/tests/setup.test.ts +7 -0
- package/tests/stats.test.ts +163 -0
- package/tests/task.test.ts +322 -0
- package/tests/transplant.test.ts +385 -0
- package/tests/types.test.ts +231 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
# MemEX — Structured Memory for AI Agents
|
|
2
|
+
|
|
3
|
+
Multi-session continuity for AI systems.
|
|
4
|
+
|
|
5
|
+
MemEX stores beliefs, evidence, conflicts, and updates -- not just retrieved text. It gives agents a continuous belief state across sessions instead of fragmented chat logs.
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
Every chat session starts from scratch. Memory systems try to fix this by appending text and summarizing when it gets long. But that loses:
|
|
10
|
+
|
|
11
|
+
- **Why** something is believed (provenance)
|
|
12
|
+
- **How much** to trust it (authority, conviction)
|
|
13
|
+
- **What conflicts** with it (contradictions)
|
|
14
|
+
- **Whether** it's still relevant (decay)
|
|
15
|
+
- **Where** it came from (source attribution)
|
|
16
|
+
|
|
17
|
+
The result: agents that can retrieve old text but can't reason about what they know.
|
|
18
|
+
|
|
19
|
+
## What MemEX Does
|
|
20
|
+
|
|
21
|
+
MemEX is a typed, scored, provenance-tracked graph. Each memory item carries:
|
|
22
|
+
|
|
23
|
+
- A **kind** -- what it is (observation, assertion, hypothesis, derivation, simulation, policy, trait)
|
|
24
|
+
- A **source_kind** -- how it got here (user-stated, observed, inferred, imported)
|
|
25
|
+
- Three **scores** -- authority (trust), conviction (author confidence), importance (attention priority)
|
|
26
|
+
- **Parents** -- what items it was derived from, forming provenance chains
|
|
27
|
+
- **Edges** -- typed relationships to other items (supports, contradicts, supersedes, alias)
|
|
28
|
+
|
|
29
|
+
This means the system can:
|
|
30
|
+
|
|
31
|
+
- Carry forward beliefs across sessions, not just text
|
|
32
|
+
- Track what was observed vs inferred vs assumed
|
|
33
|
+
- Surface contradictions instead of silently overwriting
|
|
34
|
+
- Explain *why* it believes something (provenance tree)
|
|
35
|
+
- Decay stale context while preserving stable knowledge
|
|
36
|
+
- Recognize that two observations refer to the same entity
|
|
37
|
+
|
|
38
|
+
## Where MemEX Fits
|
|
39
|
+
|
|
40
|
+
MemEX is the structured memory layer in a larger stack. It doesn't replace your other tools -- it gives them something better to read from and write to.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
┌─────────────────────────────────────────────────┐
|
|
44
|
+
│ Agent / App │
|
|
45
|
+
│ │
|
|
46
|
+
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
|
|
47
|
+
│ │ Chat │ │ Working │ │ Cognition │ │
|
|
48
|
+
│ │ Window │ │ Memory │ │ Layer │ │
|
|
49
|
+
│ │ (sliding) │ │(scratch) │ │ (thinking) │ │
|
|
50
|
+
│ └────┬─────┘ └────┬─────┘ └──────┬────────┘ │
|
|
51
|
+
│ │ │ │ │
|
|
52
|
+
│ └──────────────┼───────────────┘ │
|
|
53
|
+
│ │ │
|
|
54
|
+
│ ┌───────▼────────┐ │
|
|
55
|
+
│ │ MemEX │ │
|
|
56
|
+
│ │ (this library) │ │
|
|
57
|
+
│ └───────┬────────┘ │
|
|
58
|
+
│ │ │
|
|
59
|
+
│ ┌────────────┼────────────┐ │
|
|
60
|
+
│ │ │ │ │
|
|
61
|
+
│ ┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐ │
|
|
62
|
+
│ │ Vector │ │ Text │ │ Event │ │
|
|
63
|
+
│ │ Search │ │ Search│ │ Store │ │
|
|
64
|
+
│ └───────────┘ └───────┘ └───────────┘ │
|
|
65
|
+
└─────────────────────────────────────────────────┘
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### How the pieces connect
|
|
69
|
+
|
|
70
|
+
**Chat window (sliding context)** -- the current conversation. As messages flow, the agent extracts observations, assertions, and preferences and writes them to MemEX. The chat window is ephemeral; MemEX is where things persist.
|
|
71
|
+
|
|
72
|
+
**Working memory (scratchpad)** -- short-lived, high-importance items the agent is actively reasoning about. These live in MemEX with `kind: "hypothesis"` or `kind: "assumption"` and high `importance`. After processing, their importance decays and they settle into long-term memory.
|
|
73
|
+
|
|
74
|
+
**Vector / text search** -- MemEX stores structured items, not embeddings. Search tools subscribe to MemEX lifecycle events and maintain their own indexes. Search indexes are derived from MemEX, not the other way around.
|
|
75
|
+
|
|
76
|
+
**Cognition layer** -- uses `getScoredItems` and `smartRetrieve` to build its thinking queue. Writes back inferred items, resolved contradictions, and updated scores. The agent prioritizes thinking using authority, conviction, and importance.
|
|
77
|
+
|
|
78
|
+
**Event store** -- the append-only command log. MemEX emits lifecycle events that get persisted. On restart, `replayFromEnvelopes` rebuilds the graph from the log.
|
|
79
|
+
|
|
80
|
+
MemEX is the system of record. The library itself is pure TypeScript with a single runtime dependency (`uuidv7`). Storage, search, and bus integration belong in the service layer above.
|
|
81
|
+
|
|
82
|
+
### What changes in agent behavior
|
|
83
|
+
|
|
84
|
+
Without MemEX, an agent:
|
|
85
|
+
- Forgets between sessions, or retrieves flat text with no trust signal
|
|
86
|
+
- Can't tell if something was observed, inferred, or assumed
|
|
87
|
+
- Silently overwrites old beliefs with new ones
|
|
88
|
+
- Can't explain why it believes something
|
|
89
|
+
- Treats everything as equally important
|
|
90
|
+
|
|
91
|
+
With MemEX, an agent:
|
|
92
|
+
- Carries forward a structured belief state across sessions
|
|
93
|
+
- Knows the difference between an observation and a hypothesis
|
|
94
|
+
- Surfaces contradictions instead of hiding them
|
|
95
|
+
- Can trace any belief back to its evidence chain
|
|
96
|
+
- Prioritizes what to think about based on importance and uncertainty
|
|
97
|
+
- Lets stale context fade while stable knowledge persists
|
|
98
|
+
|
|
99
|
+
## Install
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install @ai2070/memex
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Quick Start
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import {
|
|
109
|
+
createGraphState,
|
|
110
|
+
createMemoryItem,
|
|
111
|
+
applyCommand,
|
|
112
|
+
getItems,
|
|
113
|
+
getScoredItems,
|
|
114
|
+
smartRetrieve,
|
|
115
|
+
} from "@ai2070/memex";
|
|
116
|
+
|
|
117
|
+
// create an empty graph
|
|
118
|
+
let state = createGraphState();
|
|
119
|
+
|
|
120
|
+
// add an observation
|
|
121
|
+
const obs = createMemoryItem({
|
|
122
|
+
scope: "user:laz/general",
|
|
123
|
+
kind: "observation",
|
|
124
|
+
content: { key: "login_count", value: 42 },
|
|
125
|
+
author: "agent:monitor",
|
|
126
|
+
source_kind: "observed",
|
|
127
|
+
authority: 0.9,
|
|
128
|
+
importance: 0.7,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const result = applyCommand(state, { type: "memory.create", item: obs });
|
|
132
|
+
state = result.state;
|
|
133
|
+
|
|
134
|
+
// add a hypothesis derived from the observation
|
|
135
|
+
const hyp = createMemoryItem({
|
|
136
|
+
scope: "user:laz/general",
|
|
137
|
+
kind: "hypothesis",
|
|
138
|
+
content: { key: "is_power_user", value: true },
|
|
139
|
+
author: "agent:reasoner",
|
|
140
|
+
source_kind: "agent_inferred",
|
|
141
|
+
parents: [obs.id],
|
|
142
|
+
authority: 0.4,
|
|
143
|
+
conviction: 0.7,
|
|
144
|
+
importance: 0.8,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
state = applyCommand(state, { type: "memory.create", item: hyp }).state;
|
|
148
|
+
|
|
149
|
+
// query with filters
|
|
150
|
+
const recent = getItems(state, {
|
|
151
|
+
or: [{ kind: "observation" }, { kind: "assertion" }],
|
|
152
|
+
range: { authority: { min: 0.5 } },
|
|
153
|
+
created: { after: Date.now() - 86400000 },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// scored retrieval with time decay
|
|
157
|
+
const ranked = getScoredItems(
|
|
158
|
+
state,
|
|
159
|
+
{
|
|
160
|
+
authority: 0.5,
|
|
161
|
+
conviction: 0.3,
|
|
162
|
+
importance: 0.2,
|
|
163
|
+
decay: { rate: 0.1, interval: "day", type: "exponential" },
|
|
164
|
+
},
|
|
165
|
+
{ pre: { scope: "user:laz/general" }, limit: 10 },
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// smart retrieval: decay + contradiction surfacing + diversity + budget
|
|
169
|
+
const context = smartRetrieve(state, {
|
|
170
|
+
budget: 4096,
|
|
171
|
+
costFn: (item) => JSON.stringify(item.content).length,
|
|
172
|
+
weights: {
|
|
173
|
+
authority: 0.5,
|
|
174
|
+
importance: 0.5,
|
|
175
|
+
decay: { rate: 0.1, interval: "day", type: "exponential" },
|
|
176
|
+
},
|
|
177
|
+
filter: { scope: "user:laz/general" },
|
|
178
|
+
contradictions: "surface",
|
|
179
|
+
diversity: { author_penalty: 0.3 },
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Core Concepts
|
|
184
|
+
|
|
185
|
+
### Memory Items
|
|
186
|
+
|
|
187
|
+
Not everything is a "fact." A `MemoryItem` can be an observation, an assertion, an assumption, a hypothesis, a derivation, a simulation, a policy, or a trait. The `kind` field says what it *is*; the `source_kind` field says how it *got here*.
|
|
188
|
+
|
|
189
|
+
### Three Scores
|
|
190
|
+
|
|
191
|
+
| Score | Question | Range |
|
|
192
|
+
|-------|----------|-------|
|
|
193
|
+
| `authority` | How much should the system trust this? | 0..1 |
|
|
194
|
+
| `conviction` | How sure was the author? | 0..1 |
|
|
195
|
+
| `importance` | How much attention does this need right now? (salience) | 0..1 |
|
|
196
|
+
|
|
197
|
+
These are orthogonal. A hypothesis can be high-importance (matters a lot) but low-authority (not yet verified).
|
|
198
|
+
|
|
199
|
+
### Time Decay
|
|
200
|
+
|
|
201
|
+
Scores decay over time at query time -- the stored values are not mutated. Configure decay per query:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
{ rate: 0.1, interval: "day", type: "exponential" }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Three types: **exponential** (smooth curve, never zero), **linear** (straight to zero), **step** (drops at interval boundaries). You can also filter out items that have decayed below a threshold.
|
|
208
|
+
|
|
209
|
+
### Provenance
|
|
210
|
+
|
|
211
|
+
Items can declare **parents** -- the items they were derived or inferred from. This creates provenance chains that let the system explain *why* it believes something:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
getSupportSet(state, claimId)
|
|
215
|
+
// -> [claim, parent1, parent2, grandparent1] -- everything that justifies this claim
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
If a parent is retracted, `getStaleItems` finds orphaned children. `cascadeRetract` removes the entire dependency chain.
|
|
219
|
+
|
|
220
|
+
### Contradictions
|
|
221
|
+
|
|
222
|
+
When two items conflict, they can be linked with a `CONTRADICTS` edge. At retrieval time:
|
|
223
|
+
|
|
224
|
+
- `contradictions: "filter"` -- keep the higher-scoring side (clean context)
|
|
225
|
+
- `contradictions: "surface"` -- keep both, flagged with `contradicted_by` (agent reasoning)
|
|
226
|
+
|
|
227
|
+
Contradictions can be resolved: `resolveContradiction` creates a `SUPERSEDES` edge and lowers the loser's authority.
|
|
228
|
+
|
|
229
|
+
### Identity
|
|
230
|
+
|
|
231
|
+
Two observations of the same entity can be aliased: `markAlias` creates bidirectional `ALIAS` edges. `getAliasGroup` returns the full identity group via transitive closure.
|
|
232
|
+
|
|
233
|
+
### Edges
|
|
234
|
+
|
|
235
|
+
Typed relationships between items:
|
|
236
|
+
|
|
237
|
+
| Edge | Meaning |
|
|
238
|
+
|------|---------|
|
|
239
|
+
| `DERIVED_FROM` | Relationship discovered after creation |
|
|
240
|
+
| `CONTRADICTS` | Two items assert conflicting things |
|
|
241
|
+
| `SUPPORTS` | Evidence for another item |
|
|
242
|
+
| `ABOUT` | References another item |
|
|
243
|
+
| `SUPERSEDES` | Replaces another item (conflict resolution) |
|
|
244
|
+
| `ALIAS` | Same entity, different observations |
|
|
245
|
+
|
|
246
|
+
### Events
|
|
247
|
+
|
|
248
|
+
Three categories, all under `namespace: "memory"`:
|
|
249
|
+
|
|
250
|
+
- **Commands** (imperative): `memory.create`, `memory.update`, `memory.retract`, `edge.create`, `edge.update`, `edge.retract`
|
|
251
|
+
- **Lifecycle** (past tense): `memory.created`, `memory.updated`, `memory.retracted`, `edge.created`, `edge.updated`, `edge.retracted`
|
|
252
|
+
- **State**: `state.memory`, `state.edge`
|
|
253
|
+
|
|
254
|
+
Commands go in, lifecycle events come out of the reducer, state events are full snapshots for downstream consumers.
|
|
255
|
+
|
|
256
|
+
### Immutability
|
|
257
|
+
|
|
258
|
+
`applyCommand` never mutates input state. It returns a new `GraphState` and an array of lifecycle events. History is in the append-only event log; `GraphState` is always the latest snapshot.
|
|
259
|
+
|
|
260
|
+
## Three Graphs
|
|
261
|
+
|
|
262
|
+
MemEX contains three logical graphs in one package. Use what you need:
|
|
263
|
+
|
|
264
|
+
| Graph | Purpose | Core type | Namespace |
|
|
265
|
+
|-------|---------|-----------|-----------|
|
|
266
|
+
| **Memory** | Epistemic state -- beliefs, evidence, contradictions | `MemoryItem` | `"memory"` |
|
|
267
|
+
| **Intent** | Goals and objectives | `Intent` | `"intent"` |
|
|
268
|
+
| **Task** | Units of work tied to intents | `Task` | `"task"` |
|
|
269
|
+
|
|
270
|
+
All three follow the same pattern: commands → reducer → lifecycle events. They cross-reference by ID:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
// intent links to memory items that motivated it
|
|
274
|
+
const intent = createIntent({ label: "find_kati", root_memory_ids: [obs.id], ... });
|
|
275
|
+
|
|
276
|
+
// task links to its parent intent and memory items it consumes/produces
|
|
277
|
+
const task = createTask({ intent_id: intent.id, input_memory_ids: [obs.id], ... });
|
|
278
|
+
|
|
279
|
+
// after task completes, memory items link back
|
|
280
|
+
createMemoryItem({ ..., meta: { creation_intent_id: intent.id, creation_task_id: task.id } });
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## The Loop
|
|
284
|
+
|
|
285
|
+
The three graphs form a continuous cycle:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
┌─────────────────────────────────────────┐
|
|
289
|
+
│ │
|
|
290
|
+
▼ │
|
|
291
|
+
Memory ──────► Intent ──────► Task ──────────┘
|
|
292
|
+
(belief) (direction) (execution)
|
|
293
|
+
│ │ │
|
|
294
|
+
│ something │ spawns │ produces
|
|
295
|
+
│ important │ actionable │ new memory
|
|
296
|
+
│ or uncertain │ steps │ (results,
|
|
297
|
+
│ appears │ │ failures,
|
|
298
|
+
│ │ │ observations)
|
|
299
|
+
└───────────────┘ │
|
|
300
|
+
updates belief │
|
|
301
|
+
state with new ◄──────────┘
|
|
302
|
+
evidence
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
1. **Memory produces intents** — an important or uncertain item surfaces, triggering a goal
|
|
306
|
+
2. **Intents spawn tasks** — the goal breaks into actionable steps
|
|
307
|
+
3. **Tasks produce new memory** — results, observations, and failures write back as memory items
|
|
308
|
+
4. **Memory updates belief state** — new evidence resolves contradictions, reinforces or decays existing beliefs
|
|
309
|
+
|
|
310
|
+
Most AI systems mix these together: goals hidden in prompts, tasks implicit in code, memory as text blobs. MemEX separates them:
|
|
311
|
+
|
|
312
|
+
| Layer | Responsibility |
|
|
313
|
+
|-------|---------------|
|
|
314
|
+
| Memory | What is believed |
|
|
315
|
+
| Intent | What is wanted |
|
|
316
|
+
| Task | What is done |
|
|
317
|
+
|
|
318
|
+
Each layer has its own types, commands, reducer, and query — but they reference each other by ID and share the same event envelope pattern. The separation is what makes the loop auditable: you can trace any belief back to the task that produced it, the intent that motivated it, and the evidence it was based on.
|
|
319
|
+
|
|
320
|
+
## Cognitive Transfer
|
|
321
|
+
|
|
322
|
+
The three graphs together form a complete cognitive state that can be serialized, transferred, and resumed by another agent.
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
Agent A → Agent B:
|
|
326
|
+
|
|
327
|
+
Memory export (what I know)
|
|
328
|
+
+ Intent export (what I want)
|
|
329
|
+
+ Task export (what I've tried, what worked, what failed)
|
|
330
|
+
= Complete cognitive state transferred
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
This isn't just data migration. The receiving agent inherits:
|
|
334
|
+
|
|
335
|
+
- **Context** — the belief state (observations, hypotheses, contradictions)
|
|
336
|
+
- **Direction** — active goals and their priorities
|
|
337
|
+
- **Progress** — which approaches were tried, which failed, which are still running
|
|
338
|
+
|
|
339
|
+
The agent picks up where the other left off. It doesn't re-derive context from scratch. It doesn't retry failed approaches. It continues.
|
|
340
|
+
|
|
341
|
+
### The vector model
|
|
342
|
+
|
|
343
|
+
Think of the three graphs as a cognitive vector:
|
|
344
|
+
|
|
345
|
+
| Component | Role | Analogy |
|
|
346
|
+
|-----------|------|---------|
|
|
347
|
+
| **Memory** | Origin | Starting point in state space — what is known |
|
|
348
|
+
| **Intent** | Magnitude | How much energy is allocated — priority and importance |
|
|
349
|
+
| **Task** | Direction | Which approaches have been tried — path through solution space |
|
|
350
|
+
|
|
351
|
+
Transferring cognition between agents is transferring this vector. The receiving agent starts from the same origin (memory), pursues the same goals with the same energy (intent), and avoids the same dead ends (task history).
|
|
352
|
+
|
|
353
|
+
This is what `exportSlice` / `importSlice` enables at the library level. The transport layer (network, bus, file) is outside the library; MemEX provides the serializable structure.
|
|
354
|
+
|
|
355
|
+
## Features
|
|
356
|
+
|
|
357
|
+
**Memory graph:**
|
|
358
|
+
- Full query algebra: `and`, `or`, `not`, `range`, `ids`, `scope_prefix`, `parents` (includes/count), `meta` (dot-path), `meta_has`, `created` (time range), `decay` (freshness filter)
|
|
359
|
+
- Multi-sort with tiebreakers (authority, conviction, importance, recency)
|
|
360
|
+
- Configurable time decay: exponential, linear, or step -- applied at query time, not stored
|
|
361
|
+
- Scored retrieval with pre/post filters, min_score threshold, and decay
|
|
362
|
+
- Smart retrieval: contradiction-aware packing + diversity penalties + budget limits
|
|
363
|
+
- Budget-aware retrieval (greedy knapsack by score/cost)
|
|
364
|
+
- Provenance trees and minimal support sets (`getSupportTree`, `getSupportSet`)
|
|
365
|
+
- Temporal sort and time-based importance decay
|
|
366
|
+
- Bulk transforms with conditional update/retract (`applyMany`)
|
|
367
|
+
- Conflict detection and resolution (`CONTRADICTS` / `SUPERSEDES`)
|
|
368
|
+
- Staleness detection and cascade retraction
|
|
369
|
+
- Identity resolution (transitive `ALIAS` groups)
|
|
370
|
+
- Serialization (`toJSON` / `fromJSON` / `stringify` / `parse`)
|
|
371
|
+
- Graph stats (counts by kind, author, scope, edge kind)
|
|
372
|
+
- Event envelope wrapping for bus integration
|
|
373
|
+
- Command log replay for state reconstruction
|
|
374
|
+
|
|
375
|
+
**Intent graph:**
|
|
376
|
+
- Status machine: active ↔ paused → completed / cancelled
|
|
377
|
+
- Query by owner, status, priority, linked memory items
|
|
378
|
+
- Invalid transitions throw typed errors
|
|
379
|
+
|
|
380
|
+
**Task graph:**
|
|
381
|
+
- Status machine: pending → running → completed / failed, with retry support (failed → running)
|
|
382
|
+
- Links to parent intent, input/output memory items, agent assignment
|
|
383
|
+
- Query by intent, action, status, agent, linked memory items
|
|
384
|
+
|
|
385
|
+
**Transplant (export / import):**
|
|
386
|
+
- Export a self-contained slice by walking provenance chains, aliases, related intents/tasks
|
|
387
|
+
- Import into another graph instance — default: skip existing ids, append-only
|
|
388
|
+
- Optional shallow compare to detect conflicts, optional re-id to mint new ids on conflict
|
|
389
|
+
- JSON-serializable slices for migration, sub-agent isolation, cloning, and backup
|
|
390
|
+
|
|
391
|
+
## Multi-Agent & Crew Orchestration
|
|
392
|
+
|
|
393
|
+
MemEX supports multi-agent systems where each agent works on a segment of the graph. No separate memory stores per agent — one graph, segmented by conventions.
|
|
394
|
+
|
|
395
|
+
### Soft isolation (shared graph, scoped views)
|
|
396
|
+
|
|
397
|
+
Each agent reads and writes to the shared graph, filtered by `meta.agent_id` and `scope`:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
// agent:researcher only sees its own observations
|
|
401
|
+
const myMemories = getItems(state, {
|
|
402
|
+
meta: { agent_id: "agent:researcher" },
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// agent:analyst sees everything in a project scope
|
|
406
|
+
const projectMemories = getItems(state, {
|
|
407
|
+
scope_prefix: "project:cyberdeck/",
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// orchestrator sees all agents' work, ranked by importance
|
|
411
|
+
const ranked = getScoredItems(state,
|
|
412
|
+
{ authority: 0.5, importance: 0.5 },
|
|
413
|
+
{ pre: { scope_prefix: "project:cyberdeck/" } },
|
|
414
|
+
);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Agents write with their own `author` and `meta.agent_id`. The orchestrator can query across all agents, compare their findings, and resolve contradictions.
|
|
418
|
+
|
|
419
|
+
### Hard isolation (exported slices)
|
|
420
|
+
|
|
421
|
+
For risky operations or external sandboxes, export a slice for the sub-agent to work on independently:
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
// give the sub-agent a slice of the graph
|
|
425
|
+
const slice = exportSlice(memState, intentState, taskState, {
|
|
426
|
+
memory_ids: relevantIds,
|
|
427
|
+
include_parents: true,
|
|
428
|
+
include_related_tasks: true,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// sub-agent works on its own copy...
|
|
432
|
+
// ...then merge results back
|
|
433
|
+
const { memState: updated, report } = importSlice(
|
|
434
|
+
memState, intentState, taskState,
|
|
435
|
+
subAgentSlice,
|
|
436
|
+
);
|
|
437
|
+
// report.created -> what the sub-agent added
|
|
438
|
+
// existing items untouched (append-only)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Crew patterns
|
|
442
|
+
|
|
443
|
+
| Pattern | How |
|
|
444
|
+
|---------|-----|
|
|
445
|
+
| Shared workspace | All agents write to the same scope, filter by `meta.agent_id` to see own work |
|
|
446
|
+
| Pipeline | Agent A's `output_memory_ids` on a task become agent B's `input_memory_ids` |
|
|
447
|
+
| Review | Agent B reads agent A's items, creates `SUPPORTS` / `CONTRADICTS` edges |
|
|
448
|
+
| Delegation | Orchestrator creates an intent, assigns tasks to specific agents via `task.agent_id` |
|
|
449
|
+
| Sandbox | Export slice → sub-agent mutates copy → import results back |
|
|
450
|
+
|
|
451
|
+
### What the `author` and `meta` fields enable
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
// who wrote this?
|
|
455
|
+
item.author // "agent:researcher"
|
|
456
|
+
|
|
457
|
+
// which agent instance?
|
|
458
|
+
item.meta.agent_id // "agent:researcher-v2"
|
|
459
|
+
|
|
460
|
+
// which session?
|
|
461
|
+
item.meta.session_id // "session-abc"
|
|
462
|
+
|
|
463
|
+
// which crew run?
|
|
464
|
+
item.meta.crew_id // "crew:investigation-42"
|
|
465
|
+
|
|
466
|
+
// which intent spawned this?
|
|
467
|
+
item.meta.creation_intent_id // "i1"
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
All of these are queryable via `meta` and `meta_has` filters. The graph is one shared structure; segmentation is just queries.
|
|
471
|
+
|
|
472
|
+
## Dynamic Resolution
|
|
473
|
+
|
|
474
|
+
MemEX supports different levels of detail at every stage of the memory lifecycle:
|
|
475
|
+
|
|
476
|
+
| Stage | Low resolution | High resolution |
|
|
477
|
+
|-------|---------------|----------------|
|
|
478
|
+
| **Retrieval** | High-authority items only, no inferred, fast | Include hypotheses, simulations, full provenance chains |
|
|
479
|
+
| **Thinking** | Direct facts + deterministic derivations | Multi-hop reasoning, contradiction surfacing, support tree traversal |
|
|
480
|
+
| **Insertion** | Store summaries, mark details as low-importance | Store atomic events with full `DERIVED_FROM` chains |
|
|
481
|
+
|
|
482
|
+
Resolution is controlled through the same primitives -- filters, score weights, and decay:
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
// low resolution: only trusted, recent items
|
|
486
|
+
getItems(state, {
|
|
487
|
+
range: { authority: { min: 0.7 } },
|
|
488
|
+
not: { or: [{ kind: "hypothesis" }, { kind: "simulation" }] },
|
|
489
|
+
decay: { config: { rate: 0.3, interval: "day", type: "exponential" }, min: 0.5 },
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// high resolution: everything, scored and ranked
|
|
493
|
+
smartRetrieve(state, {
|
|
494
|
+
budget: 8192,
|
|
495
|
+
costFn: (item) => JSON.stringify(item.content).length,
|
|
496
|
+
weights: { authority: 0.3, conviction: 0.3, importance: 0.4 },
|
|
497
|
+
contradictions: "surface",
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
The agent decides resolution based on the task. A routine action uses low resolution. A decision with consequences uses high resolution. The same graph serves both -- no separate "fast" and "deep" memory stores.
|
|
502
|
+
|
|
503
|
+
### Thinking Budget from Scores
|
|
504
|
+
|
|
505
|
+
The three scores can drive the thinking budget itself. Items that are important but uncertain deserve more processing. Items that have been processed should have their importance reduced.
|
|
506
|
+
|
|
507
|
+
```text
|
|
508
|
+
thinking_priority = importance * (1 - authority)
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
An item with `importance: 0.9` and `authority: 0.3` gets priority `0.63` -- high attention, uncertain, worth reasoning about. An item with `importance: 0.9` and `authority: 0.95` gets priority `0.045` -- important but already trusted, just use it.
|
|
512
|
+
|
|
513
|
+
After the agent processes an item, reduce its importance:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
applyCommand(state, {
|
|
517
|
+
type: "memory.update",
|
|
518
|
+
item_id: processedItem.id,
|
|
519
|
+
partial: { importance: processedItem.importance * 0.3 },
|
|
520
|
+
author: "system:thinker",
|
|
521
|
+
reason: "processed",
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
This creates a natural attention cycle: new items arrive with high importance, get processed, importance drops, and they fade into long-term memory unless re-activated. Items that are never processed accumulate and eventually surface through importance-weighted queries.
|
|
526
|
+
|
|
527
|
+
The cognition layer above can use `getScoredItems` with importance-heavy weights to build its thinking queue, and `decayImportance` to age out items that were never worth processing.
|
|
528
|
+
|
|
529
|
+
## Choosing Parameters
|
|
530
|
+
|
|
531
|
+
The library provides knobs. Here's how to think about turning them.
|
|
532
|
+
|
|
533
|
+
### Decay
|
|
534
|
+
|
|
535
|
+
| Scenario | Recommendation |
|
|
536
|
+
|----------|---------------|
|
|
537
|
+
| Chat context, ephemeral state | Fast decay: `{ rate: 0.3, interval: "hour", type: "linear" }` |
|
|
538
|
+
| Project knowledge, working memory | Moderate decay: `{ rate: 0.1, interval: "day", type: "exponential" }` |
|
|
539
|
+
| Policies, traits, identity | No decay — these don't become less true over time |
|
|
540
|
+
| Mixed graph | Use the `decay` filter to exclude stale items, but don't decay items with `kind: "policy"` or `kind: "trait"` — filter them in separately with `or` |
|
|
541
|
+
|
|
542
|
+
### Diversity penalties
|
|
543
|
+
|
|
544
|
+
| Scenario | Recommendation |
|
|
545
|
+
|----------|---------------|
|
|
546
|
+
| Exploration ("what do we know?") | High `author_penalty` (0.3-0.5) — spread across sources |
|
|
547
|
+
| Verification ("is this true?") | Low or zero `author_penalty` — you *want* correlated evidence |
|
|
548
|
+
| Summarization | Moderate `parent_penalty` (0.2-0.3) — avoid redundant derivations |
|
|
549
|
+
| Debugging / audit | Zero penalties — show everything |
|
|
550
|
+
|
|
551
|
+
### Score weights
|
|
552
|
+
|
|
553
|
+
| Scenario | Weights |
|
|
554
|
+
|----------|---------|
|
|
555
|
+
| High-trust retrieval | `{ authority: 0.8, importance: 0.2 }` |
|
|
556
|
+
| Attention-driven (what needs processing?) | `{ importance: 0.8, authority: 0.2 }` |
|
|
557
|
+
| Agent self-evaluation | `{ conviction: 0.5, authority: 0.5 }` |
|
|
558
|
+
| Balanced | `{ authority: 0.4, conviction: 0.3, importance: 0.3 }` |
|
|
559
|
+
|
|
560
|
+
### Contradiction handling
|
|
561
|
+
|
|
562
|
+
| Scenario | Mode |
|
|
563
|
+
|----------|------|
|
|
564
|
+
| User-facing context (clean, no confusion) | `contradictions: "filter"` |
|
|
565
|
+
| Agent reasoning (needs to see disagreement) | `contradictions: "surface"` |
|
|
566
|
+
| Audit / debugging | Neither — use `getContradictions()` directly |
|
|
567
|
+
|
|
568
|
+
These are starting points, not prescriptions. Calibrate based on your use case.
|
|
569
|
+
|
|
570
|
+
See [API.md](./API.md) for the full API reference.
|
|
571
|
+
|
|
572
|
+
## License
|
|
573
|
+
|
|
574
|
+
Apache 2.0 -- see [LICENSE](./LICENSE).
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai2070/memex",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "MemEX memory layer — graph of memories and edges over an append-only event log",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"prettier": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
19
|
+
"prettier:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\""
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"uuidv7": "^1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"prettier": "^3.8.2",
|
|
27
|
+
"typescript": "^5.9.0",
|
|
28
|
+
"vitest": "^4.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|