swarm-mail 1.6.0 → 1.6.2
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/README.md +953 -50
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/schema/streams.d.ts +490 -0
- package/dist/db/schema/streams.d.ts.map +1 -1
- package/dist/hive/index.d.ts +1 -1
- package/dist/hive/index.d.ts.map +1 -1
- package/dist/hive/queries-drizzle.d.ts +13 -0
- package/dist/hive/queries-drizzle.d.ts.map +1 -1
- package/dist/hive/queries.d.ts +18 -0
- package/dist/hive/queries.d.ts.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34815 -33256
- package/dist/memory/adapter.d.ts +3 -0
- package/dist/memory/adapter.d.ts.map +1 -1
- package/dist/memory/migrations.d.ts +19 -0
- package/dist/memory/migrations.d.ts.map +1 -1
- package/dist/sessions/chunk-processor.d.ts +104 -0
- package/dist/sessions/chunk-processor.d.ts.map +1 -0
- package/dist/sessions/file-watcher.d.ts +86 -0
- package/dist/sessions/file-watcher.d.ts.map +1 -0
- package/dist/sessions/index.d.ts +13 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/pagination.d.ts +60 -0
- package/dist/sessions/pagination.d.ts.map +1 -0
- package/dist/sessions/session-indexer.d.ts +210 -0
- package/dist/sessions/session-indexer.d.ts.map +1 -0
- package/dist/sessions/session-parser.d.ts +88 -0
- package/dist/sessions/session-parser.d.ts.map +1 -0
- package/dist/sessions/session-viewer.d.ts +20 -0
- package/dist/sessions/session-viewer.d.ts.map +1 -0
- package/dist/sessions/staleness-detector.d.ts +58 -0
- package/dist/sessions/staleness-detector.d.ts.map +1 -0
- package/dist/streams/agent-mail.d.ts.map +1 -1
- package/dist/streams/decision-trace-store.d.ts +258 -0
- package/dist/streams/decision-trace-store.d.ts.map +1 -0
- package/dist/streams/durable-server.d.ts +12 -3
- package/dist/streams/durable-server.d.ts.map +1 -1
- package/dist/streams/events.d.ts +3479 -108
- package/dist/streams/events.d.ts.map +1 -1
- package/dist/streams/index.d.ts +1 -0
- package/dist/streams/index.d.ts.map +1 -1
- package/dist/streams/libsql-schema.d.ts +2 -0
- package/dist/streams/libsql-schema.d.ts.map +1 -1
- package/dist/streams/store.d.ts +4 -0
- package/dist/streams/store.d.ts.map +1 -1
- package/dist/streams/swarm-mail.d.ts +14 -0
- package/dist/streams/swarm-mail.d.ts.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ A TypeScript library providing:
|
|
|
39
39
|
1. **Event Store** - Append-only log with automatic projection updates (agents, messages, file reservations)
|
|
40
40
|
2. **Actor Primitives** - DurableMailbox, DurableLock, DurableCursor, DurableDeferred (Effect-TS based)
|
|
41
41
|
3. **Hive** - Git-synced work item tracker (cells, epics, dependencies)
|
|
42
|
-
4. **Semantic Memory** - Vector embeddings for persistent agent learnings (Ollama +
|
|
42
|
+
4. **Semantic Memory** - Vector embeddings for persistent agent learnings (Ollama + libSQL native vector support via sqlite-vec)
|
|
43
43
|
|
|
44
44
|
```
|
|
45
45
|
┌─────────────────────────────────────────────────────────────┐
|
|
@@ -58,13 +58,98 @@ A TypeScript library providing:
|
|
|
58
58
|
│ └── DurableDeferred - Distributed promise │
|
|
59
59
|
│ │
|
|
60
60
|
│ MEMORY │
|
|
61
|
-
│ └── Semantic Memory - Vector embeddings (
|
|
61
|
+
│ └── Semantic Memory - Vector embeddings (Ollama/sqlite-vec) │
|
|
62
62
|
│ │
|
|
63
63
|
│ STORAGE │
|
|
64
64
|
│ └── libSQL (Embedded SQLite via Drizzle ORM) │
|
|
65
65
|
└─────────────────────────────────────────────────────────────┘
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
## Event Flow Architecture
|
|
69
|
+
|
|
70
|
+
Shows how agent actions flow from tool calls → libSQL events → CLI queries/dashboards.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
74
|
+
│ EVENT FLOW: Agent → libSQL → CLI │
|
|
75
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
76
|
+
│ │
|
|
77
|
+
│ ╔═══════════════════╗ │
|
|
78
|
+
│ ║ AGENT (Worker) ║ Tool Calls via OpenCode MCP: │
|
|
79
|
+
│ ╚═══════════════════╝ ┌────────────────────────────────────┐ │
|
|
80
|
+
│ │ │ swarmmail_init() │ │
|
|
81
|
+
│ │ │ swarmmail_reserve(["src/auth.ts"]) │ │
|
|
82
|
+
│ │ │ swarm_progress(progress=50) │ │
|
|
83
|
+
│ │ │ swarm_complete(...) │ │
|
|
84
|
+
│ │ └────────────────────────────────────┘ │
|
|
85
|
+
│ ▼ │
|
|
86
|
+
│ ╔═══════════════════════════════════════════════════════════════╗ │
|
|
87
|
+
│ ║ ADAPTER LAYER (swarm-mail) ║ │
|
|
88
|
+
│ ║ packages/swarm-mail/src/adapter.ts ║ │
|
|
89
|
+
│ ╠═══════════════════════════════════════════════════════════════╣ │
|
|
90
|
+
│ ║ appendEvent(event: SwarmMailEvent) ║ │
|
|
91
|
+
│ ║ 1. Validate with Zod schemas (30+ event types) ║ │
|
|
92
|
+
│ ║ 2. Serialize event.data to JSON ║ │
|
|
93
|
+
│ ║ 3. INSERT into events table ║ │
|
|
94
|
+
│ ║ ║ │
|
|
95
|
+
│ ║ Query Helpers (read from projections): ║ │
|
|
96
|
+
│ ║ ├─ getInbox() → messages table ║ │
|
|
97
|
+
│ ║ ├─ getReservations() → reservations table ║ │
|
|
98
|
+
│ ║ └─ getSwarmContext() → swarm_contexts table ║ │
|
|
99
|
+
│ ╚═══════════════════════════════════════════════════════════════╝ │
|
|
100
|
+
│ │ │
|
|
101
|
+
│ ▼ │
|
|
102
|
+
│ ╔═══════════════════════════════════════════════════════════════╗ │
|
|
103
|
+
│ ║ libSQL DATABASE (SQLite embedded, no server) ║ │
|
|
104
|
+
│ ║ ~/.config/swarm-tools/libsql/<project-hash>/swarm.db ║ │
|
|
105
|
+
│ ╠═══════════════════════════════════════════════════════════════╣ │
|
|
106
|
+
│ ║ events (append-only log) ║ │
|
|
107
|
+
│ ║ ┌──────┬──────────┬───────────┬──────────┬──────────┐ ║ │
|
|
108
|
+
│ ║ │ id │ type │ timestamp │project_key│ data │ ║ │
|
|
109
|
+
│ ║ ├──────┼──────────┼───────────┼──────────┼──────────┤ ║ │
|
|
110
|
+
│ ║ │ 1 │agent_reg │1703001234 │/proj/path│{"..."} │ ║ │
|
|
111
|
+
│ ║ │ 2 │file_res │1703001240 │/proj/path│{"..."} │ ║ │
|
|
112
|
+
│ ║ │ 3 │task_start│1703001299 │/proj/path│{"..."} │ ║ │
|
|
113
|
+
│ ║ └──────┴──────────┴───────────┴──────────┴──────────┘ ║ │
|
|
114
|
+
│ ║ ║ │
|
|
115
|
+
│ ║ PROJECTIONS (auto-updated via triggers): ║ │
|
|
116
|
+
│ ║ ├─ agents ← agent_registered, agent_active ║ │
|
|
117
|
+
│ ║ ├─ messages ← message_sent ║ │
|
|
118
|
+
│ ║ ├─ reservations ← file_reserved, file_released ║ │
|
|
119
|
+
│ ║ ├─ swarm_contexts ← swarm_checkpointed ║ │
|
|
120
|
+
│ ║ └─ eval_records ← eval_captured, eval_scored ║ │
|
|
121
|
+
│ ╚═══════════════════════════════════════════════════════════════╝ │
|
|
122
|
+
│ │ │
|
|
123
|
+
│ ┌──────┼─────┬──────────┬──────────┬──────────┐ │
|
|
124
|
+
│ ▼ ▼ ▼ ▼ ▼ ▼ │
|
|
125
|
+
│ ┌────┐┌────┐┌─────────┐┌────────┐┌────────┐┌────────┐ │
|
|
126
|
+
│ │query││stats││dashboard││replay ││export ││log │ │
|
|
127
|
+
│ │(SQL)││(cnt)││ (TUI) ││(time) ││(JSONL) ││(tail) │ │
|
|
128
|
+
│ └────┘└────┘└─────────┘└────────┘└────────┘└────────┘ │
|
|
129
|
+
│ │ │ │ │ │ │ │
|
|
130
|
+
│ └─────┴───────┴──────────┴─────────┴─────────┘ │
|
|
131
|
+
│ ▼ │
|
|
132
|
+
│ ╔═══════════════════════════════════════════════════════════════╗ │
|
|
133
|
+
│ ║ CLI OUTPUT ║ │
|
|
134
|
+
│ ╠═══════════════════════════════════════════════════════════════╣ │
|
|
135
|
+
│ ║ 📊 Analytics 📈 Dashboards 🔍 Debugging ║ │
|
|
136
|
+
│ ║ ├─ Event counts ├─ Live progress ├─ Event replay ║ │
|
|
137
|
+
│ ║ ├─ Duration P95 ├─ Agent status ├─ Agent timeline ║ │
|
|
138
|
+
│ ║ ├─ Failure rate ├─ File locks ├─ Conflict detection ║ │
|
|
139
|
+
│ ║ └─ Conflict rate └─ Auto-refresh └─ Checkpoint history ║ │
|
|
140
|
+
│ ╚═══════════════════════════════════════════════════════════════╝ │
|
|
141
|
+
│ │
|
|
142
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Flow Summary:**
|
|
146
|
+
|
|
147
|
+
1. **Agent Tool Call** → Agent calls `swarmmail_init()`, `swarm_progress()`, etc.
|
|
148
|
+
2. **Adapter Layer** → Validates with Zod (30+ event types), serializes to JSON, appends to `events` table
|
|
149
|
+
3. **libSQL Storage** → Events stored in append-only log, projections auto-updated via triggers
|
|
150
|
+
4. **CLI Tools** → Query events/projections for analytics, monitoring, debugging
|
|
151
|
+
5. **Observability** → Full audit trail, replay capabilities, real-time dashboards
|
|
152
|
+
|
|
68
153
|
## Install
|
|
69
154
|
|
|
70
155
|
```bash
|
|
@@ -165,26 +250,33 @@ await hive.closeCell(cell.id, "Completed: OAuth implemented");
|
|
|
165
250
|
|
|
166
251
|
### Semantic Memory
|
|
167
252
|
|
|
168
|
-
Vector embeddings for persistent agent learnings
|
|
253
|
+
Vector embeddings for persistent agent learnings. Uses **libSQL native vector support via sqlite-vec extension** + **Ollama** for embeddings. Includes **Wave 1-3 smart operations** (Mem0 pattern, auto-tagging, linking, entity extraction).
|
|
254
|
+
|
|
255
|
+
#### Basic Usage
|
|
169
256
|
|
|
170
257
|
```typescript
|
|
171
258
|
import { createSemanticMemory } from "swarm-mail";
|
|
172
259
|
|
|
173
260
|
const memory = await createSemanticMemory("/my/project");
|
|
174
261
|
|
|
175
|
-
//
|
|
262
|
+
// Simple store (backward compatible - always adds new)
|
|
263
|
+
const { id } = await memory.store(
|
|
264
|
+
"OAuth refresh tokens need 5min buffer before expiry to avoid race conditions"
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// Store with manual metadata
|
|
176
268
|
const { id } = await memory.store(
|
|
177
|
-
"OAuth
|
|
269
|
+
"OAuth tokens need 5min buffer before expiry",
|
|
178
270
|
{ tags: "auth,tokens,debugging" }
|
|
179
271
|
);
|
|
180
272
|
|
|
181
|
-
// Search by
|
|
273
|
+
// Search by semantic similarity (vector search)
|
|
182
274
|
const results = await memory.find("token refresh issues", { limit: 5 });
|
|
183
275
|
|
|
184
|
-
// Get memory
|
|
276
|
+
// Get specific memory
|
|
185
277
|
const mem = await memory.get(id);
|
|
186
278
|
|
|
187
|
-
// Validate (resets decay timer)
|
|
279
|
+
// Validate (resets 90-day decay timer)
|
|
188
280
|
await memory.validate(id);
|
|
189
281
|
|
|
190
282
|
// Check Ollama health
|
|
@@ -192,7 +284,210 @@ const health = await memory.checkHealth();
|
|
|
192
284
|
// { ollama: true, model: "mxbai-embed-large" }
|
|
193
285
|
```
|
|
194
286
|
|
|
195
|
-
|
|
287
|
+
#### Wave 1-3 Smart Operations
|
|
288
|
+
|
|
289
|
+
**Smart Upsert (Mem0 Pattern)**
|
|
290
|
+
|
|
291
|
+
LLM analyzes new information and decides: ADD (new), UPDATE (refines), DELETE (contradicts), or NOOP (duplicate):
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// LLM decides operation intelligently
|
|
295
|
+
const result = await memory.upsert(
|
|
296
|
+
"OAuth tokens need 5min buffer (changed from 3min)",
|
|
297
|
+
{ useSmartOps: true }
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
console.log(result.operation); // "UPDATE" - refines existing memory
|
|
301
|
+
console.log(result.reason); // "Refines existing memory with updated timing"
|
|
302
|
+
console.log(result.id); // Memory ID (existing for UPDATE/DELETE/NOOP, new for ADD)
|
|
303
|
+
|
|
304
|
+
// Examples of each operation:
|
|
305
|
+
// ADD: Completely new information not in memory
|
|
306
|
+
// UPDATE: Refines/updates existing memory with additional detail
|
|
307
|
+
// DELETE: New info contradicts existing memory (supersedes it)
|
|
308
|
+
// NOOP: Duplicate - already stored
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Auto-Tagging**
|
|
312
|
+
|
|
313
|
+
LLM extracts relevant tags from content automatically:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const { id, autoTags } = await memory.store(
|
|
317
|
+
"OAuth tokens need 5min buffer before expiry",
|
|
318
|
+
{ autoTag: true }
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
console.log(autoTags);
|
|
322
|
+
// { tags: ["auth", "oauth", "tokens", "timing"], confidence: 0.85 }
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Memory Linking (Zettelkasten Pattern)**
|
|
326
|
+
|
|
327
|
+
Auto-link to semantically related memories:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const { id, links } = await memory.store(
|
|
331
|
+
"Token refresh race condition fixed in auth service",
|
|
332
|
+
{ autoLink: true }
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
console.log(links);
|
|
336
|
+
// [{ memory_id: "mem-abc123", link_type: "related", score: 0.82 }]
|
|
337
|
+
|
|
338
|
+
// Query linked memories
|
|
339
|
+
const related = await memory.getLinkedMemories("mem-abc123", "related");
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Entity Extraction (A-MEM Pattern)**
|
|
343
|
+
|
|
344
|
+
Build knowledge graph from natural language automatically:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const { id, entities } = await memory.store(
|
|
348
|
+
"Joel prefers TypeScript for Next.js projects",
|
|
349
|
+
{ extractEntities: true }
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
console.log(entities);
|
|
353
|
+
// {
|
|
354
|
+
// entities: [
|
|
355
|
+
// { name: "Joel", type: "person" },
|
|
356
|
+
// { name: "TypeScript", type: "technology" },
|
|
357
|
+
// { name: "Next.js", type: "technology" }
|
|
358
|
+
// ],
|
|
359
|
+
// relationships: [
|
|
360
|
+
// { from: "Joel", to: "TypeScript", type: "prefers" }
|
|
361
|
+
// ]
|
|
362
|
+
// }
|
|
363
|
+
|
|
364
|
+
// Query by entity
|
|
365
|
+
const joelMemories = await memory.findByEntity("Joel", "person");
|
|
366
|
+
|
|
367
|
+
// Get knowledge graph for a memory
|
|
368
|
+
const graph = await memory.getKnowledgeGraph("mem-abc123");
|
|
369
|
+
// { entities: [...], relationships: [...] }
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Combine All Smart Features**
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const result = await memory.store(
|
|
376
|
+
"OAuth tokens need 5min buffer before expiry to avoid race conditions",
|
|
377
|
+
{
|
|
378
|
+
autoTag: true, // LLM extracts tags
|
|
379
|
+
autoLink: true, // Links to related memories
|
|
380
|
+
extractEntities: true // Builds knowledge graph
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// Returns: { id, autoTags, links, entities }
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Temporal Queries (Wave 2)
|
|
388
|
+
|
|
389
|
+
Query memories valid at specific timestamps for time-travel debugging:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// Find memories valid on January 1, 2024
|
|
393
|
+
const pastMemories = await memory.findValidAt(
|
|
394
|
+
"authentication patterns",
|
|
395
|
+
new Date("2024-01-01")
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// Track supersession chains (version history)
|
|
399
|
+
const chain = await memory.getSupersessionChain("mem-v1");
|
|
400
|
+
// Returns: [mem-v1, mem-v2, mem-v3] (chronological evolution)
|
|
401
|
+
|
|
402
|
+
// Mark memory as superseded (when updating)
|
|
403
|
+
await memory.supersede("mem-old-auth", "mem-new-auth");
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### Schema Extensions (Wave 1-3)
|
|
407
|
+
|
|
408
|
+
New tables and columns added for smart operations:
|
|
409
|
+
|
|
410
|
+
**Tables:**
|
|
411
|
+
- `memory_links` - Semantic relationships between memories (Zettelkasten)
|
|
412
|
+
- `entities` - Extracted entities (people, places, concepts)
|
|
413
|
+
- `relationships` - Entity relationships (subject-predicate-object triples)
|
|
414
|
+
- `memory_entities` - Join table linking memories to entities
|
|
415
|
+
|
|
416
|
+
**Columns:**
|
|
417
|
+
- `memories.valid_from` - Temporal validity start (UTC timestamp)
|
|
418
|
+
- `memories.valid_until` - Temporal validity end (NULL = current)
|
|
419
|
+
- `memories.superseded_by` - Points to newer version (version chains)
|
|
420
|
+
- `memories.auto_tags` - JSON array of LLM-extracted tags
|
|
421
|
+
- `memories.keywords` - Searchable keyword index
|
|
422
|
+
|
|
423
|
+
#### Graceful Degradation
|
|
424
|
+
|
|
425
|
+
All smart operations use **fallback heuristics** if LLM/Ollama unavailable:
|
|
426
|
+
|
|
427
|
+
- **Smart ops**: Falls back to simple ADD operation
|
|
428
|
+
- **Auto-tagging**: Returns `undefined` (no auto-tags added)
|
|
429
|
+
- **Auto-linking**: Returns `undefined` (no links created)
|
|
430
|
+
- **Entity extraction**: Returns empty `entities`/`relationships` arrays
|
|
431
|
+
- **Vector search**: Falls back to full-text search (FTS5)
|
|
432
|
+
|
|
433
|
+
**No crashes, no exceptions** - degraded functionality, not broken functionality.
|
|
434
|
+
|
|
435
|
+
#### ID Prefix Convention
|
|
436
|
+
|
|
437
|
+
Memory IDs use `mem-` prefix (not `mem_`):
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const { id } = await memory.store("Content");
|
|
441
|
+
console.log(id); // "mem-abc123def456" (16 hex chars after prefix)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### Wave 1-3 Service Exports
|
|
445
|
+
|
|
446
|
+
For advanced use cases, you can access the underlying services directly:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import {
|
|
450
|
+
// Smart Operations (Mem0 pattern)
|
|
451
|
+
analyzeMemoryOperation,
|
|
452
|
+
|
|
453
|
+
// Auto-tagging
|
|
454
|
+
generateTags,
|
|
455
|
+
|
|
456
|
+
// Memory Linking (Zettelkasten)
|
|
457
|
+
autoLinkMemory,
|
|
458
|
+
createLink,
|
|
459
|
+
findRelatedMemories,
|
|
460
|
+
|
|
461
|
+
// Entity Extraction (A-MEM)
|
|
462
|
+
extractEntitiesAndRelationships,
|
|
463
|
+
storeEntities,
|
|
464
|
+
} from "swarm-mail";
|
|
465
|
+
|
|
466
|
+
// Example: Manual smart operation
|
|
467
|
+
const analysis = await analyzeMemoryOperation(
|
|
468
|
+
"OAuth tokens need 5min buffer",
|
|
469
|
+
existingMemories,
|
|
470
|
+
aiGatewayKey
|
|
471
|
+
);
|
|
472
|
+
console.log(analysis.operation); // "ADD" | "UPDATE" | "DELETE" | "NOOP"
|
|
473
|
+
|
|
474
|
+
// Example: Manual tag generation
|
|
475
|
+
const tags = await generateTags(
|
|
476
|
+
"OAuth tokens need 5min buffer before expiry",
|
|
477
|
+
aiGatewayKey
|
|
478
|
+
);
|
|
479
|
+
console.log(tags); // { tags: ["auth", "oauth", "tokens"], confidence: 0.85 }
|
|
480
|
+
|
|
481
|
+
// Example: Manual entity extraction
|
|
482
|
+
const extracted = await extractEntitiesAndRelationships(
|
|
483
|
+
"Joel prefers TypeScript for Next.js projects",
|
|
484
|
+
aiGatewayKey
|
|
485
|
+
);
|
|
486
|
+
console.log(extracted.entities); // [{ name: "Joel", type: "person" }, ...]
|
|
487
|
+
console.log(extracted.relationships); // [{ from: "Joel", to: "TypeScript", type: "prefers" }]
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
> **Note:** Requires [Ollama](https://ollama.ai/) for vector embeddings and smart operations. Falls back to full-text search if unavailable.
|
|
196
491
|
>
|
|
197
492
|
> ```bash
|
|
198
493
|
> ollama pull mxbai-embed-large
|
|
@@ -225,54 +520,549 @@ await swarmMail.appendEvent({
|
|
|
225
520
|
});
|
|
226
521
|
```
|
|
227
522
|
|
|
228
|
-
## Event
|
|
523
|
+
## Event Schema
|
|
524
|
+
|
|
525
|
+
All swarm coordination is recorded as immutable events in libSQL. Events are **append-only** - nothing is deleted, everything is auditable.
|
|
526
|
+
|
|
527
|
+
### Event Store Table
|
|
528
|
+
|
|
529
|
+
```sql
|
|
530
|
+
CREATE TABLE events (
|
|
531
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
532
|
+
type TEXT NOT NULL, -- Event type discriminator
|
|
533
|
+
project_key TEXT NOT NULL, -- Project path (for multi-project isolation)
|
|
534
|
+
timestamp INTEGER NOT NULL, -- Unix ms
|
|
535
|
+
sequence INTEGER GENERATED ALWAYS AS (id) STORED,
|
|
536
|
+
data TEXT NOT NULL, -- JSON payload (event-specific fields)
|
|
537
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
-- Fast queries via composite indexes
|
|
541
|
+
CREATE INDEX idx_events_project_key ON events(project_key);
|
|
542
|
+
CREATE INDEX idx_events_type ON events(type);
|
|
543
|
+
CREATE INDEX idx_events_timestamp ON events(timestamp);
|
|
544
|
+
CREATE INDEX idx_events_project_type ON events(project_key, type);
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Event Types
|
|
548
|
+
|
|
549
|
+
All events extend `BaseEventSchema` with common fields:
|
|
550
|
+
```typescript
|
|
551
|
+
{
|
|
552
|
+
id?: number; // Auto-generated
|
|
553
|
+
type: string; // Event discriminator
|
|
554
|
+
project_key: string; // Project path
|
|
555
|
+
timestamp: number; // Unix ms
|
|
556
|
+
sequence?: number; // Auto-generated ordering
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Agent Lifecycle (2 types):**
|
|
561
|
+
```typescript
|
|
562
|
+
{ type: "agent_registered"; agent_name: string; program?: string; model?: string; task_description?: string }
|
|
563
|
+
{ type: "agent_active"; agent_name: string }
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Messages (5 types):**
|
|
567
|
+
```typescript
|
|
568
|
+
{ type: "message_sent"; message_id?: number; from_agent: string; to_agents: string[]; subject: string; body: string; thread_id?: string; importance?: "low" | "normal" | "high" | "urgent"; ack_required?: boolean; epic_id?: string; bead_id?: string; message_type?: "progress" | "blocked" | "question" | "status" | "general"; body_length?: number; recipient_count?: number; is_broadcast?: boolean }
|
|
569
|
+
{ type: "message_read"; message_id: number; agent_name: string }
|
|
570
|
+
{ type: "message_acked"; message_id: number; agent_name: string }
|
|
571
|
+
{ type: "thread_created"; thread_id: string; epic_id?: string; initial_subject: string; creator_agent: string }
|
|
572
|
+
{ type: "thread_activity"; thread_id: string; message_count: number; participant_count: number; last_message_agent: string; has_unread: boolean }
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**File Reservations (3 types):**
|
|
576
|
+
```typescript
|
|
577
|
+
{ type: "file_reserved"; reservation_id?: number; agent_name: string; paths: string[]; reason?: string; exclusive?: boolean; ttl_seconds?: number; expires_at: number; lock_holder_ids?: string[]; epic_id?: string; bead_id?: string; file_count?: number; is_retry?: boolean; conflict_agent?: string }
|
|
578
|
+
{ type: "file_released"; agent_name: string; paths?: string[]; reservation_ids?: number[]; lock_holder_ids?: string[]; epic_id?: string; bead_id?: string; file_count?: number; hold_duration_ms?: number; files_modified?: number }
|
|
579
|
+
{ type: "file_conflict"; requesting_agent: string; holding_agent: string; paths: string[]; epic_id?: string; bead_id?: string; resolution?: "wait" | "force" | "abort" }
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Task Tracking (4 types):**
|
|
583
|
+
```typescript
|
|
584
|
+
{ type: "task_started"; agent_name: string; bead_id: string; epic_id?: string }
|
|
585
|
+
{ type: "task_progress"; agent_name: string; bead_id: string; progress_percent?: number; message?: string; files_touched?: string[] }
|
|
586
|
+
{ type: "task_completed"; agent_name: string; bead_id: string; summary: string; files_touched?: string[]; success: boolean }
|
|
587
|
+
{ type: "task_blocked"; agent_name: string; bead_id: string; reason: string }
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Swarm Coordination (8 types):**
|
|
591
|
+
```typescript
|
|
592
|
+
{ type: "swarm_started"; epic_id: string; epic_title: string; strategy: "file-based" | "feature-based" | "risk-based"; subtask_count: number; total_files: number; coordinator_agent: string }
|
|
593
|
+
{ type: "worker_spawned"; epic_id: string; bead_id: string; worker_agent: string; subtask_title: string; files_assigned: string[]; spawn_order: number; is_parallel: boolean }
|
|
594
|
+
{ type: "worker_completed"; epic_id: string; bead_id: string; worker_agent: string; success: boolean; duration_ms: number; files_touched: string[]; error_message?: string }
|
|
595
|
+
{ type: "review_started"; epic_id: string; bead_id: string; attempt: number }
|
|
596
|
+
{ type: "review_completed"; epic_id: string; bead_id: string; status: "approved" | "needs_changes" | "blocked"; attempt: number; duration_ms?: number }
|
|
597
|
+
{ type: "swarm_completed"; epic_id: string; epic_title: string; success: boolean; total_duration_ms: number; subtasks_completed: number; subtasks_failed: number; total_files_touched: string[] }
|
|
598
|
+
{ type: "decomposition_generated"; epic_id: string; task: string; context?: string; strategy: "file-based" | "feature-based" | "risk-based"; epic_title: string; subtasks: Array<{ title: string; files: string[]; priority?: number }>; recovery_context?: object }
|
|
599
|
+
{ type: "subtask_outcome"; epic_id: string; bead_id: string; planned_files: string[]; actual_files: string[]; duration_ms: number; error_count?: number; retry_count?: number; success: boolean; scope_violation?: boolean; violation_files?: string[] }
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Checkpoints & Recovery (4 types):**
|
|
603
|
+
```typescript
|
|
604
|
+
{ type: "swarm_checkpointed"; epic_id: string; bead_id: string; strategy: "file-based" | "feature-based" | "risk-based"; files: string[]; dependencies: string[]; directives: object; recovery: object; checkpoint_size_bytes?: number; trigger?: "manual" | "auto" | "progress" | "error"; context_tokens_before?: number; context_tokens_after?: number }
|
|
605
|
+
{ type: "swarm_recovered"; epic_id: string; bead_id: string; recovered_from_checkpoint: number; recovery_duration_ms?: number; checkpoint_age_ms?: number; files_restored?: string[]; context_restored_tokens?: number }
|
|
606
|
+
{ type: "checkpoint_created"; epic_id: string; bead_id: string; agent_name: string; checkpoint_id: string; trigger: "manual" | "auto" | "progress" | "error"; progress_percent: number; files_snapshot: string[] }
|
|
607
|
+
{ type: "context_compacted"; epic_id?: string; bead_id?: string; agent_name: string; tokens_before: number; tokens_after: number; compression_ratio: number; summary_length: number }
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Validation & Learning (4 types):**
|
|
611
|
+
```typescript
|
|
612
|
+
{ type: "validation_started"; epic_id: string; swarm_id: string; started_at: number }
|
|
613
|
+
{ type: "validation_issue"; epic_id: string; severity: "error" | "warning" | "info"; category: "schema_mismatch" | "missing_event" | "undefined_value" | "dashboard_render" | "websocket_delivery"; message: string; location?: object }
|
|
614
|
+
{ type: "validation_completed"; epic_id: string; swarm_id: string; passed: boolean; issue_count: number; duration_ms: number }
|
|
615
|
+
{ type: "human_feedback"; epic_id: string; accepted: boolean; modified?: boolean; notes?: string }
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**Total: 30+ event types** - Full Zod schemas in [src/streams/events.ts](src/streams/events.ts)
|
|
619
|
+
|
|
620
|
+
## libSQL Query Examples
|
|
621
|
+
|
|
622
|
+
All state is derived from events via **projections** (materialized views). Query the event store directly or use projections for common patterns.
|
|
623
|
+
|
|
624
|
+
### Agent Queries
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
// Who's registered in this project?
|
|
628
|
+
const agents = await swarmMail.getAgents();
|
|
629
|
+
// SELECT * FROM agents WHERE project_key = ? ORDER BY registered_at
|
|
630
|
+
|
|
631
|
+
// Get specific agent details
|
|
632
|
+
const agent = await swarmMail.getAgent("WorkerBee");
|
|
633
|
+
// SELECT * FROM agents WHERE project_key = ? AND name = ?
|
|
634
|
+
|
|
635
|
+
// Debug agent activity
|
|
636
|
+
const debug = await swarmMail.debugAgent("WorkerBee");
|
|
637
|
+
// Returns: { agent, messages_sent, messages_received, reservations, recent_events }
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Raw SQL examples:**
|
|
641
|
+
|
|
642
|
+
```sql
|
|
643
|
+
-- All agents in project
|
|
644
|
+
SELECT name, program, model, task_description, registered_at
|
|
645
|
+
FROM agents
|
|
646
|
+
WHERE project_key = '/path/to/project'
|
|
647
|
+
ORDER BY last_active_at DESC;
|
|
648
|
+
|
|
649
|
+
-- Agents active in last hour
|
|
650
|
+
SELECT name, last_active_at
|
|
651
|
+
FROM agents
|
|
652
|
+
WHERE project_key = '/path/to/project'
|
|
653
|
+
AND last_active_at > (strftime('%s', 'now') - 3600) * 1000;
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Message Queries
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
// Get inbox (materialized view)
|
|
660
|
+
const messages = await swarmMail.getInbox("WorkerBee", {
|
|
661
|
+
limit: 5,
|
|
662
|
+
unreadOnly: true
|
|
663
|
+
});
|
|
664
|
+
// SELECT * FROM messages WHERE ... ORDER BY sent_at DESC LIMIT 5
|
|
665
|
+
|
|
666
|
+
// Get thread (all messages with same thread_id)
|
|
667
|
+
const thread = await swarmMail.getThread("epic-123");
|
|
668
|
+
// SELECT * FROM messages WHERE thread_id = ? ORDER BY sent_at
|
|
669
|
+
|
|
670
|
+
// Mark as read
|
|
671
|
+
await swarmMail.appendEvent({
|
|
672
|
+
type: "message_read",
|
|
673
|
+
message_id: 42,
|
|
674
|
+
agent_name: "WorkerBee",
|
|
675
|
+
timestamp: Date.now(),
|
|
676
|
+
});
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Raw SQL examples:**
|
|
680
|
+
|
|
681
|
+
```sql
|
|
682
|
+
-- Unread messages for agent
|
|
683
|
+
SELECT m.id, m.subject, m.from_agent, m.sent_at, m.importance
|
|
684
|
+
FROM messages m
|
|
685
|
+
LEFT JOIN message_reads mr
|
|
686
|
+
ON mr.message_id = m.id AND mr.agent_name = 'WorkerBee'
|
|
687
|
+
WHERE m.project_key = '/path/to/project'
|
|
688
|
+
AND EXISTS (
|
|
689
|
+
SELECT 1 FROM message_recipients
|
|
690
|
+
WHERE message_id = m.id AND recipient_name = 'WorkerBee'
|
|
691
|
+
)
|
|
692
|
+
AND mr.id IS NULL
|
|
693
|
+
ORDER BY m.sent_at DESC;
|
|
694
|
+
|
|
695
|
+
-- Thread view with participants
|
|
696
|
+
SELECT m.id, m.subject, m.from_agent, m.body, m.sent_at,
|
|
697
|
+
GROUP_CONCAT(mr.recipient_name) as recipients
|
|
698
|
+
FROM messages m
|
|
699
|
+
LEFT JOIN message_recipients mr ON mr.message_id = m.id
|
|
700
|
+
WHERE m.thread_id = 'epic-123'
|
|
701
|
+
GROUP BY m.id
|
|
702
|
+
ORDER BY m.sent_at;
|
|
703
|
+
|
|
704
|
+
-- Message traffic by importance
|
|
705
|
+
SELECT importance, COUNT(*) as count
|
|
706
|
+
FROM messages
|
|
707
|
+
WHERE project_key = '/path/to/project'
|
|
708
|
+
AND sent_at > (strftime('%s', 'now') - 86400) * 1000 -- last 24h
|
|
709
|
+
GROUP BY importance;
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### File Reservation Queries
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
// Check for conflicts before reserving
|
|
716
|
+
const conflicts = await swarmMail.checkConflicts(
|
|
717
|
+
["src/auth.ts", "src/db.ts"],
|
|
718
|
+
"WorkerBee"
|
|
719
|
+
);
|
|
720
|
+
// Returns: [{ path, holder, expires_at, exclusive }]
|
|
721
|
+
|
|
722
|
+
// Get all active reservations
|
|
723
|
+
const reservations = await swarmMail.getReservations();
|
|
724
|
+
// SELECT * FROM reservations WHERE expires_at > ? ORDER BY created_at
|
|
725
|
+
|
|
726
|
+
// Get agent's reservations
|
|
727
|
+
const myReservations = await swarmMail.getReservationsForAgent("WorkerBee");
|
|
728
|
+
// SELECT * FROM reservations WHERE agent_name = ? AND expires_at > ?
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Raw SQL examples:**
|
|
732
|
+
|
|
733
|
+
```sql
|
|
734
|
+
-- Active file locks
|
|
735
|
+
SELECT agent_name, path_pattern, exclusive,
|
|
736
|
+
datetime(created_at/1000, 'unixepoch') as locked_at,
|
|
737
|
+
datetime(expires_at/1000, 'unixepoch') as expires
|
|
738
|
+
FROM reservations
|
|
739
|
+
WHERE project_key = '/path/to/project'
|
|
740
|
+
AND expires_at > (strftime('%s', 'now') * 1000)
|
|
741
|
+
ORDER BY created_at;
|
|
742
|
+
|
|
743
|
+
-- Conflict detection (who holds this path?)
|
|
744
|
+
SELECT agent_name, exclusive, expires_at
|
|
745
|
+
FROM reservations
|
|
746
|
+
WHERE project_key = '/path/to/project'
|
|
747
|
+
AND path_pattern LIKE '%src/auth.ts%'
|
|
748
|
+
AND expires_at > (strftime('%s', 'now') * 1000);
|
|
749
|
+
|
|
750
|
+
-- Expired reservations (cleanup candidates)
|
|
751
|
+
SELECT agent_name, path_pattern,
|
|
752
|
+
(strftime('%s', 'now') * 1000 - expires_at) / 1000 as expired_seconds_ago
|
|
753
|
+
FROM reservations
|
|
754
|
+
WHERE project_key = '/path/to/project'
|
|
755
|
+
AND expires_at < (strftime('%s', 'now') * 1000)
|
|
756
|
+
ORDER BY expires_at;
|
|
757
|
+
|
|
758
|
+
-- Reservation timeline (who locked what when?)
|
|
759
|
+
SELECT
|
|
760
|
+
datetime(timestamp/1000, 'unixepoch') as time,
|
|
761
|
+
json_extract(data, '$.agent_name') as agent,
|
|
762
|
+
json_extract(data, '$.paths') as paths,
|
|
763
|
+
json_extract(data, '$.exclusive') as exclusive,
|
|
764
|
+
CASE type
|
|
765
|
+
WHEN 'file_reserved' THEN '🔒 LOCK'
|
|
766
|
+
WHEN 'file_released' THEN '🔓 RELEASE'
|
|
767
|
+
END as action
|
|
768
|
+
FROM events
|
|
769
|
+
WHERE project_key = '/path/to/project'
|
|
770
|
+
AND type IN ('file_reserved', 'file_released')
|
|
771
|
+
ORDER BY timestamp DESC
|
|
772
|
+
LIMIT 20;
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Task Progress Queries
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
// Get swarm checkpoint for recovery
|
|
779
|
+
const context = await swarmMail.getSwarmContext("epic-123");
|
|
780
|
+
// SELECT * FROM swarm_contexts WHERE epic_id = ? ORDER BY version DESC LIMIT 1
|
|
781
|
+
|
|
782
|
+
// Query events for timeline
|
|
783
|
+
const events = await swarmMail.getEvents({
|
|
784
|
+
limit: 100,
|
|
785
|
+
after: lastSequence
|
|
786
|
+
});
|
|
787
|
+
// SELECT * FROM events WHERE sequence > ? ORDER BY sequence LIMIT 100
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**Raw SQL examples:**
|
|
791
|
+
|
|
792
|
+
```sql
|
|
793
|
+
-- Task timeline (start → progress → complete)
|
|
794
|
+
SELECT
|
|
795
|
+
datetime(timestamp/1000, 'unixepoch') as time,
|
|
796
|
+
type,
|
|
797
|
+
json_extract(data, '$.agent_name') as agent,
|
|
798
|
+
json_extract(data, '$.bead_id') as task,
|
|
799
|
+
json_extract(data, '$.progress_percent') as progress,
|
|
800
|
+
json_extract(data, '$.message') as status
|
|
801
|
+
FROM events
|
|
802
|
+
WHERE project_key = '/path/to/project'
|
|
803
|
+
AND type IN ('task_started', 'task_progress', 'task_completed', 'task_blocked')
|
|
804
|
+
AND json_extract(data, '$.epic_id') = 'epic-123'
|
|
805
|
+
ORDER BY timestamp;
|
|
806
|
+
|
|
807
|
+
-- Task outcomes (success vs failure)
|
|
808
|
+
SELECT
|
|
809
|
+
json_extract(data, '$.bead_id') as task,
|
|
810
|
+
json_extract(data, '$.success') as success,
|
|
811
|
+
json_extract(data, '$.duration_ms') as duration,
|
|
812
|
+
json_extract(data, '$.error_count') as errors,
|
|
813
|
+
datetime(timestamp/1000, 'unixepoch') as completed_at
|
|
814
|
+
FROM events
|
|
815
|
+
WHERE type = 'task_completed'
|
|
816
|
+
AND json_extract(data, '$.epic_id') = 'epic-123'
|
|
817
|
+
ORDER BY timestamp;
|
|
818
|
+
|
|
819
|
+
-- Checkpoint history (resume points)
|
|
820
|
+
SELECT version, progress,
|
|
821
|
+
datetime(created_at/1000, 'unixepoch') as checkpoint_time,
|
|
822
|
+
json_extract(state, '$.files_touched') as files
|
|
823
|
+
FROM swarm_contexts
|
|
824
|
+
WHERE epic_id = 'epic-123'
|
|
825
|
+
ORDER BY version DESC;
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### Analytics Queries
|
|
829
|
+
|
|
830
|
+
**Four Golden Signals** for observability:
|
|
831
|
+
|
|
832
|
+
```sql
|
|
833
|
+
-- 1. LATENCY - Task duration distribution
|
|
834
|
+
SELECT
|
|
835
|
+
CAST(json_extract(data, '$.duration_ms') / 1000.0 AS INT) as seconds,
|
|
836
|
+
COUNT(*) as tasks,
|
|
837
|
+
ROUND(AVG(json_extract(data, '$.duration_ms')), 0) as avg_ms
|
|
838
|
+
FROM events
|
|
839
|
+
WHERE type = 'task_completed'
|
|
840
|
+
AND json_extract(data, '$.success') = 1
|
|
841
|
+
GROUP BY seconds
|
|
842
|
+
ORDER BY seconds;
|
|
843
|
+
|
|
844
|
+
-- 2. TRAFFIC - Events per hour
|
|
845
|
+
SELECT
|
|
846
|
+
strftime('%Y-%m-%d %H:00', datetime(timestamp/1000, 'unixepoch')) as hour,
|
|
847
|
+
type,
|
|
848
|
+
COUNT(*) as count
|
|
849
|
+
FROM events
|
|
850
|
+
WHERE timestamp > (strftime('%s', 'now') - 86400) * 1000 -- last 24h
|
|
851
|
+
GROUP BY hour, type
|
|
852
|
+
ORDER BY hour DESC, count DESC;
|
|
853
|
+
|
|
854
|
+
-- 3. ERRORS - Failure analysis
|
|
855
|
+
SELECT
|
|
856
|
+
json_extract(data, '$.bead_id') as task,
|
|
857
|
+
json_extract(data, '$.error_count') as errors,
|
|
858
|
+
json_extract(data, '$.retry_count') as retries,
|
|
859
|
+
json_extract(data, '$.files_touched') as files,
|
|
860
|
+
datetime(timestamp/1000, 'unixepoch') as failed_at
|
|
861
|
+
FROM events
|
|
862
|
+
WHERE type = 'task_completed'
|
|
863
|
+
AND json_extract(data, '$.success') = 0
|
|
864
|
+
ORDER BY timestamp DESC
|
|
865
|
+
LIMIT 10;
|
|
866
|
+
|
|
867
|
+
-- 4. SATURATION - File contention
|
|
868
|
+
SELECT
|
|
869
|
+
json_extract(data, '$.paths') as file_path,
|
|
870
|
+
COUNT(DISTINCT json_extract(data, '$.agent_name')) as agent_count,
|
|
871
|
+
COUNT(*) as reservation_attempts,
|
|
872
|
+
GROUP_CONCAT(json_extract(data, '$.agent_name')) as competing_agents
|
|
873
|
+
FROM events
|
|
874
|
+
WHERE type = 'file_reserved'
|
|
875
|
+
AND timestamp > (strftime('%s', 'now') - 3600) * 1000 -- last hour
|
|
876
|
+
GROUP BY file_path
|
|
877
|
+
HAVING agent_count > 1
|
|
878
|
+
ORDER BY reservation_attempts DESC;
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
### Debugging Queries
|
|
882
|
+
|
|
883
|
+
```sql
|
|
884
|
+
-- Agent activity timeline (comprehensive view)
|
|
885
|
+
SELECT
|
|
886
|
+
datetime(timestamp/1000, 'unixepoch') as time,
|
|
887
|
+
type,
|
|
888
|
+
CASE
|
|
889
|
+
WHEN type = 'agent_registered' THEN 'Registered: ' || json_extract(data, '$.task_description')
|
|
890
|
+
WHEN type = 'message_sent' THEN 'Sent: ' || json_extract(data, '$.subject')
|
|
891
|
+
WHEN type = 'file_reserved' THEN 'Locked: ' || json_extract(data, '$.paths')
|
|
892
|
+
WHEN type = 'file_released' THEN 'Released: ' || json_extract(data, '$.file_count') || ' files'
|
|
893
|
+
WHEN type = 'task_started' THEN 'Started: ' || json_extract(data, '$.bead_id')
|
|
894
|
+
WHEN type = 'task_progress' THEN 'Progress: ' || json_extract(data, '$.progress_percent') || '%'
|
|
895
|
+
WHEN type = 'task_completed' THEN 'Completed: ' || json_extract(data, '$.bead_id') || ' (' || CASE WHEN json_extract(data, '$.success') = 1 THEN 'success' ELSE 'failed' END || ')'
|
|
896
|
+
WHEN type = 'task_blocked' THEN 'Blocked: ' || json_extract(data, '$.reason')
|
|
897
|
+
ELSE type
|
|
898
|
+
END as activity
|
|
899
|
+
FROM events
|
|
900
|
+
WHERE json_extract(data, '$.agent_name') = 'WorkerBee'
|
|
901
|
+
ORDER BY timestamp DESC
|
|
902
|
+
LIMIT 50;
|
|
903
|
+
|
|
904
|
+
-- Find stuck tasks (started but not completed)
|
|
905
|
+
WITH task_events AS (
|
|
906
|
+
SELECT
|
|
907
|
+
json_extract(data, '$.bead_id') as task_id,
|
|
908
|
+
json_extract(data, '$.agent_name') as agent,
|
|
909
|
+
MIN(CASE WHEN type = 'task_started' THEN timestamp END) as started_at,
|
|
910
|
+
MAX(CASE WHEN type IN ('task_completed', 'task_blocked') THEN timestamp END) as ended_at
|
|
911
|
+
FROM events
|
|
912
|
+
WHERE type IN ('task_started', 'task_completed', 'task_blocked')
|
|
913
|
+
GROUP BY task_id, agent
|
|
914
|
+
)
|
|
915
|
+
SELECT
|
|
916
|
+
task_id,
|
|
917
|
+
agent,
|
|
918
|
+
datetime(started_at/1000, 'unixepoch') as started,
|
|
919
|
+
ROUND((strftime('%s', 'now') * 1000 - started_at) / 60000.0, 1) as minutes_stuck
|
|
920
|
+
FROM task_events
|
|
921
|
+
WHERE ended_at IS NULL
|
|
922
|
+
ORDER BY started_at DESC;
|
|
923
|
+
|
|
924
|
+
-- Message response times (high/urgent only)
|
|
925
|
+
WITH message_timings AS (
|
|
926
|
+
SELECT
|
|
927
|
+
json_extract(sent.data, '$.message_id') as msg_id,
|
|
928
|
+
json_extract(sent.data, '$.subject') as subject,
|
|
929
|
+
json_extract(sent.data, '$.from_agent') as sender,
|
|
930
|
+
json_extract(sent.data, '$.importance') as importance,
|
|
931
|
+
sent.timestamp as sent_at,
|
|
932
|
+
read.timestamp as read_at
|
|
933
|
+
FROM events sent
|
|
934
|
+
LEFT JOIN events read
|
|
935
|
+
ON read.type = 'message_read'
|
|
936
|
+
AND json_extract(read.data, '$.message_id') = json_extract(sent.data, '$.message_id')
|
|
937
|
+
WHERE sent.type = 'message_sent'
|
|
938
|
+
AND json_extract(sent.data, '$.importance') IN ('high', 'urgent')
|
|
939
|
+
)
|
|
940
|
+
SELECT
|
|
941
|
+
msg_id,
|
|
942
|
+
subject,
|
|
943
|
+
sender,
|
|
944
|
+
importance,
|
|
945
|
+
datetime(sent_at/1000, 'unixepoch') as sent,
|
|
946
|
+
CASE
|
|
947
|
+
WHEN read_at IS NULL THEN 'UNREAD'
|
|
948
|
+
ELSE ROUND((read_at - sent_at) / 1000.0, 1) || 's'
|
|
949
|
+
END as response_time
|
|
950
|
+
FROM message_timings
|
|
951
|
+
ORDER BY sent_at DESC
|
|
952
|
+
LIMIT 20;
|
|
953
|
+
|
|
954
|
+
-- Swarm execution summary (epic overview)
|
|
955
|
+
WITH epic_events AS (
|
|
956
|
+
SELECT
|
|
957
|
+
json_extract(data, '$.epic_id') as epic_id,
|
|
958
|
+
type,
|
|
959
|
+
timestamp,
|
|
960
|
+
data
|
|
961
|
+
FROM events
|
|
962
|
+
WHERE json_extract(data, '$.epic_id') = 'mjmas3zxlmg'
|
|
963
|
+
)
|
|
964
|
+
SELECT
|
|
965
|
+
'Epic' as metric,
|
|
966
|
+
json_extract((SELECT data FROM epic_events WHERE type = 'swarm_started' LIMIT 1), '$.epic_title') as value
|
|
967
|
+
UNION ALL
|
|
968
|
+
SELECT
|
|
969
|
+
'Strategy',
|
|
970
|
+
json_extract((SELECT data FROM epic_events WHERE type = 'swarm_started' LIMIT 1), '$.strategy')
|
|
971
|
+
UNION ALL
|
|
972
|
+
SELECT
|
|
973
|
+
'Workers Spawned',
|
|
974
|
+
CAST(COUNT(*) AS TEXT)
|
|
975
|
+
FROM epic_events WHERE type = 'worker_spawned'
|
|
976
|
+
UNION ALL
|
|
977
|
+
SELECT
|
|
978
|
+
'Tasks Completed',
|
|
979
|
+
CAST(COUNT(*) AS TEXT)
|
|
980
|
+
FROM epic_events WHERE type = 'worker_completed' AND json_extract(data, '$.success') = 1
|
|
981
|
+
UNION ALL
|
|
982
|
+
SELECT
|
|
983
|
+
'Tasks Failed',
|
|
984
|
+
CAST(COUNT(*) AS TEXT)
|
|
985
|
+
FROM epic_events WHERE type = 'worker_completed' AND json_extract(data, '$.success') = 0
|
|
986
|
+
UNION ALL
|
|
987
|
+
SELECT
|
|
988
|
+
'File Conflicts',
|
|
989
|
+
CAST(COUNT(*) AS TEXT)
|
|
990
|
+
FROM epic_events WHERE type = 'file_conflict'
|
|
991
|
+
UNION ALL
|
|
992
|
+
SELECT
|
|
993
|
+
'Checkpoints',
|
|
994
|
+
CAST(COUNT(*) AS TEXT)
|
|
995
|
+
FROM epic_events WHERE type = 'checkpoint_created';
|
|
996
|
+
|
|
997
|
+
-- Worker performance comparison
|
|
998
|
+
SELECT
|
|
999
|
+
json_extract(data, '$.worker_agent') as worker,
|
|
1000
|
+
COUNT(*) as tasks_completed,
|
|
1001
|
+
SUM(CASE WHEN json_extract(data, '$.success') = 1 THEN 1 ELSE 0 END) as successful,
|
|
1002
|
+
ROUND(AVG(json_extract(data, '$.duration_ms')) / 1000.0, 1) as avg_duration_sec,
|
|
1003
|
+
ROUND(AVG(CAST(json_length(json_extract(data, '$.files_touched')) AS REAL)), 1) as avg_files_touched
|
|
1004
|
+
FROM events
|
|
1005
|
+
WHERE type = 'worker_completed'
|
|
1006
|
+
AND json_extract(data, '$.epic_id') = 'mjmas3zxlmg'
|
|
1007
|
+
GROUP BY worker
|
|
1008
|
+
ORDER BY successful DESC, avg_duration_sec ASC;
|
|
1009
|
+
|
|
1010
|
+
## Dashboard Data Layer (Programmatic Access)
|
|
1011
|
+
|
|
1012
|
+
For building custom dashboards or monitoring tools, use the dashboard data layer from `opencode-swarm-plugin`:
|
|
229
1013
|
|
|
230
1014
|
```typescript
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
duration_ms: number;
|
|
262
|
-
};
|
|
1015
|
+
import {
|
|
1016
|
+
getWorkerStatus,
|
|
1017
|
+
getSubtaskProgress,
|
|
1018
|
+
getFileLocks,
|
|
1019
|
+
getRecentMessages,
|
|
1020
|
+
getEpicList
|
|
1021
|
+
} from "opencode-swarm-plugin";
|
|
1022
|
+
import { getSwarmMailLibSQL } from "swarm-mail";
|
|
1023
|
+
|
|
1024
|
+
const db = await getSwarmMailLibSQL("/my/project");
|
|
1025
|
+
|
|
1026
|
+
// Get worker statuses (idle/working/blocked)
|
|
1027
|
+
const workers = await getWorkerStatus(db, { project_key: "/my/project" });
|
|
1028
|
+
// Returns: [{ agent_name: "Worker1", status: "working", current_task: "bd-123", last_activity: "2025-12-26T..." }]
|
|
1029
|
+
|
|
1030
|
+
// Get subtask progress for an epic
|
|
1031
|
+
const progress = await getSubtaskProgress(db, "mjmas3zxlmg");
|
|
1032
|
+
// Returns: [{ bead_id: "bd-123", title: "Add auth", status: "in_progress", progress_percent: 50 }]
|
|
1033
|
+
|
|
1034
|
+
// Get active file locks
|
|
1035
|
+
const locks = await getFileLocks(db);
|
|
1036
|
+
// Returns: [{ path: "src/auth.ts", agent_name: "Worker1", reason: "bd-123: Auth", acquired_at: "...", ttl_seconds: 3600 }]
|
|
1037
|
+
|
|
1038
|
+
// Get recent messages
|
|
1039
|
+
const messages = await getRecentMessages(db, { limit: 10, importance: "high" });
|
|
1040
|
+
// Returns: [{ id: 1, from: "Worker1", to: ["Coordinator"], subject: "BLOCKED", timestamp: "...", importance: "high" }]
|
|
1041
|
+
|
|
1042
|
+
// List all epics
|
|
1043
|
+
const epics = await getEpicList(db);
|
|
1044
|
+
// Returns: [{ epic_id: "mjmas3zxlmg", title: "Feature X", subtask_count: 5, completed_count: 3 }]
|
|
263
1045
|
```
|
|
264
1046
|
|
|
1047
|
+
**Use cases:**
|
|
1048
|
+
- Build custom TUI dashboards (see `swarm dashboard` implementation)
|
|
1049
|
+
- Export to monitoring systems (Prometheus, Datadog)
|
|
1050
|
+
- Real-time web dashboards (WebSocket + React)
|
|
1051
|
+
- Alerting (detect stuck tasks, file conflicts)
|
|
1052
|
+
|
|
265
1053
|
## Projections
|
|
266
1054
|
|
|
267
|
-
Materialized views automatically updated from events:
|
|
1055
|
+
Materialized views automatically updated from events via triggers:
|
|
268
1056
|
|
|
269
|
-
| Projection | Description |
|
|
270
|
-
| ------------------- | ---------------------------------- |
|
|
271
|
-
| `agents` | Active agents per project |
|
|
272
|
-
| `messages` | Agent inbox/outbox with recipients |
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
1057
|
+
| Projection | Description | Updated By |
|
|
1058
|
+
| ------------------- | ---------------------------------- | ---------- |
|
|
1059
|
+
| `agents` | Active agents per project | `agent_registered`, `agent_active` |
|
|
1060
|
+
| `messages` | Agent inbox/outbox with recipients | `message_sent` |
|
|
1061
|
+
| `message_recipients`| Many-to-many message targets | `message_sent` |
|
|
1062
|
+
| `message_reads` | Read receipts | `message_read` |
|
|
1063
|
+
| `reservations` | Current file locks with TTL | `file_reserved`, `file_released` |
|
|
1064
|
+
| `swarm_contexts` | Checkpoint state for recovery | `swarm_checkpointed` |
|
|
1065
|
+
| `eval_records` | Outcome data for learning | `eval_captured`, `eval_scored`, `eval_finalized` |
|
|
276
1066
|
|
|
277
1067
|
## Testing
|
|
278
1068
|
|
|
@@ -372,6 +1162,119 @@ interface SwarmMailAdapter {
|
|
|
372
1162
|
}
|
|
373
1163
|
```
|
|
374
1164
|
|
|
1165
|
+
## Session Indexing
|
|
1166
|
+
|
|
1167
|
+
Cross-agent session search and indexing layer for multi-agent conversation history.
|
|
1168
|
+
|
|
1169
|
+
**Inspired by [CASS (coding_agent_session_search)](https://github.com/Dicklesworthstone/coding_agent_session_search) by Dicklesworthstone** - we've adapted the session indexing concepts for TypeScript + libSQL.
|
|
1170
|
+
|
|
1171
|
+
```
|
|
1172
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
1173
|
+
│ SESSION INDEXING ARCHITECTURE │
|
|
1174
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1175
|
+
│ │
|
|
1176
|
+
│ ┌──────────────┐ │
|
|
1177
|
+
│ │ Agent Logs │ (OpenCode, Cursor, Claude, etc.) │
|
|
1178
|
+
│ │ ~/.config/ │ │
|
|
1179
|
+
│ │ *.jsonl │ │
|
|
1180
|
+
│ └──────┬───────┘ │
|
|
1181
|
+
│ │ │
|
|
1182
|
+
│ ▼ │
|
|
1183
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1184
|
+
│ │ SessionParser (JSONL → Normalized) │ │
|
|
1185
|
+
│ │ ├─ Line-by-line parsing │ │
|
|
1186
|
+
│ │ └─ Multi-agent format detection │ │
|
|
1187
|
+
│ └──────────────┬───────────────────────────┘ │
|
|
1188
|
+
│ │ │
|
|
1189
|
+
│ ▼ │
|
|
1190
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1191
|
+
│ │ ChunkProcessor (Message-level chunks) │ │
|
|
1192
|
+
│ │ ├─ Sliding window (512 token chunks) │ │
|
|
1193
|
+
│ │ ├─ Ollama embeddings (mxbai-embed-large)│ │
|
|
1194
|
+
│ │ └─ Store in semantic-memory │ │
|
|
1195
|
+
│ └──────────────┬───────────────────────────┘ │
|
|
1196
|
+
│ │ │
|
|
1197
|
+
│ ▼ │
|
|
1198
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1199
|
+
│ │ libSQL Storage (via semantic-memory) │ │
|
|
1200
|
+
│ │ ├─ memories table (vector search) │ │
|
|
1201
|
+
│ │ ├─ metadata (agent, session, timestamp) │ │
|
|
1202
|
+
│ │ └─ libSQL vec extension similarity │ │
|
|
1203
|
+
│ └──────────────┬───────────────────────────┘ │
|
|
1204
|
+
│ │ │
|
|
1205
|
+
│ ▼ │
|
|
1206
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1207
|
+
│ │ StalenessDetector (index freshness) │ │
|
|
1208
|
+
│ │ ├─ Track indexed sessions + mtimes │ │
|
|
1209
|
+
│ │ ├─ Detect new/modified files │ │
|
|
1210
|
+
│ │ └─ Trigger re-indexing when needed │ │
|
|
1211
|
+
│ └──────────────┬───────────────────────────┘ │
|
|
1212
|
+
│ │ │
|
|
1213
|
+
│ ▼ │
|
|
1214
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1215
|
+
│ │ Search API (cass_* plugin tools) │ │
|
|
1216
|
+
│ │ ├─ Semantic search (vector similarity) │ │
|
|
1217
|
+
│ │ ├─ Full-text search (FTS5) │ │
|
|
1218
|
+
│ │ ├─ Pagination + field projection │ │
|
|
1219
|
+
│ │ └─ Session viewer (expand context) │ │
|
|
1220
|
+
│ └──────────────────────────────────────────┘ │
|
|
1221
|
+
│ │
|
|
1222
|
+
└─────────────────────────────────────────────────────────────┘
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
### Components
|
|
1226
|
+
|
|
1227
|
+
| Component | Purpose | Key Methods |
|
|
1228
|
+
|-----------|---------|-------------|
|
|
1229
|
+
| **ChunkProcessor** | Message-level chunking + embedding | `processMessage()`, `generateEmbedding()` |
|
|
1230
|
+
| **SessionParser** | JSONL → NormalizedMessage | `parseLine()`, `detectAgent()` |
|
|
1231
|
+
| **SessionViewer** | Line-by-line JSONL reader | `readLines()`, `expandContext()` |
|
|
1232
|
+
| **StalenessDetector** | Index freshness tracking | `recordIndexed()`, `checkStaleness()` |
|
|
1233
|
+
| **Pagination** | Field projection for compact output | `projectSearchResult()`, `FIELD_SETS` |
|
|
1234
|
+
|
|
1235
|
+
### Usage
|
|
1236
|
+
|
|
1237
|
+
```typescript
|
|
1238
|
+
import { ChunkProcessor, StalenessDetector } from "swarm-mail";
|
|
1239
|
+
import { createSemanticMemory } from "swarm-mail";
|
|
1240
|
+
|
|
1241
|
+
// 1. Initialize semantic memory (vector store)
|
|
1242
|
+
const memory = await createSemanticMemory("/my/project");
|
|
1243
|
+
|
|
1244
|
+
// 2. Create chunk processor
|
|
1245
|
+
const processor = new ChunkProcessor(memory);
|
|
1246
|
+
|
|
1247
|
+
// 3. Process a session message
|
|
1248
|
+
const chunks = await processor.processMessage({
|
|
1249
|
+
session_id: "abc123",
|
|
1250
|
+
agent: "opencode",
|
|
1251
|
+
timestamp: Date.now(),
|
|
1252
|
+
role: "assistant",
|
|
1253
|
+
content: "Message text...",
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
// 4. Search across indexed sessions
|
|
1257
|
+
const results = await memory.find("authentication error", { limit: 5 });
|
|
1258
|
+
|
|
1259
|
+
// 5. Check if re-indexing needed
|
|
1260
|
+
const detector = new StalenessDetector(memory);
|
|
1261
|
+
await detector.recordIndexed("/path/to/session.jsonl", Date.now());
|
|
1262
|
+
const isStale = await detector.checkStaleness("/path/to/session.jsonl");
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
### Plugin Integration
|
|
1266
|
+
|
|
1267
|
+
The session indexing layer powers the `cass_*` plugin tools (see AGENTS.md):
|
|
1268
|
+
|
|
1269
|
+
- `cass_search` - Search across all agent histories
|
|
1270
|
+
- `cass_view` - View specific session
|
|
1271
|
+
- `cass_expand` - Expand context around line
|
|
1272
|
+
- `cass_index` - Build/rebuild index
|
|
1273
|
+
- `cass_health` - Check index readiness
|
|
1274
|
+
- `cass_stats` - Show index statistics
|
|
1275
|
+
|
|
1276
|
+
**Before solving problems from scratch, query the index** - another agent may have already solved it.
|
|
1277
|
+
|
|
375
1278
|
## Resources
|
|
376
1279
|
|
|
377
1280
|
- **Documentation:** [swarmtools.ai/docs](https://swarmtools.ai/docs)
|