silo-agent 1.0.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/README.md +330 -0
- package/bin/silo.js +55 -0
- package/build/demo.d.ts +13 -0
- package/build/demo.js +91 -0
- package/build/demo.js.map +1 -0
- package/build/doctor.d.ts +13 -0
- package/build/doctor.js +108 -0
- package/build/doctor.js.map +1 -0
- package/build/index.d.ts +16 -0
- package/build/index.js +17 -0
- package/build/index.js.map +1 -0
- package/build/lib/agent-router.d.ts +110 -0
- package/build/lib/agent-router.js +197 -0
- package/build/lib/agent-router.js.map +1 -0
- package/build/lib/attestation.d.ts +64 -0
- package/build/lib/attestation.js +112 -0
- package/build/lib/attestation.js.map +1 -0
- package/build/lib/autonomy.d.ts +116 -0
- package/build/lib/autonomy.js +266 -0
- package/build/lib/autonomy.js.map +1 -0
- package/build/lib/crypto.d.ts +33 -0
- package/build/lib/crypto.js +63 -0
- package/build/lib/crypto.js.map +1 -0
- package/build/lib/heartbeat.d.ts +111 -0
- package/build/lib/heartbeat.js +256 -0
- package/build/lib/heartbeat.js.map +1 -0
- package/build/lib/memory-coordinator.d.ts +150 -0
- package/build/lib/memory-coordinator.js +249 -0
- package/build/lib/memory-coordinator.js.map +1 -0
- package/build/lib/memory-index.d.ts +83 -0
- package/build/lib/memory-index.js +209 -0
- package/build/lib/memory-index.js.map +1 -0
- package/build/lib/shared-memory.d.ts +165 -0
- package/build/lib/shared-memory.js +398 -0
- package/build/lib/shared-memory.js.map +1 -0
- package/build/lib/storage.d.ts +38 -0
- package/build/lib/storage.js +106 -0
- package/build/lib/storage.js.map +1 -0
- package/build/lib/vault.d.ts +71 -0
- package/build/lib/vault.js +119 -0
- package/build/lib/vault.js.map +1 -0
- package/build/mcp.d.ts +32 -0
- package/build/mcp.js +734 -0
- package/build/mcp.js.map +1 -0
- package/build/server.d.ts +1 -0
- package/build/server.js +370 -0
- package/build/server.js.map +1 -0
- package/build/tests/attestation.test.d.ts +1 -0
- package/build/tests/attestation.test.js +175 -0
- package/build/tests/attestation.test.js.map +1 -0
- package/build/tests/crypto.test.d.ts +1 -0
- package/build/tests/crypto.test.js +109 -0
- package/build/tests/crypto.test.js.map +1 -0
- package/build/verify-flow.d.ts +10 -0
- package/build/verify-flow.js +81 -0
- package/build/verify-flow.js.map +1 -0
- package/build/verify.d.ts +14 -0
- package/build/verify.js +77 -0
- package/build/verify.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SILO — Heartbeat Daemon
|
|
3
|
+
*
|
|
4
|
+
* A cron-like scheduler for autonomous agent operation.
|
|
5
|
+
*
|
|
6
|
+
* When a user enables "fully autonomous" mode, the heartbeat daemon:
|
|
7
|
+
* 1. Periodically checks agent health (storage connectivity, balance, session state)
|
|
8
|
+
* 2. Runs diagnostic tasks and pushes fixes automatically
|
|
9
|
+
* 3. Auto-commits attestation sessions at configurable intervals
|
|
10
|
+
* 4. Syncs shared memory channels with other agents
|
|
11
|
+
* 5. Stores heartbeat records on 0G for audit trail
|
|
12
|
+
*
|
|
13
|
+
* Inspired by clawdbots' heartbeat pattern — adapted for 0G-backed agent orchestration.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
autonomousMode: false,
|
|
18
|
+
baseIntervalMs: 30_000,
|
|
19
|
+
maxHistorySize: 500,
|
|
20
|
+
persistHeartbeats: true,
|
|
21
|
+
heartbeatChannel: "silo:heartbeat",
|
|
22
|
+
};
|
|
23
|
+
export class HeartbeatDaemon {
|
|
24
|
+
vault;
|
|
25
|
+
sharedMemory;
|
|
26
|
+
config;
|
|
27
|
+
tasks = new Map();
|
|
28
|
+
taskTimers = new Map();
|
|
29
|
+
running = false;
|
|
30
|
+
heartbeatId;
|
|
31
|
+
sequenceNumber = 0;
|
|
32
|
+
startedAt = null;
|
|
33
|
+
taskHistory = [];
|
|
34
|
+
mainTimer = null;
|
|
35
|
+
constructor(vault, sharedMemory, config = {}) {
|
|
36
|
+
this.vault = vault;
|
|
37
|
+
this.sharedMemory = sharedMemory;
|
|
38
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
39
|
+
this.heartbeatId = createHash("sha256")
|
|
40
|
+
.update(`heartbeat:${vault.address}:${Date.now()}:${Math.random()}`)
|
|
41
|
+
.digest("hex")
|
|
42
|
+
.slice(0, 16);
|
|
43
|
+
}
|
|
44
|
+
get id() { return this.heartbeatId; }
|
|
45
|
+
get isRunning() { return this.running; }
|
|
46
|
+
get isAutonomous() { return this.config.autonomousMode; }
|
|
47
|
+
/** Register a task with the heartbeat daemon */
|
|
48
|
+
registerTask(task) {
|
|
49
|
+
this.tasks.set(task.name, {
|
|
50
|
+
...task,
|
|
51
|
+
lastRunAt: null,
|
|
52
|
+
runCount: 0,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Enable or disable autonomous mode at runtime */
|
|
56
|
+
setAutonomousMode(enabled) {
|
|
57
|
+
this.config.autonomousMode = enabled;
|
|
58
|
+
}
|
|
59
|
+
/** Register the default set of tasks for autonomous operation */
|
|
60
|
+
registerDefaultTasks() {
|
|
61
|
+
this.registerTask({
|
|
62
|
+
name: "health_check",
|
|
63
|
+
type: "health_check",
|
|
64
|
+
intervalMs: 30_000,
|
|
65
|
+
enabled: true,
|
|
66
|
+
handler: async (ctx) => {
|
|
67
|
+
const balance = await ctx.vault.getBalance();
|
|
68
|
+
const balanceNum = parseFloat(balance);
|
|
69
|
+
return {
|
|
70
|
+
taskName: "health_check",
|
|
71
|
+
taskType: "health_check",
|
|
72
|
+
success: balanceNum > 0,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
duration: 0,
|
|
75
|
+
output: JSON.stringify({
|
|
76
|
+
balance,
|
|
77
|
+
sessionId: ctx.vault.sessionId,
|
|
78
|
+
agentAddress: ctx.vault.address,
|
|
79
|
+
lowBalance: balanceNum < 0.01,
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
this.registerTask({
|
|
85
|
+
name: "session_auto_commit",
|
|
86
|
+
type: "session_commit",
|
|
87
|
+
intervalMs: 300_000,
|
|
88
|
+
enabled: true,
|
|
89
|
+
handler: async (ctx) => {
|
|
90
|
+
if (!ctx.autonomousMode) {
|
|
91
|
+
return {
|
|
92
|
+
taskName: "session_auto_commit",
|
|
93
|
+
taskType: "session_commit",
|
|
94
|
+
success: true,
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
duration: 0,
|
|
97
|
+
output: "Skipped — not in autonomous mode",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const summary = ctx.vault.sessionSummary();
|
|
101
|
+
if (summary.includes("Events: 0")) {
|
|
102
|
+
return {
|
|
103
|
+
taskName: "session_auto_commit",
|
|
104
|
+
taskType: "session_commit",
|
|
105
|
+
success: true,
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
duration: 0,
|
|
108
|
+
output: "Skipped — no events to commit",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const result = await ctx.vault.commitSession();
|
|
112
|
+
return {
|
|
113
|
+
taskName: "session_auto_commit",
|
|
114
|
+
taskType: "session_commit",
|
|
115
|
+
success: true,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
duration: 0,
|
|
118
|
+
output: JSON.stringify(result),
|
|
119
|
+
rootHash: result.traceRootHash,
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
this.registerTask({
|
|
124
|
+
name: "memory_sync",
|
|
125
|
+
type: "memory_sync",
|
|
126
|
+
intervalMs: 15_000,
|
|
127
|
+
enabled: true,
|
|
128
|
+
handler: async (ctx) => {
|
|
129
|
+
const channels = ctx.sharedMemory.listChannels();
|
|
130
|
+
return {
|
|
131
|
+
taskName: "memory_sync",
|
|
132
|
+
taskType: "memory_sync",
|
|
133
|
+
success: true,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
duration: 0,
|
|
136
|
+
output: JSON.stringify({
|
|
137
|
+
channelCount: channels.length,
|
|
138
|
+
channels: channels.map(c => ({
|
|
139
|
+
name: c.name,
|
|
140
|
+
entries: c.entryCount,
|
|
141
|
+
subscribers: c.subscribers.length,
|
|
142
|
+
})),
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Start the heartbeat daemon. Begins running all registered tasks on their intervals.
|
|
150
|
+
*/
|
|
151
|
+
start() {
|
|
152
|
+
if (this.running)
|
|
153
|
+
return;
|
|
154
|
+
this.running = true;
|
|
155
|
+
this.startedAt = Date.now();
|
|
156
|
+
for (const [name, task] of this.tasks) {
|
|
157
|
+
if (!task.enabled)
|
|
158
|
+
continue;
|
|
159
|
+
const timer = setInterval(async () => {
|
|
160
|
+
await this.executeTask(task);
|
|
161
|
+
}, task.intervalMs);
|
|
162
|
+
this.taskTimers.set(name, timer);
|
|
163
|
+
}
|
|
164
|
+
this.mainTimer = setInterval(async () => {
|
|
165
|
+
await this.beat();
|
|
166
|
+
}, this.config.baseIntervalMs);
|
|
167
|
+
this.sharedMemory.createChannel(this.config.heartbeatChannel);
|
|
168
|
+
}
|
|
169
|
+
/** Stop the heartbeat daemon */
|
|
170
|
+
stop() {
|
|
171
|
+
if (!this.running)
|
|
172
|
+
return;
|
|
173
|
+
this.running = false;
|
|
174
|
+
for (const timer of this.taskTimers.values()) {
|
|
175
|
+
clearInterval(timer);
|
|
176
|
+
}
|
|
177
|
+
this.taskTimers.clear();
|
|
178
|
+
if (this.mainTimer) {
|
|
179
|
+
clearInterval(this.mainTimer);
|
|
180
|
+
this.mainTimer = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async executeTask(task) {
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
let result;
|
|
186
|
+
try {
|
|
187
|
+
const ctx = {
|
|
188
|
+
vault: this.vault,
|
|
189
|
+
sharedMemory: this.sharedMemory,
|
|
190
|
+
heartbeatId: this.heartbeatId,
|
|
191
|
+
autonomousMode: this.config.autonomousMode,
|
|
192
|
+
taskHistory: this.taskHistory,
|
|
193
|
+
};
|
|
194
|
+
result = await task.handler(ctx);
|
|
195
|
+
result.duration = Date.now() - startTime;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
result = {
|
|
199
|
+
taskName: task.name,
|
|
200
|
+
taskType: task.type,
|
|
201
|
+
success: false,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
duration: Date.now() - startTime,
|
|
204
|
+
error: error.message,
|
|
205
|
+
};
|
|
206
|
+
this.config.onError?.(error, task);
|
|
207
|
+
}
|
|
208
|
+
task.lastRunAt = Date.now();
|
|
209
|
+
task.runCount++;
|
|
210
|
+
this.taskHistory.push(result);
|
|
211
|
+
if (this.taskHistory.length > this.config.maxHistorySize) {
|
|
212
|
+
this.taskHistory.shift();
|
|
213
|
+
}
|
|
214
|
+
this.config.onTaskComplete?.(result);
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
/** The main heartbeat — runs on the base interval */
|
|
218
|
+
async beat() {
|
|
219
|
+
this.sequenceNumber++;
|
|
220
|
+
const record = {
|
|
221
|
+
heartbeatId: this.heartbeatId,
|
|
222
|
+
agentId: this.vault.address,
|
|
223
|
+
timestamp: Date.now(),
|
|
224
|
+
sequenceNumber: this.sequenceNumber,
|
|
225
|
+
tasksRun: this.taskHistory.slice(-10),
|
|
226
|
+
autonomousMode: this.config.autonomousMode,
|
|
227
|
+
uptime: this.startedAt ? Date.now() - this.startedAt : 0,
|
|
228
|
+
};
|
|
229
|
+
this.config.onBeat?.(record);
|
|
230
|
+
if (this.config.persistHeartbeats && this.config.autonomousMode) {
|
|
231
|
+
try {
|
|
232
|
+
await this.sharedMemory.write(this.config.heartbeatChannel, JSON.stringify(record), { type: "heartbeat", seq: this.sequenceNumber });
|
|
233
|
+
}
|
|
234
|
+
catch { }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Get the current heartbeat status */
|
|
238
|
+
status() {
|
|
239
|
+
return {
|
|
240
|
+
running: this.running,
|
|
241
|
+
autonomous: this.config.autonomousMode,
|
|
242
|
+
heartbeatId: this.heartbeatId,
|
|
243
|
+
uptime: this.startedAt ? Date.now() - this.startedAt : 0,
|
|
244
|
+
sequenceNumber: this.sequenceNumber,
|
|
245
|
+
registeredTasks: Array.from(this.tasks.values()).map(t => ({
|
|
246
|
+
name: t.name,
|
|
247
|
+
type: t.type,
|
|
248
|
+
enabled: t.enabled,
|
|
249
|
+
lastRunAt: t.lastRunAt,
|
|
250
|
+
runCount: t.runCount,
|
|
251
|
+
})),
|
|
252
|
+
recentResults: this.taskHistory.slice(-20),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../../src/lib/heartbeat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SILO — Memory Coordinator
|
|
3
|
+
*
|
|
4
|
+
* Solves the multi-agent coordination problem for shared memory:
|
|
5
|
+
*
|
|
6
|
+
* 1. Channel Locks — Prevents concurrent writes to the same channel.
|
|
7
|
+
* Agents acquire a lock before writing, release after. TTL-based
|
|
8
|
+
* expiry prevents deadlocks from crashed agents.
|
|
9
|
+
*
|
|
10
|
+
* 2. Optimistic Concurrency — Every write must include the expected
|
|
11
|
+
* prevRootHash. If another agent wrote in between, the write is
|
|
12
|
+
* rejected with a CONFLICT error. The agent must re-read and retry.
|
|
13
|
+
*
|
|
14
|
+
* 3. Fork Detection — If two agents somehow both write with the same
|
|
15
|
+
* prevRootHash (e.g., lock wasn't used), the coordinator detects
|
|
16
|
+
* the fork and flags it for resolution.
|
|
17
|
+
*
|
|
18
|
+
* 4. Version Vector — Each channel tracks a monotonic version counter.
|
|
19
|
+
* Agents include the version they read from, enabling the server
|
|
20
|
+
* to detect stale writes without comparing root hashes.
|
|
21
|
+
*
|
|
22
|
+
* The coordinator runs on the API server (single source of truth).
|
|
23
|
+
* Agents interact with it via REST or WebSocket before writing to 0G.
|
|
24
|
+
*
|
|
25
|
+
* Protocol:
|
|
26
|
+
* 1. Agent calls acquireLock(channel) → gets lockToken
|
|
27
|
+
* 2. Agent reads current head via getHead(channel) → {headRootHash, version}
|
|
28
|
+
* 3. Agent writes to 0G with prevRootHash = headRootHash
|
|
29
|
+
* 4. Agent calls commitWrite(channel, newRootHash, expectedVersion, lockToken)
|
|
30
|
+
* 5. Coordinator validates version match → updates head → releases lock
|
|
31
|
+
* 6. Coordinator broadcasts update to all subscribers
|
|
32
|
+
*/
|
|
33
|
+
export interface ChannelLock {
|
|
34
|
+
channel: string;
|
|
35
|
+
holderId: string;
|
|
36
|
+
token: string;
|
|
37
|
+
acquiredAt: number;
|
|
38
|
+
ttlMs: number;
|
|
39
|
+
expiresAt: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ChannelHead {
|
|
42
|
+
channel: string;
|
|
43
|
+
headRootHash: string | null;
|
|
44
|
+
version: number;
|
|
45
|
+
lastWriterId: string | null;
|
|
46
|
+
lastWriteAt: number | null;
|
|
47
|
+
}
|
|
48
|
+
export interface WriteReceipt {
|
|
49
|
+
channel: string;
|
|
50
|
+
rootHash: string;
|
|
51
|
+
prevRootHash: string | null;
|
|
52
|
+
version: number;
|
|
53
|
+
writerId: string;
|
|
54
|
+
timestamp: number;
|
|
55
|
+
}
|
|
56
|
+
export interface ForkRecord {
|
|
57
|
+
channel: string;
|
|
58
|
+
detectedAt: number;
|
|
59
|
+
branchA: {
|
|
60
|
+
rootHash: string;
|
|
61
|
+
writerId: string;
|
|
62
|
+
version: number;
|
|
63
|
+
};
|
|
64
|
+
branchB: {
|
|
65
|
+
rootHash: string;
|
|
66
|
+
writerId: string;
|
|
67
|
+
version: number;
|
|
68
|
+
};
|
|
69
|
+
commonAncestor: string | null;
|
|
70
|
+
resolved: boolean;
|
|
71
|
+
resolution?: "keep_a" | "keep_b" | "merge";
|
|
72
|
+
}
|
|
73
|
+
export type ConflictError = {
|
|
74
|
+
code: "CONFLICT";
|
|
75
|
+
message: string;
|
|
76
|
+
currentVersion: number;
|
|
77
|
+
currentHead: string | null;
|
|
78
|
+
yourVersion: number;
|
|
79
|
+
};
|
|
80
|
+
export type LockError = {
|
|
81
|
+
code: "LOCKED";
|
|
82
|
+
message: string;
|
|
83
|
+
holder: string;
|
|
84
|
+
expiresAt: number;
|
|
85
|
+
};
|
|
86
|
+
export interface CoordinatorConfig {
|
|
87
|
+
defaultLockTtlMs: number;
|
|
88
|
+
maxLockTtlMs: number;
|
|
89
|
+
enableForkDetection: boolean;
|
|
90
|
+
onForkDetected?: (fork: ForkRecord) => void;
|
|
91
|
+
onHeadUpdated?: (receipt: WriteReceipt) => void;
|
|
92
|
+
}
|
|
93
|
+
export declare class MemoryCoordinator {
|
|
94
|
+
private locks;
|
|
95
|
+
private heads;
|
|
96
|
+
private writeLog;
|
|
97
|
+
private forks;
|
|
98
|
+
private config;
|
|
99
|
+
private cleanupTimer;
|
|
100
|
+
constructor(config?: Partial<CoordinatorConfig>);
|
|
101
|
+
/** Initialize or get the tracked head for a channel */
|
|
102
|
+
ensureChannel(channel: string): ChannelHead;
|
|
103
|
+
/**
|
|
104
|
+
* Acquire a write lock on a channel.
|
|
105
|
+
* Returns a lock token that must be passed to commitWrite.
|
|
106
|
+
* Rejects if channel is already locked by another agent.
|
|
107
|
+
*/
|
|
108
|
+
acquireLock(channel: string, agentId: string, ttlMs?: number): ChannelLock | LockError;
|
|
109
|
+
/** Release a lock (requires the token or the holder's agentId) */
|
|
110
|
+
releaseLock(channel: string, tokenOrAgentId: string): boolean;
|
|
111
|
+
/** Check if a channel is locked and by whom */
|
|
112
|
+
getLock(channel: string): ChannelLock | null;
|
|
113
|
+
/** Get the current head of a channel (what agents read before writing) */
|
|
114
|
+
getHead(channel: string): ChannelHead;
|
|
115
|
+
/**
|
|
116
|
+
* Commit a write to a channel.
|
|
117
|
+
*
|
|
118
|
+
* This is the critical coordination point:
|
|
119
|
+
* - Validates the lock token
|
|
120
|
+
* - Checks the expectedVersion matches current version (optimistic concurrency)
|
|
121
|
+
* - Detects forks if the prevRootHash doesn't match current head
|
|
122
|
+
* - Updates the head and increments version
|
|
123
|
+
* - Releases the lock
|
|
124
|
+
* - Returns a receipt
|
|
125
|
+
*/
|
|
126
|
+
commitWrite(channel: string, newRootHash: string, prevRootHash: string | null, expectedVersion: number, writerId: string, lockToken?: string): WriteReceipt | ConflictError;
|
|
127
|
+
/**
|
|
128
|
+
* Coordinated write — acquire lock, check version, commit, release.
|
|
129
|
+
* This is the high-level "safe write" that agents should use.
|
|
130
|
+
*/
|
|
131
|
+
beginWrite(channel: string, agentId: string): {
|
|
132
|
+
head: ChannelHead;
|
|
133
|
+
lock: ChannelLock;
|
|
134
|
+
} | LockError;
|
|
135
|
+
/** Get the write history for a channel */
|
|
136
|
+
getWriteLog(channel: string, limit?: number): WriteReceipt[];
|
|
137
|
+
/** Get all detected forks */
|
|
138
|
+
getForks(channel?: string): ForkRecord[];
|
|
139
|
+
/** Resolve a fork (mark it as handled) */
|
|
140
|
+
resolveFork(channel: string, resolution: "keep_a" | "keep_b" | "merge", resolvedHead?: string): boolean;
|
|
141
|
+
/** List all channels with their coordination state */
|
|
142
|
+
listChannels(): {
|
|
143
|
+
head: ChannelHead;
|
|
144
|
+
locked: boolean;
|
|
145
|
+
lockHolder?: string;
|
|
146
|
+
}[];
|
|
147
|
+
private cleanExpiredLocks;
|
|
148
|
+
/** Shutdown the coordinator */
|
|
149
|
+
destroy(): void;
|
|
150
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SILO — Memory Coordinator
|
|
3
|
+
*
|
|
4
|
+
* Solves the multi-agent coordination problem for shared memory:
|
|
5
|
+
*
|
|
6
|
+
* 1. Channel Locks — Prevents concurrent writes to the same channel.
|
|
7
|
+
* Agents acquire a lock before writing, release after. TTL-based
|
|
8
|
+
* expiry prevents deadlocks from crashed agents.
|
|
9
|
+
*
|
|
10
|
+
* 2. Optimistic Concurrency — Every write must include the expected
|
|
11
|
+
* prevRootHash. If another agent wrote in between, the write is
|
|
12
|
+
* rejected with a CONFLICT error. The agent must re-read and retry.
|
|
13
|
+
*
|
|
14
|
+
* 3. Fork Detection — If two agents somehow both write with the same
|
|
15
|
+
* prevRootHash (e.g., lock wasn't used), the coordinator detects
|
|
16
|
+
* the fork and flags it for resolution.
|
|
17
|
+
*
|
|
18
|
+
* 4. Version Vector — Each channel tracks a monotonic version counter.
|
|
19
|
+
* Agents include the version they read from, enabling the server
|
|
20
|
+
* to detect stale writes without comparing root hashes.
|
|
21
|
+
*
|
|
22
|
+
* The coordinator runs on the API server (single source of truth).
|
|
23
|
+
* Agents interact with it via REST or WebSocket before writing to 0G.
|
|
24
|
+
*
|
|
25
|
+
* Protocol:
|
|
26
|
+
* 1. Agent calls acquireLock(channel) → gets lockToken
|
|
27
|
+
* 2. Agent reads current head via getHead(channel) → {headRootHash, version}
|
|
28
|
+
* 3. Agent writes to 0G with prevRootHash = headRootHash
|
|
29
|
+
* 4. Agent calls commitWrite(channel, newRootHash, expectedVersion, lockToken)
|
|
30
|
+
* 5. Coordinator validates version match → updates head → releases lock
|
|
31
|
+
* 6. Coordinator broadcasts update to all subscribers
|
|
32
|
+
*/
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
const DEFAULT_CONFIG = {
|
|
35
|
+
defaultLockTtlMs: 30_000,
|
|
36
|
+
maxLockTtlMs: 120_000,
|
|
37
|
+
enableForkDetection: true,
|
|
38
|
+
};
|
|
39
|
+
export class MemoryCoordinator {
|
|
40
|
+
locks = new Map();
|
|
41
|
+
heads = new Map();
|
|
42
|
+
writeLog = new Map();
|
|
43
|
+
forks = [];
|
|
44
|
+
config;
|
|
45
|
+
cleanupTimer = null;
|
|
46
|
+
constructor(config = {}) {
|
|
47
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
48
|
+
this.cleanupTimer = setInterval(() => this.cleanExpiredLocks(), 5_000);
|
|
49
|
+
}
|
|
50
|
+
/** Initialize or get the tracked head for a channel */
|
|
51
|
+
ensureChannel(channel) {
|
|
52
|
+
if (!this.heads.has(channel)) {
|
|
53
|
+
this.heads.set(channel, {
|
|
54
|
+
channel,
|
|
55
|
+
headRootHash: null,
|
|
56
|
+
version: 0,
|
|
57
|
+
lastWriterId: null,
|
|
58
|
+
lastWriteAt: null,
|
|
59
|
+
});
|
|
60
|
+
this.writeLog.set(channel, []);
|
|
61
|
+
}
|
|
62
|
+
return this.heads.get(channel);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Acquire a write lock on a channel.
|
|
66
|
+
* Returns a lock token that must be passed to commitWrite.
|
|
67
|
+
* Rejects if channel is already locked by another agent.
|
|
68
|
+
*/
|
|
69
|
+
acquireLock(channel, agentId, ttlMs) {
|
|
70
|
+
this.cleanExpiredLocks();
|
|
71
|
+
this.ensureChannel(channel);
|
|
72
|
+
const existing = this.locks.get(channel);
|
|
73
|
+
if (existing && existing.holderId !== agentId) {
|
|
74
|
+
return {
|
|
75
|
+
code: "LOCKED",
|
|
76
|
+
message: `Channel "${channel}" is locked by ${existing.holderId.slice(0, 8)}... (expires in ${Math.round((existing.expiresAt - Date.now()) / 1000)}s)`,
|
|
77
|
+
holder: existing.holderId,
|
|
78
|
+
expiresAt: existing.expiresAt,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (existing && existing.holderId === agentId) {
|
|
82
|
+
existing.expiresAt = Date.now() + (ttlMs ?? this.config.defaultLockTtlMs);
|
|
83
|
+
return existing;
|
|
84
|
+
}
|
|
85
|
+
const effectiveTtl = Math.min(ttlMs ?? this.config.defaultLockTtlMs, this.config.maxLockTtlMs);
|
|
86
|
+
const lock = {
|
|
87
|
+
channel,
|
|
88
|
+
holderId: agentId,
|
|
89
|
+
token: createHash("sha256")
|
|
90
|
+
.update(`lock:${channel}:${agentId}:${Date.now()}:${Math.random()}`)
|
|
91
|
+
.digest("hex")
|
|
92
|
+
.slice(0, 32),
|
|
93
|
+
acquiredAt: Date.now(),
|
|
94
|
+
ttlMs: effectiveTtl,
|
|
95
|
+
expiresAt: Date.now() + effectiveTtl,
|
|
96
|
+
};
|
|
97
|
+
this.locks.set(channel, lock);
|
|
98
|
+
return lock;
|
|
99
|
+
}
|
|
100
|
+
/** Release a lock (requires the token or the holder's agentId) */
|
|
101
|
+
releaseLock(channel, tokenOrAgentId) {
|
|
102
|
+
const lock = this.locks.get(channel);
|
|
103
|
+
if (!lock)
|
|
104
|
+
return true;
|
|
105
|
+
if (lock.token === tokenOrAgentId || lock.holderId === tokenOrAgentId) {
|
|
106
|
+
this.locks.delete(channel);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
/** Check if a channel is locked and by whom */
|
|
112
|
+
getLock(channel) {
|
|
113
|
+
this.cleanExpiredLocks();
|
|
114
|
+
return this.locks.get(channel) ?? null;
|
|
115
|
+
}
|
|
116
|
+
/** Get the current head of a channel (what agents read before writing) */
|
|
117
|
+
getHead(channel) {
|
|
118
|
+
return this.ensureChannel(channel);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Commit a write to a channel.
|
|
122
|
+
*
|
|
123
|
+
* This is the critical coordination point:
|
|
124
|
+
* - Validates the lock token
|
|
125
|
+
* - Checks the expectedVersion matches current version (optimistic concurrency)
|
|
126
|
+
* - Detects forks if the prevRootHash doesn't match current head
|
|
127
|
+
* - Updates the head and increments version
|
|
128
|
+
* - Releases the lock
|
|
129
|
+
* - Returns a receipt
|
|
130
|
+
*/
|
|
131
|
+
commitWrite(channel, newRootHash, prevRootHash, expectedVersion, writerId, lockToken) {
|
|
132
|
+
const head = this.ensureChannel(channel);
|
|
133
|
+
if (lockToken) {
|
|
134
|
+
const lock = this.locks.get(channel);
|
|
135
|
+
if (lock && lock.token !== lockToken && lock.holderId !== writerId) {
|
|
136
|
+
return {
|
|
137
|
+
code: "CONFLICT",
|
|
138
|
+
message: `Invalid lock token for channel "${channel}"`,
|
|
139
|
+
currentVersion: head.version,
|
|
140
|
+
currentHead: head.headRootHash,
|
|
141
|
+
yourVersion: expectedVersion,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (expectedVersion !== head.version) {
|
|
146
|
+
if (this.config.enableForkDetection && prevRootHash !== head.headRootHash) {
|
|
147
|
+
const fork = {
|
|
148
|
+
channel,
|
|
149
|
+
detectedAt: Date.now(),
|
|
150
|
+
branchA: { rootHash: head.headRootHash ?? "null", writerId: head.lastWriterId ?? "unknown", version: head.version },
|
|
151
|
+
branchB: { rootHash: newRootHash, writerId, version: expectedVersion },
|
|
152
|
+
commonAncestor: prevRootHash,
|
|
153
|
+
resolved: false,
|
|
154
|
+
};
|
|
155
|
+
this.forks.push(fork);
|
|
156
|
+
this.config.onForkDetected?.(fork);
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
code: "CONFLICT",
|
|
160
|
+
message: `Version mismatch on "${channel}": expected ${expectedVersion}, current is ${head.version}. Re-read and retry.`,
|
|
161
|
+
currentVersion: head.version,
|
|
162
|
+
currentHead: head.headRootHash,
|
|
163
|
+
yourVersion: expectedVersion,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
head.headRootHash = newRootHash;
|
|
167
|
+
head.version++;
|
|
168
|
+
head.lastWriterId = writerId;
|
|
169
|
+
head.lastWriteAt = Date.now();
|
|
170
|
+
const receipt = {
|
|
171
|
+
channel,
|
|
172
|
+
rootHash: newRootHash,
|
|
173
|
+
prevRootHash,
|
|
174
|
+
version: head.version,
|
|
175
|
+
writerId,
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
};
|
|
178
|
+
const log = this.writeLog.get(channel);
|
|
179
|
+
log.push(receipt);
|
|
180
|
+
if (log.length > 200)
|
|
181
|
+
log.shift();
|
|
182
|
+
this.releaseLock(channel, writerId);
|
|
183
|
+
this.config.onHeadUpdated?.(receipt);
|
|
184
|
+
return receipt;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Coordinated write — acquire lock, check version, commit, release.
|
|
188
|
+
* This is the high-level "safe write" that agents should use.
|
|
189
|
+
*/
|
|
190
|
+
beginWrite(channel, agentId) {
|
|
191
|
+
const lockResult = this.acquireLock(channel, agentId);
|
|
192
|
+
if ("code" in lockResult)
|
|
193
|
+
return lockResult;
|
|
194
|
+
const head = this.getHead(channel);
|
|
195
|
+
return { head, lock: lockResult };
|
|
196
|
+
}
|
|
197
|
+
/** Get the write history for a channel */
|
|
198
|
+
getWriteLog(channel, limit = 50) {
|
|
199
|
+
const log = this.writeLog.get(channel) ?? [];
|
|
200
|
+
return log.slice(-limit);
|
|
201
|
+
}
|
|
202
|
+
/** Get all detected forks */
|
|
203
|
+
getForks(channel) {
|
|
204
|
+
if (channel)
|
|
205
|
+
return this.forks.filter(f => f.channel === channel);
|
|
206
|
+
return [...this.forks];
|
|
207
|
+
}
|
|
208
|
+
/** Resolve a fork (mark it as handled) */
|
|
209
|
+
resolveFork(channel, resolution, resolvedHead) {
|
|
210
|
+
const fork = this.forks.find(f => f.channel === channel && !f.resolved);
|
|
211
|
+
if (!fork)
|
|
212
|
+
return false;
|
|
213
|
+
fork.resolved = true;
|
|
214
|
+
fork.resolution = resolution;
|
|
215
|
+
if (resolvedHead) {
|
|
216
|
+
const head = this.ensureChannel(channel);
|
|
217
|
+
head.headRootHash = resolvedHead;
|
|
218
|
+
head.version++;
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
/** List all channels with their coordination state */
|
|
223
|
+
listChannels() {
|
|
224
|
+
return Array.from(this.heads.values()).map(head => {
|
|
225
|
+
const lock = this.locks.get(head.channel);
|
|
226
|
+
return {
|
|
227
|
+
head,
|
|
228
|
+
locked: !!lock,
|
|
229
|
+
lockHolder: lock?.holderId,
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
cleanExpiredLocks() {
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
for (const [channel, lock] of this.locks) {
|
|
236
|
+
if (lock.expiresAt <= now) {
|
|
237
|
+
this.locks.delete(channel);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Shutdown the coordinator */
|
|
242
|
+
destroy() {
|
|
243
|
+
if (this.cleanupTimer) {
|
|
244
|
+
clearInterval(this.cleanupTimer);
|
|
245
|
+
this.cleanupTimer = null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=memory-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-coordinator.js","sourceRoot":"","sources":["../../src/lib/memory-coordinator.ts"],"names":[],"mappings":"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"}
|