opencode-swarm-plugin 0.44.0 → 0.44.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/bin/swarm.serve.test.ts +6 -4
- package/bin/swarm.ts +18 -12
- package/dist/compaction-prompt-scoring.js +139 -0
- package/dist/eval-capture.js +12811 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/hive.js +14834 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7743 -62593
- package/dist/plugin.js +24052 -78907
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-prompts.js +39407 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/dist/swarm-validation.d.ts +127 -0
- package/dist/swarm-validation.d.ts.map +1 -0
- package/dist/validators/index.d.ts +7 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/schema-validator.d.ts +58 -0
- package/dist/validators/schema-validator.d.ts.map +1 -0
- package/package.json +17 -5
- package/.changeset/swarm-insights-data-layer.md +0 -63
- package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
- package/.hive/analysis/session-data-quality-audit.md +0 -320
- package/.hive/eval-results.json +0 -483
- package/.hive/issues.jsonl +0 -138
- package/.hive/memories.jsonl +0 -729
- package/.opencode/eval-history.jsonl +0 -327
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -2286
- package/SCORER-ANALYSIS.md +0 -598
- package/docs/analysis/subagent-coordination-patterns.md +0 -902
- package/docs/analysis-socratic-planner-pattern.md +0 -504
- package/docs/planning/ADR-001-monorepo-structure.md +0 -171
- package/docs/planning/ADR-002-package-extraction.md +0 -393
- package/docs/planning/ADR-003-performance-improvements.md +0 -451
- package/docs/planning/ADR-004-message-queue-features.md +0 -187
- package/docs/planning/ADR-005-devtools-observability.md +0 -202
- package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
- package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
- package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
- package/docs/planning/ADR-010-cass-inhousing.md +0 -1215
- package/docs/planning/ROADMAP.md +0 -368
- package/docs/semantic-memory-cli-syntax.md +0 -123
- package/docs/swarm-mail-architecture.md +0 -1147
- package/docs/testing/context-recovery-test.md +0 -470
- package/evals/ARCHITECTURE.md +0 -1189
- package/evals/README.md +0 -768
- package/evals/compaction-prompt.eval.ts +0 -149
- package/evals/compaction-resumption.eval.ts +0 -289
- package/evals/coordinator-behavior.eval.ts +0 -307
- package/evals/coordinator-session.eval.ts +0 -154
- package/evals/evalite.config.ts.bak +0 -15
- package/evals/example.eval.ts +0 -31
- package/evals/fixtures/cass-baseline.ts +0 -217
- package/evals/fixtures/compaction-cases.ts +0 -350
- package/evals/fixtures/compaction-prompt-cases.ts +0 -311
- package/evals/fixtures/coordinator-sessions.ts +0 -328
- package/evals/fixtures/decomposition-cases.ts +0 -105
- package/evals/lib/compaction-loader.test.ts +0 -248
- package/evals/lib/compaction-loader.ts +0 -320
- package/evals/lib/data-loader.evalite-test.ts +0 -289
- package/evals/lib/data-loader.test.ts +0 -345
- package/evals/lib/data-loader.ts +0 -281
- package/evals/lib/llm.ts +0 -115
- package/evals/scorers/compaction-prompt-scorers.ts +0 -145
- package/evals/scorers/compaction-scorers.ts +0 -305
- package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
- package/evals/scorers/coordinator-discipline.ts +0 -325
- package/evals/scorers/index.test.ts +0 -146
- package/evals/scorers/index.ts +0 -328
- package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
- package/evals/scorers/outcome-scorers.ts +0 -349
- package/evals/swarm-decomposition.eval.ts +0 -121
- package/examples/commands/swarm.md +0 -745
- package/examples/plugin-wrapper-template.ts +0 -2515
- package/examples/skills/hive-workflow/SKILL.md +0 -212
- package/examples/skills/skill-creator/SKILL.md +0 -223
- package/examples/skills/swarm-coordination/SKILL.md +0 -292
- package/global-skills/cli-builder/SKILL.md +0 -344
- package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
- package/global-skills/learning-systems/SKILL.md +0 -644
- package/global-skills/skill-creator/LICENSE.txt +0 -202
- package/global-skills/skill-creator/SKILL.md +0 -352
- package/global-skills/skill-creator/references/output-patterns.md +0 -82
- package/global-skills/skill-creator/references/workflows.md +0 -28
- package/global-skills/swarm-coordination/SKILL.md +0 -995
- package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
- package/global-skills/swarm-coordination/references/strategies.md +0 -138
- package/global-skills/system-design/SKILL.md +0 -213
- package/global-skills/testing-patterns/SKILL.md +0 -430
- package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
- package/opencode-swarm-plugin-0.30.7.tgz +0 -0
- package/opencode-swarm-plugin-0.31.0.tgz +0 -0
- package/scripts/cleanup-test-memories.ts +0 -346
- package/scripts/init-skill.ts +0 -222
- package/scripts/migrate-unknown-sessions.ts +0 -349
- package/scripts/validate-skill.ts +0 -204
- package/src/agent-mail.ts +0 -1724
- package/src/anti-patterns.test.ts +0 -1167
- package/src/anti-patterns.ts +0 -448
- package/src/compaction-capture.integration.test.ts +0 -257
- package/src/compaction-hook.test.ts +0 -838
- package/src/compaction-hook.ts +0 -1204
- package/src/compaction-observability.integration.test.ts +0 -139
- package/src/compaction-observability.test.ts +0 -187
- package/src/compaction-observability.ts +0 -324
- package/src/compaction-prompt-scorers.test.ts +0 -475
- package/src/compaction-prompt-scoring.ts +0 -300
- package/src/contributor-tools.test.ts +0 -133
- package/src/contributor-tools.ts +0 -201
- package/src/dashboard.test.ts +0 -611
- package/src/dashboard.ts +0 -462
- package/src/error-enrichment.test.ts +0 -403
- package/src/error-enrichment.ts +0 -219
- package/src/eval-capture.test.ts +0 -1015
- package/src/eval-capture.ts +0 -929
- package/src/eval-gates.test.ts +0 -306
- package/src/eval-gates.ts +0 -218
- package/src/eval-history.test.ts +0 -508
- package/src/eval-history.ts +0 -214
- package/src/eval-learning.test.ts +0 -378
- package/src/eval-learning.ts +0 -360
- package/src/eval-runner.test.ts +0 -223
- package/src/eval-runner.ts +0 -402
- package/src/export-tools.test.ts +0 -476
- package/src/export-tools.ts +0 -257
- package/src/hive.integration.test.ts +0 -2241
- package/src/hive.ts +0 -1628
- package/src/index.ts +0 -940
- package/src/learning.integration.test.ts +0 -1815
- package/src/learning.ts +0 -1079
- package/src/logger.test.ts +0 -189
- package/src/logger.ts +0 -135
- package/src/mandate-promotion.test.ts +0 -473
- package/src/mandate-promotion.ts +0 -239
- package/src/mandate-storage.integration.test.ts +0 -601
- package/src/mandate-storage.test.ts +0 -578
- package/src/mandate-storage.ts +0 -794
- package/src/mandates.ts +0 -540
- package/src/memory-tools.test.ts +0 -195
- package/src/memory-tools.ts +0 -344
- package/src/memory.integration.test.ts +0 -334
- package/src/memory.test.ts +0 -158
- package/src/memory.ts +0 -527
- package/src/model-selection.test.ts +0 -188
- package/src/model-selection.ts +0 -68
- package/src/observability-tools.test.ts +0 -359
- package/src/observability-tools.ts +0 -871
- package/src/output-guardrails.test.ts +0 -438
- package/src/output-guardrails.ts +0 -381
- package/src/pattern-maturity.test.ts +0 -1160
- package/src/pattern-maturity.ts +0 -525
- package/src/planning-guardrails.test.ts +0 -491
- package/src/planning-guardrails.ts +0 -438
- package/src/plugin.ts +0 -23
- package/src/post-compaction-tracker.test.ts +0 -251
- package/src/post-compaction-tracker.ts +0 -237
- package/src/query-tools.test.ts +0 -636
- package/src/query-tools.ts +0 -324
- package/src/rate-limiter.integration.test.ts +0 -466
- package/src/rate-limiter.ts +0 -774
- package/src/replay-tools.test.ts +0 -496
- package/src/replay-tools.ts +0 -240
- package/src/repo-crawl.integration.test.ts +0 -441
- package/src/repo-crawl.ts +0 -610
- package/src/schemas/cell-events.test.ts +0 -347
- package/src/schemas/cell-events.ts +0 -807
- package/src/schemas/cell.ts +0 -257
- package/src/schemas/evaluation.ts +0 -166
- package/src/schemas/index.test.ts +0 -199
- package/src/schemas/index.ts +0 -286
- package/src/schemas/mandate.ts +0 -232
- package/src/schemas/swarm-context.ts +0 -115
- package/src/schemas/task.ts +0 -161
- package/src/schemas/worker-handoff.test.ts +0 -302
- package/src/schemas/worker-handoff.ts +0 -131
- package/src/sessions/agent-discovery.test.ts +0 -137
- package/src/sessions/agent-discovery.ts +0 -112
- package/src/sessions/index.ts +0 -15
- package/src/skills.integration.test.ts +0 -1192
- package/src/skills.test.ts +0 -643
- package/src/skills.ts +0 -1549
- package/src/storage.integration.test.ts +0 -341
- package/src/storage.ts +0 -884
- package/src/structured.integration.test.ts +0 -817
- package/src/structured.test.ts +0 -1046
- package/src/structured.ts +0 -762
- package/src/swarm-decompose.test.ts +0 -188
- package/src/swarm-decompose.ts +0 -1302
- package/src/swarm-deferred.integration.test.ts +0 -157
- package/src/swarm-deferred.test.ts +0 -38
- package/src/swarm-insights.test.ts +0 -214
- package/src/swarm-insights.ts +0 -459
- package/src/swarm-mail.integration.test.ts +0 -970
- package/src/swarm-mail.ts +0 -739
- package/src/swarm-orchestrate.integration.test.ts +0 -282
- package/src/swarm-orchestrate.test.ts +0 -548
- package/src/swarm-orchestrate.ts +0 -3084
- package/src/swarm-prompts.test.ts +0 -1270
- package/src/swarm-prompts.ts +0 -2077
- package/src/swarm-research.integration.test.ts +0 -701
- package/src/swarm-research.test.ts +0 -698
- package/src/swarm-research.ts +0 -472
- package/src/swarm-review.integration.test.ts +0 -285
- package/src/swarm-review.test.ts +0 -879
- package/src/swarm-review.ts +0 -709
- package/src/swarm-strategies.ts +0 -407
- package/src/swarm-worktree.test.ts +0 -501
- package/src/swarm-worktree.ts +0 -575
- package/src/swarm.integration.test.ts +0 -2377
- package/src/swarm.ts +0 -38
- package/src/tool-adapter.integration.test.ts +0 -1221
- package/src/tool-availability.ts +0 -461
- package/tsconfig.json +0 -28
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
# ADR-003: Performance Improvements via Live Queries and Batching
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Proposed
|
|
6
|
-
|
|
7
|
-
## Context
|
|
8
|
-
|
|
9
|
-
Current Swarm Mail implementation uses polling for real-time updates, which is inefficient:
|
|
10
|
-
|
|
11
|
-
- `getInbox()` polls every 500ms to detect new messages
|
|
12
|
-
- File reservation conflicts checked via repeated queries
|
|
13
|
-
- High CPU usage in background polling loops
|
|
14
|
-
- Latency: 250-500ms average between event and notification
|
|
15
|
-
|
|
16
|
-
Research shows PGLite provides two superior alternatives to polling:
|
|
17
|
-
|
|
18
|
-
1. **LISTEN/NOTIFY** - PostgreSQL pub/sub via `pg.listen()` and `pg.unlisten()`
|
|
19
|
-
2. **Live Queries** - Incremental query results via `live.incrementalQuery()` (RECOMMENDED)
|
|
20
|
-
|
|
21
|
-
Additionally, batch operations can reduce transaction overhead:
|
|
22
|
-
|
|
23
|
-
- Current: Individual transactions per message (N transactions)
|
|
24
|
-
- Improved: Batched inserts via `.transaction()` or `.exec()` (1 transaction)
|
|
25
|
-
|
|
26
|
-
## Decision
|
|
27
|
-
|
|
28
|
-
### Replace Polling with Live Queries
|
|
29
|
-
|
|
30
|
-
**Why Live Queries over LISTEN/NOTIFY:**
|
|
31
|
-
|
|
32
|
-
- Simpler API - returns incremental result sets automatically
|
|
33
|
-
- No manual query re-execution needed
|
|
34
|
-
- Built-in change detection
|
|
35
|
-
- Works with existing SQL queries
|
|
36
|
-
|
|
37
|
-
**Implementation:**
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
// Before (polling):
|
|
41
|
-
async function watchInbox(
|
|
42
|
-
agentName: string,
|
|
43
|
-
callback: (messages: Message[]) => void,
|
|
44
|
-
) {
|
|
45
|
-
const interval = setInterval(async () => {
|
|
46
|
-
const messages = await getInbox(agentName, { limit: 5 });
|
|
47
|
-
callback(messages);
|
|
48
|
-
}, 500);
|
|
49
|
-
return () => clearInterval(interval);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// After (live query):
|
|
53
|
-
import { live } from "@electric-sql/pglite/live";
|
|
54
|
-
|
|
55
|
-
async function watchInbox(
|
|
56
|
-
agentName: string,
|
|
57
|
-
callback: (messages: Message[]) => void,
|
|
58
|
-
) {
|
|
59
|
-
const liveQuery = await db.live.incrementalQuery(
|
|
60
|
-
`SELECT id, sender, subject, importance, received_at
|
|
61
|
-
FROM messages
|
|
62
|
-
WHERE recipient = $1 AND read_at IS NULL
|
|
63
|
-
ORDER BY importance DESC, received_at DESC
|
|
64
|
-
LIMIT 5`,
|
|
65
|
-
[agentName],
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
liveQuery.subscribe(({ rows }) => {
|
|
69
|
-
callback(rows as Message[]);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return () => liveQuery.unsubscribe();
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Projection Updates:**
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
// Watch for new events in real-time
|
|
80
|
-
const liveEvents = await db.live.incrementalQuery(
|
|
81
|
-
`SELECT * FROM events WHERE sequence > $1 ORDER BY sequence ASC`,
|
|
82
|
-
[lastProcessedSequence],
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
liveEvents.subscribe(({ rows }) => {
|
|
86
|
-
rows.forEach((event) => updateProjection(event));
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Batch Operations for Multi-Insert
|
|
91
|
-
|
|
92
|
-
**Use `.transaction()` for Multiple Related Operations:**
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// Before: N transactions
|
|
96
|
-
for (const message of messages) {
|
|
97
|
-
await sendMessage(message); // 1 transaction each
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// After: 1 transaction
|
|
101
|
-
await db.transaction(async (tx) => {
|
|
102
|
-
for (const message of messages) {
|
|
103
|
-
await tx.query(
|
|
104
|
-
`INSERT INTO messages (sender, recipient, subject, body) VALUES ($1, $2, $3, $4)`,
|
|
105
|
-
[message.sender, message.recipient, message.subject, message.body],
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Use `.exec()` for Batch SQL:**
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
// Atomic multi-statement execution
|
|
115
|
-
await db.exec(`
|
|
116
|
-
BEGIN;
|
|
117
|
-
INSERT INTO messages (sender, recipient, subject) VALUES ('Alice', 'Bob', 'Test 1');
|
|
118
|
-
INSERT INTO messages (sender, recipient, subject) VALUES ('Alice', 'Bob', 'Test 2');
|
|
119
|
-
INSERT INTO messages (sender, recipient, subject) VALUES ('Alice', 'Bob', 'Test 3');
|
|
120
|
-
COMMIT;
|
|
121
|
-
`);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Connection Pooling Decision
|
|
125
|
-
|
|
126
|
-
**NOT NEEDED for PGLite:**
|
|
127
|
-
|
|
128
|
-
- PGLite runs in-process (WASM embedded database)
|
|
129
|
-
- Single-user design (no concurrent connections)
|
|
130
|
-
- Connection pooling is for client-server PostgreSQL
|
|
131
|
-
- Would add unnecessary complexity
|
|
132
|
-
|
|
133
|
-
## Consequences
|
|
134
|
-
|
|
135
|
-
### Easier
|
|
136
|
-
|
|
137
|
-
- **Real-time updates** - Sub-millisecond latency for new messages/events
|
|
138
|
-
- **Lower CPU usage** - No background polling loops
|
|
139
|
-
- **Fewer queries** - Live queries push changes, don't pull
|
|
140
|
-
- **Atomic batches** - Multi-message sends in 1 transaction
|
|
141
|
-
- **Simplified code** - No interval management, cleanup handled by unsubscribe
|
|
142
|
-
|
|
143
|
-
### More Difficult
|
|
144
|
-
|
|
145
|
-
- **Subscription management** - Must track and unsubscribe live queries
|
|
146
|
-
- **Memory usage** - Live queries hold result sets in memory
|
|
147
|
-
- **Testing complexity** - Async subscriptions harder to test than sync polls
|
|
148
|
-
- **Debugging** - Push-based updates less visible in logs
|
|
149
|
-
|
|
150
|
-
### Performance Impact (Estimated)
|
|
151
|
-
|
|
152
|
-
| Metric | Before (Polling) | After (Live Queries) | Improvement |
|
|
153
|
-
| -------------------- | ---------------- | ----------------------- | --------------- |
|
|
154
|
-
| Notification latency | 250-500ms | <10ms | 25-50x faster |
|
|
155
|
-
| CPU usage (idle) | 5-10% (polling) | <1% (event-driven) | 5-10x reduction |
|
|
156
|
-
| Queries per second | 2-4 (polling) | 0 (push) | Eliminated |
|
|
157
|
-
| Transaction overhead | N (individual) | 1 (batched) | N speedup |
|
|
158
|
-
| Memory usage | Low | Medium (result caching) | +10-20% |
|
|
159
|
-
|
|
160
|
-
### Risks & Mitigations
|
|
161
|
-
|
|
162
|
-
| Risk | Impact | Mitigation |
|
|
163
|
-
| --------------------------------- | ------ | --------------------------------------------------- |
|
|
164
|
-
| Memory leaks from subscriptions | High | Enforce unsubscribe in cleanup, add timeout guards |
|
|
165
|
-
| Live queries fail on syntax error | High | Validate SQL in tests, fallback to polling on error |
|
|
166
|
-
| Large result sets in memory | Medium | Hard limit on LIMIT clause (max 100 rows) |
|
|
167
|
-
| Subscription overhead at scale | Medium | Pool subscriptions, deduplicate queries |
|
|
168
|
-
| Debugging push updates | Low | Add subscription logging, event stream tracing |
|
|
169
|
-
|
|
170
|
-
## Implementation Notes
|
|
171
|
-
|
|
172
|
-
### Phase 1: Live Query Infrastructure (Week 1)
|
|
173
|
-
|
|
174
|
-
**1.1 Create Live Query Wrapper**
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
// src/streams/live-query.ts
|
|
178
|
-
import { live } from "@electric-sql/pglite/live";
|
|
179
|
-
import type { PGlite } from "@electric-sql/pglite";
|
|
180
|
-
|
|
181
|
-
interface LiveQueryOptions<T> {
|
|
182
|
-
db: PGlite;
|
|
183
|
-
query: string;
|
|
184
|
-
params: unknown[];
|
|
185
|
-
onUpdate: (rows: T[]) => void;
|
|
186
|
-
onError?: (error: Error) => void;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export async function createLiveQuery<T>(options: LiveQueryOptions<T>) {
|
|
190
|
-
const { db, query, params, onUpdate, onError } = options;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const liveQuery = await db.live.incrementalQuery(query, params);
|
|
194
|
-
|
|
195
|
-
const unsubscribe = liveQuery.subscribe({
|
|
196
|
-
next: ({ rows }) => onUpdate(rows as T[]),
|
|
197
|
-
error: onError || ((err) => console.error("Live query error:", err)),
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
unsubscribe,
|
|
202
|
-
refresh: () => liveQuery.refresh(),
|
|
203
|
-
};
|
|
204
|
-
} catch (error) {
|
|
205
|
-
if (onError) onError(error as Error);
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**1.2 Add Cleanup Tracking**
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// Track active subscriptions for cleanup
|
|
215
|
-
const activeSubscriptions = new Set<() => void>();
|
|
216
|
-
|
|
217
|
-
export function registerSubscription(unsubscribe: () => void) {
|
|
218
|
-
activeSubscriptions.add(unsubscribe);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function cleanupAllSubscriptions() {
|
|
222
|
-
activeSubscriptions.forEach((unsub) => unsub());
|
|
223
|
-
activeSubscriptions.clear();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Call on shutdown
|
|
227
|
-
process.on("SIGTERM", cleanupAllSubscriptions);
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### Phase 2: Replace Polling (Week 2)
|
|
231
|
-
|
|
232
|
-
**2.1 Inbox Watching**
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
// src/agent-mail.ts
|
|
236
|
-
export async function watchInbox(
|
|
237
|
-
db: PGlite,
|
|
238
|
-
agentName: string,
|
|
239
|
-
callback: (messages: Message[]) => void,
|
|
240
|
-
) {
|
|
241
|
-
const { unsubscribe } = await createLiveQuery<Message>({
|
|
242
|
-
db,
|
|
243
|
-
query: `
|
|
244
|
-
SELECT id, sender, subject, importance, received_at
|
|
245
|
-
FROM messages
|
|
246
|
-
WHERE recipient = $1 AND read_at IS NULL
|
|
247
|
-
ORDER BY importance DESC, received_at DESC
|
|
248
|
-
LIMIT 5
|
|
249
|
-
`,
|
|
250
|
-
params: [agentName],
|
|
251
|
-
onUpdate: callback,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
registerSubscription(unsubscribe);
|
|
255
|
-
return unsubscribe;
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
**2.2 Event Stream Watching**
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
// src/streams/projections.ts
|
|
263
|
-
export async function watchEvents(
|
|
264
|
-
db: PGlite,
|
|
265
|
-
fromSequence: number,
|
|
266
|
-
callback: (events: SwarmMailEvent[]) => void,
|
|
267
|
-
) {
|
|
268
|
-
const { unsubscribe } = await createLiveQuery<SwarmMailEvent>({
|
|
269
|
-
db,
|
|
270
|
-
query: `SELECT * FROM events WHERE sequence > $1 ORDER BY sequence ASC`,
|
|
271
|
-
params: [fromSequence],
|
|
272
|
-
onUpdate: callback,
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
registerSubscription(unsubscribe);
|
|
276
|
-
return unsubscribe;
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**2.3 File Reservation Conflicts**
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
// Watch for conflicts with my reservations
|
|
284
|
-
export async function watchReservationConflicts(
|
|
285
|
-
db: PGlite,
|
|
286
|
-
myReservations: string[],
|
|
287
|
-
callback: (conflicts: Conflict[]) => void,
|
|
288
|
-
) {
|
|
289
|
-
const { unsubscribe } = await createLiveQuery<Conflict>({
|
|
290
|
-
db,
|
|
291
|
-
query: `
|
|
292
|
-
SELECT r.* FROM file_reservations r
|
|
293
|
-
WHERE r.path_pattern = ANY($1)
|
|
294
|
-
AND r.agent_name != $2
|
|
295
|
-
AND r.expires_at > NOW()
|
|
296
|
-
`,
|
|
297
|
-
params: [myReservations, currentAgent],
|
|
298
|
-
onUpdate: callback,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
return unsubscribe;
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Phase 3: Batch Operations (Week 3)
|
|
306
|
-
|
|
307
|
-
**3.1 Batch Message Send**
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
export async function sendMessages(db: PGlite, messages: MessageDraft[]) {
|
|
311
|
-
await db.transaction(async (tx) => {
|
|
312
|
-
for (const msg of messages) {
|
|
313
|
-
await tx.query(
|
|
314
|
-
`INSERT INTO messages (sender, recipient, subject, body, thread_id, importance)
|
|
315
|
-
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
316
|
-
[
|
|
317
|
-
msg.sender,
|
|
318
|
-
msg.recipient,
|
|
319
|
-
msg.subject,
|
|
320
|
-
msg.body,
|
|
321
|
-
msg.thread_id,
|
|
322
|
-
msg.importance,
|
|
323
|
-
],
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
**3.2 Batch Event Append**
|
|
331
|
-
|
|
332
|
-
```typescript
|
|
333
|
-
export async function appendEvents(db: PGlite, events: SwarmMailEvent[]) {
|
|
334
|
-
await db.transaction(async (tx) => {
|
|
335
|
-
for (const event of events) {
|
|
336
|
-
await tx.query(
|
|
337
|
-
`INSERT INTO events (type, data, project_key, timestamp)
|
|
338
|
-
VALUES ($1, $2, $3, $4)`,
|
|
339
|
-
[
|
|
340
|
-
event.type,
|
|
341
|
-
JSON.stringify(event.data),
|
|
342
|
-
event.projectKey,
|
|
343
|
-
event.timestamp,
|
|
344
|
-
],
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### Phase 4: Testing & Benchmarks (Week 4)
|
|
352
|
-
|
|
353
|
-
**4.1 Integration Tests**
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
357
|
-
|
|
358
|
-
describe("Live Queries", () => {
|
|
359
|
-
let unsubscribe: () => void;
|
|
360
|
-
|
|
361
|
-
afterEach(() => {
|
|
362
|
-
unsubscribe?.();
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it("notifies on new message", async () => {
|
|
366
|
-
const updates: Message[] = [];
|
|
367
|
-
|
|
368
|
-
unsubscribe = await watchInbox(db, "Alice", (messages) => {
|
|
369
|
-
updates.push(...messages);
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
await sendMessage(db, { to: "Alice", subject: "Test", body: "Hello" });
|
|
373
|
-
|
|
374
|
-
// Wait for async update
|
|
375
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
376
|
-
|
|
377
|
-
expect(updates).toHaveLength(1);
|
|
378
|
-
expect(updates[0].subject).toBe("Test");
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it("cleans up on unsubscribe", async () => {
|
|
382
|
-
const updateCount = { count: 0 };
|
|
383
|
-
|
|
384
|
-
unsubscribe = await watchInbox(db, "Alice", () => {
|
|
385
|
-
updateCount.count++;
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
unsubscribe();
|
|
389
|
-
|
|
390
|
-
await sendMessage(db, {
|
|
391
|
-
to: "Alice",
|
|
392
|
-
subject: "After unsubscribe",
|
|
393
|
-
body: "Test",
|
|
394
|
-
});
|
|
395
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
396
|
-
|
|
397
|
-
expect(updateCount.count).toBe(0); // Should not increment after unsubscribe
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
**4.2 Performance Benchmarks**
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
import { bench, describe } from "vitest";
|
|
406
|
-
|
|
407
|
-
describe("Batch vs Individual Inserts", () => {
|
|
408
|
-
bench("Individual inserts (N transactions)", async () => {
|
|
409
|
-
for (let i = 0; i < 100; i++) {
|
|
410
|
-
await sendMessage(db, { to: "Bob", subject: `Msg ${i}`, body: "Test" });
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
bench("Batch insert (1 transaction)", async () => {
|
|
415
|
-
const messages = Array.from({ length: 100 }, (_, i) => ({
|
|
416
|
-
to: "Bob",
|
|
417
|
-
subject: `Msg ${i}`,
|
|
418
|
-
body: "Test",
|
|
419
|
-
}));
|
|
420
|
-
await sendMessages(db, messages);
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Success Criteria
|
|
426
|
-
|
|
427
|
-
- [ ] All polling loops removed from codebase
|
|
428
|
-
- [ ] Live queries handle 100+ concurrent watchers without leaks
|
|
429
|
-
- [ ] Batch operations 10x faster than individual inserts (benchmark)
|
|
430
|
-
- [ ] Notification latency <50ms for new messages (99th percentile)
|
|
431
|
-
- [ ] CPU usage <1% in idle state (no polling)
|
|
432
|
-
- [ ] Memory usage increase <20% compared to polling
|
|
433
|
-
- [ ] Integration tests pass with live queries
|
|
434
|
-
- [ ] Cleanup functions properly unsubscribe all watchers
|
|
435
|
-
|
|
436
|
-
### Migration Path
|
|
437
|
-
|
|
438
|
-
1. Add live query infrastructure (non-breaking)
|
|
439
|
-
2. Feature flag to toggle live queries vs polling (`ENABLE_LIVE_QUERIES=true`)
|
|
440
|
-
3. Run both in parallel for 1 week, monitor metrics
|
|
441
|
-
4. Default to live queries if metrics are better
|
|
442
|
-
5. Remove polling code after 2 weeks of stable live queries
|
|
443
|
-
6. Add batch operations (non-breaking performance improvement)
|
|
444
|
-
|
|
445
|
-
### Fallback Strategy
|
|
446
|
-
|
|
447
|
-
If live queries prove unstable:
|
|
448
|
-
|
|
449
|
-
- Keep polling as fallback (`ENABLE_LIVE_QUERIES=false`)
|
|
450
|
-
- Add exponential backoff to polling (reduce CPU)
|
|
451
|
-
- Consider hybrid: live queries for critical paths, polling for non-critical
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
# ADR-004: Advanced Message Queue Features
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Proposed
|
|
6
|
-
|
|
7
|
-
## Context
|
|
8
|
-
|
|
9
|
-
Current Swarm Mail message queue is basic:
|
|
10
|
-
|
|
11
|
-
- FIFO message delivery
|
|
12
|
-
- No priority ordering
|
|
13
|
-
- No retry/dead-letter handling
|
|
14
|
-
- No TTL/expiration
|
|
15
|
-
- No pub/sub or saga patterns
|
|
16
|
-
|
|
17
|
-
Research shows PGLite supports all enterprise message queue patterns:
|
|
18
|
-
|
|
19
|
-
- Priority queues via `ORDER BY priority DESC`
|
|
20
|
-
- Dead Letter Queues (DLQ) with retry tracking
|
|
21
|
-
- Time-To-Live (TTL) with expires_at timestamps
|
|
22
|
-
- Pub/sub via topic filtering
|
|
23
|
-
- Saga orchestration with compensation
|
|
24
|
-
|
|
25
|
-
## Decision
|
|
26
|
-
|
|
27
|
-
Add enterprise message queue features using standard SQL patterns.
|
|
28
|
-
|
|
29
|
-
### 1. Priority Queues
|
|
30
|
-
|
|
31
|
-
```sql
|
|
32
|
-
ALTER TABLE messages ADD COLUMN priority INTEGER DEFAULT 1;
|
|
33
|
-
CREATE INDEX idx_messages_priority ON messages(recipient, priority DESC, received_at DESC);
|
|
34
|
-
|
|
35
|
-
-- Fetch highest priority first
|
|
36
|
-
SELECT * FROM messages
|
|
37
|
-
WHERE recipient = $1 AND read_at IS NULL
|
|
38
|
-
ORDER BY priority DESC, received_at DESC
|
|
39
|
-
LIMIT 5;
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Priority levels:
|
|
43
|
-
|
|
44
|
-
- 0 = urgent (blocks, errors)
|
|
45
|
-
- 1 = high (task completion, conflicts)
|
|
46
|
-
- 2 = normal (progress updates)
|
|
47
|
-
- 3 = low (info, FYI)
|
|
48
|
-
|
|
49
|
-
### 2. Dead Letter Queue (DLQ)
|
|
50
|
-
|
|
51
|
-
```sql
|
|
52
|
-
CREATE TABLE failed_messages (
|
|
53
|
-
id SERIAL PRIMARY KEY,
|
|
54
|
-
original_message_id INTEGER REFERENCES messages(id),
|
|
55
|
-
retry_count INTEGER NOT NULL,
|
|
56
|
-
failure_reason TEXT NOT NULL,
|
|
57
|
-
failed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
58
|
-
will_retry_at TIMESTAMP, -- NULL = no retry
|
|
59
|
-
metadata JSONB
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
-- Move to DLQ after 3 retries
|
|
63
|
-
INSERT INTO failed_messages (original_message_id, retry_count, failure_reason)
|
|
64
|
-
VALUES ($1, $2, $3);
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 3. Time-To-Live (TTL)
|
|
68
|
-
|
|
69
|
-
```sql
|
|
70
|
-
ALTER TABLE messages ADD COLUMN expires_at TIMESTAMP;
|
|
71
|
-
|
|
72
|
-
-- Background cleanup job
|
|
73
|
-
DELETE FROM messages
|
|
74
|
-
WHERE expires_at IS NOT NULL AND expires_at < NOW() AND read_at IS NULL;
|
|
75
|
-
|
|
76
|
-
-- Fetch only non-expired messages
|
|
77
|
-
SELECT * FROM messages
|
|
78
|
-
WHERE recipient = $1 AND read_at IS NULL
|
|
79
|
-
AND (expires_at IS NULL OR expires_at > NOW())
|
|
80
|
-
ORDER BY priority DESC, received_at DESC;
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 4. Pub/Sub Topics
|
|
84
|
-
|
|
85
|
-
```sql
|
|
86
|
-
ALTER TABLE messages ADD COLUMN topic VARCHAR(255);
|
|
87
|
-
CREATE INDEX idx_messages_topic ON messages(topic, received_at DESC);
|
|
88
|
-
|
|
89
|
-
-- Subscribe to topic pattern
|
|
90
|
-
SELECT * FROM messages
|
|
91
|
-
WHERE topic LIKE 'builds.%' AND received_at > $1
|
|
92
|
-
ORDER BY received_at ASC;
|
|
93
|
-
|
|
94
|
-
-- Wildcard subscriptions
|
|
95
|
-
WHERE topic LIKE 'agent.%.error' -- All agent errors
|
|
96
|
-
WHERE topic LIKE 'bead.%.status' -- All bead status updates
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 5. Saga Orchestration Pattern
|
|
100
|
-
|
|
101
|
-
```sql
|
|
102
|
-
CREATE TABLE saga_instances (
|
|
103
|
-
id SERIAL PRIMARY KEY,
|
|
104
|
-
saga_id VARCHAR(255) UNIQUE NOT NULL, -- e.g., "epic-bd-123"
|
|
105
|
-
coordinator VARCHAR(255) NOT NULL,
|
|
106
|
-
status VARCHAR(50) NOT NULL, -- pending, in_progress, completed, failed, compensating
|
|
107
|
-
data JSONB NOT NULL,
|
|
108
|
-
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
109
|
-
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
CREATE TABLE saga_steps (
|
|
113
|
-
id SERIAL PRIMARY KEY,
|
|
114
|
-
saga_id VARCHAR(255) REFERENCES saga_instances(saga_id),
|
|
115
|
-
step_name VARCHAR(255) NOT NULL,
|
|
116
|
-
agent VARCHAR(255) NOT NULL,
|
|
117
|
-
status VARCHAR(50) NOT NULL, -- pending, completed, failed, compensated
|
|
118
|
-
compensation_for INTEGER REFERENCES saga_steps(id), -- Undo for this step
|
|
119
|
-
data JSONB,
|
|
120
|
-
completed_at TIMESTAMP
|
|
121
|
-
);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Saga workflow:
|
|
125
|
-
|
|
126
|
-
1. Coordinator creates saga_instance
|
|
127
|
-
2. Sends step messages to worker agents
|
|
128
|
-
3. Workers complete steps, update saga_steps
|
|
129
|
-
4. On failure, coordinator sends compensation messages
|
|
130
|
-
5. Workers execute compensation (undo) logic
|
|
131
|
-
|
|
132
|
-
## Consequences
|
|
133
|
-
|
|
134
|
-
### Easier
|
|
135
|
-
|
|
136
|
-
- **Priority handling** - Critical messages processed first
|
|
137
|
-
- **Resilience** - Failed messages retry or move to DLQ
|
|
138
|
-
- **TTL enforcement** - Stale messages auto-expire
|
|
139
|
-
- **Event broadcasting** - Pub/sub topics for loosely coupled agents
|
|
140
|
-
- **Long-running workflows** - Saga pattern for multi-agent coordination
|
|
141
|
-
|
|
142
|
-
### More Difficult
|
|
143
|
-
|
|
144
|
-
- **Schema complexity** - More tables, indexes, constraints
|
|
145
|
-
- **Background jobs** - TTL cleanup requires cron/scheduler
|
|
146
|
-
- **Testing** - More failure modes to test (retries, DLQ, compensations)
|
|
147
|
-
- **Debugging** - Saga state tracking adds visibility requirements
|
|
148
|
-
|
|
149
|
-
## Implementation Notes
|
|
150
|
-
|
|
151
|
-
### Phase 1: Priority Queues
|
|
152
|
-
|
|
153
|
-
- Add priority column with default=1 (existing messages unaffected)
|
|
154
|
-
- Update getInbox() to ORDER BY priority DESC
|
|
155
|
-
- Add priority parameter to sendMessage()
|
|
156
|
-
|
|
157
|
-
### Phase 2: DLQ + Retries
|
|
158
|
-
|
|
159
|
-
- Create failed_messages table
|
|
160
|
-
- Add retry logic to message processing
|
|
161
|
-
- Exponential backoff: 1min, 5min, 30min, DLQ
|
|
162
|
-
|
|
163
|
-
### Phase 3: TTL
|
|
164
|
-
|
|
165
|
-
- Add expires_at column
|
|
166
|
-
- Background cleanup job (run every 5 minutes)
|
|
167
|
-
- Filter expired messages in queries
|
|
168
|
-
|
|
169
|
-
### Phase 4: Pub/Sub
|
|
170
|
-
|
|
171
|
-
- Add topic column
|
|
172
|
-
- Update sendMessage() to accept topic
|
|
173
|
-
- Add subscribeToTopic() using live queries
|
|
174
|
-
|
|
175
|
-
### Phase 5: Sagas (Future)
|
|
176
|
-
|
|
177
|
-
- Create saga tables
|
|
178
|
-
- Add saga coordinator logic
|
|
179
|
-
- Implement compensation pattern
|
|
180
|
-
|
|
181
|
-
### Success Criteria
|
|
182
|
-
|
|
183
|
-
- [ ] Priority messages processed before normal
|
|
184
|
-
- [ ] Failed messages retry 3x before DLQ
|
|
185
|
-
- [ ] Expired messages cleaned up within 5 minutes
|
|
186
|
-
- [ ] Topic subscriptions work with wildcards
|
|
187
|
-
- [ ] Saga pattern demonstrated with 3+ step workflow
|