monomind 2.1.9 → 2.3.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/package.json +2 -2
- package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +24 -16
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +23 -7
- package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +6 -3
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/code-quality-reviewer-prompt.md +3 -3
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +19 -24
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/debug.md +49 -4
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/design.md +11 -10
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/final-reviewer-prompt.md +144 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/implementer-prompt.md +9 -4
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +20 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +40 -19
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +90 -41
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/plan.md +19 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/skill-builder.md +171 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/spec-reviewer-prompt.md +19 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +7 -5
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/taskdev.md +83 -15
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/tdd.md +30 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +6 -4
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +4 -1
- package/packages/@monomind/cli/dist/src/capabilities/manager.js +3 -1
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +26 -0
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +28 -46
- package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +18 -0
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +120 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +13 -4
- package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +34 -18
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/hooks.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/init-wizard.js +2 -2
- package/packages/@monomind/cli/dist/src/commands/init.js +54 -10
- package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
- package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
- package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
- package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/swarm.js +5 -4
- package/packages/@monomind/cli/dist/src/index.js +13 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
- package/packages/@monomind/cli/dist/src/init/settings-generator.js +8 -1
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +68 -24
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +16 -4
- package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
- package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
- package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
- package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
- package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
- package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
- package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
- package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
- package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
- package/packages/@monomind/cli/package.json +2 -2
|
@@ -454,8 +454,10 @@ class LocalReasoningBank {
|
|
|
454
454
|
// ReasoningBank via intelligence.ts's public API, so no separate
|
|
455
455
|
// models.json bridge is needed.
|
|
456
456
|
}
|
|
457
|
-
catch {
|
|
457
|
+
catch (e) {
|
|
458
458
|
// Ignore load errors, start fresh
|
|
459
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
460
|
+
console.error('[intelligence] failed to load patterns.json, starting fresh:', e);
|
|
459
461
|
}
|
|
460
462
|
}
|
|
461
463
|
/**
|
|
@@ -680,8 +682,10 @@ function loadPersistedStats() {
|
|
|
680
682
|
}
|
|
681
683
|
}
|
|
682
684
|
}
|
|
683
|
-
catch {
|
|
685
|
+
catch (e) {
|
|
684
686
|
// Ignore load errors, start fresh
|
|
687
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
688
|
+
console.error('[intelligence] failed to load stats.json, starting fresh:', e);
|
|
685
689
|
}
|
|
686
690
|
}
|
|
687
691
|
/**
|
|
@@ -693,8 +697,10 @@ function savePersistedStats() {
|
|
|
693
697
|
const path = getStatsPath();
|
|
694
698
|
writeFileSync(path, JSON.stringify(globalStats, null, 2), 'utf-8');
|
|
695
699
|
}
|
|
696
|
-
catch {
|
|
700
|
+
catch (e) {
|
|
697
701
|
// Ignore save errors
|
|
702
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
703
|
+
console.error('[intelligence] failed to save stats.json:', e);
|
|
698
704
|
}
|
|
699
705
|
}
|
|
700
706
|
// ============================================================================
|
|
@@ -762,7 +768,11 @@ async function _doInitializeIntelligence(config) {
|
|
|
762
768
|
}
|
|
763
769
|
}
|
|
764
770
|
}
|
|
765
|
-
catch {
|
|
771
|
+
catch (e) {
|
|
772
|
+
/* neural patterns file unreadable — skip */
|
|
773
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
774
|
+
console.error('[intelligence] failed to seed patterns.json into ReasoningBank:', e);
|
|
775
|
+
}
|
|
766
776
|
}
|
|
767
777
|
// Seed SONA routing patterns into the ReasoningBank so keyword-based
|
|
768
778
|
// routing knowledge from .swarm/sona-patterns.json participates in
|
|
@@ -890,7 +900,9 @@ export async function recordStep(step) {
|
|
|
890
900
|
globalStats.trajectoriesRecorded++;
|
|
891
901
|
return true;
|
|
892
902
|
}
|
|
893
|
-
catch {
|
|
903
|
+
catch (e) {
|
|
904
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
905
|
+
console.error('[intelligence] recordStep failed:', e);
|
|
894
906
|
return false;
|
|
895
907
|
}
|
|
896
908
|
}
|
|
@@ -923,7 +935,9 @@ export async function recordTrajectory(steps, verdict) {
|
|
|
923
935
|
savePersistedStats();
|
|
924
936
|
return true;
|
|
925
937
|
}
|
|
926
|
-
catch {
|
|
938
|
+
catch (e) {
|
|
939
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
940
|
+
console.error('[intelligence] recordTrajectory failed:', e);
|
|
927
941
|
return false;
|
|
928
942
|
}
|
|
929
943
|
}
|
|
@@ -980,7 +994,9 @@ export async function findSimilarPatterns(query, options) {
|
|
|
980
994
|
similarity: r.similarity ?? r.confidence ?? 0.5
|
|
981
995
|
}));
|
|
982
996
|
}
|
|
983
|
-
catch {
|
|
997
|
+
catch (e) {
|
|
998
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
999
|
+
console.error('[intelligence] findSimilarPatterns failed:', e);
|
|
984
1000
|
return [];
|
|
985
1001
|
}
|
|
986
1002
|
}
|
|
@@ -1030,7 +1046,9 @@ export async function endTrajectoryWithVerdict(verdict) {
|
|
|
1030
1046
|
savePersistedStats();
|
|
1031
1047
|
return result;
|
|
1032
1048
|
}
|
|
1033
|
-
catch {
|
|
1049
|
+
catch (e) {
|
|
1050
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
1051
|
+
console.error('[intelligence] endTrajectoryWithVerdict failed:', e);
|
|
1034
1052
|
return null;
|
|
1035
1053
|
}
|
|
1036
1054
|
}
|
|
@@ -1052,7 +1070,9 @@ export async function distillLearning() {
|
|
|
1052
1070
|
savePersistedStats();
|
|
1053
1071
|
return result;
|
|
1054
1072
|
}
|
|
1055
|
-
catch {
|
|
1073
|
+
catch (e) {
|
|
1074
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
1075
|
+
console.error('[intelligence] distillLearning failed:', e);
|
|
1056
1076
|
return null;
|
|
1057
1077
|
}
|
|
1058
1078
|
}
|
|
@@ -1184,7 +1204,9 @@ function loadSonaRoutingPatterns() {
|
|
|
1184
1204
|
}
|
|
1185
1205
|
return results;
|
|
1186
1206
|
}
|
|
1187
|
-
catch {
|
|
1207
|
+
catch (e) {
|
|
1208
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
1209
|
+
console.error('[intelligence] failed to load .swarm/sona-patterns.json:', e);
|
|
1188
1210
|
return [];
|
|
1189
1211
|
}
|
|
1190
1212
|
}
|
|
@@ -148,7 +148,10 @@ async function getBackend(dbPath) {
|
|
|
148
148
|
};
|
|
149
149
|
_embedder = embeddingGenerator;
|
|
150
150
|
}
|
|
151
|
-
catch {
|
|
151
|
+
catch (e) {
|
|
152
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
153
|
+
console.error('[memory-bridge] embedding model failed to load — store and search without vectors:', e);
|
|
154
|
+
}
|
|
152
155
|
const backend = new LanceDBBackend({
|
|
153
156
|
// Always route through getDbPath's traversal guard — dbPath here can
|
|
154
157
|
// originate from a caller/tool-supplied value, and getDbPath(undefined)
|
|
@@ -212,7 +215,10 @@ export async function bridgeStoreEntry(options) {
|
|
|
212
215
|
embedding = await _embedder(value);
|
|
213
216
|
embeddingInfo = { dimensions: embedding.length, model: BRIDGE_EMBEDDING_MODEL };
|
|
214
217
|
}
|
|
215
|
-
catch {
|
|
218
|
+
catch (e) {
|
|
219
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
220
|
+
console.error('[memory-bridge] embedding generation failed — storing entry without embedding:', e);
|
|
221
|
+
}
|
|
216
222
|
}
|
|
217
223
|
const mod = await import('@monoes/memory');
|
|
218
224
|
const entry = mod.createDefaultEntry({
|
|
@@ -233,7 +239,10 @@ export async function bridgeStoreEntry(options) {
|
|
|
233
239
|
if (existing)
|
|
234
240
|
await backend.delete(existing.id);
|
|
235
241
|
}
|
|
236
|
-
catch {
|
|
242
|
+
catch (e) {
|
|
243
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
244
|
+
console.error('[memory-bridge] upsert failed to delete existing entry — may create a duplicate:', e);
|
|
245
|
+
}
|
|
237
246
|
}
|
|
238
247
|
// Dedup gate: skip if a near-duplicate already exists
|
|
239
248
|
const automemCfg = getAutomemConfig();
|
|
@@ -644,7 +653,10 @@ export async function bridgeSessionEnd(options) {
|
|
|
644
653
|
try {
|
|
645
654
|
data = JSON.parse(existing.content);
|
|
646
655
|
}
|
|
647
|
-
catch {
|
|
656
|
+
catch (e) {
|
|
657
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
658
|
+
console.error('[memory-bridge] session content failed to parse — ending session with empty prior state:', e);
|
|
659
|
+
}
|
|
648
660
|
await backend.update(existing.id, {
|
|
649
661
|
content: JSON.stringify({
|
|
650
662
|
...data,
|
|
@@ -238,8 +238,32 @@ export async function storeEntry(options) {
|
|
|
238
238
|
}
|
|
239
239
|
const fileBuffer = fs.readFileSync(dbPath);
|
|
240
240
|
const db = new SQL.Database(fileBuffer);
|
|
241
|
-
|
|
241
|
+
// memory_entries has no UNIQUE constraint on (key, namespace) — only
|
|
242
|
+
// `id TEXT PRIMARY KEY`. `INSERT OR REPLACE` only replaces a row when
|
|
243
|
+
// its PRIMARY KEY collides, so generating a fresh id on every call (as
|
|
244
|
+
// this used to do unconditionally) meant upsert never actually matched
|
|
245
|
+
// the existing row for a given key+namespace — it silently inserted a
|
|
246
|
+
// duplicate instead of replacing it. When upserting, look up the
|
|
247
|
+
// existing row's real id first and reuse it so the replace actually
|
|
248
|
+
// collides; only mint a new id when no existing row is found.
|
|
249
|
+
let id = `entry_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
250
|
+
let existingCreatedAt = null;
|
|
251
|
+
if (upsert) {
|
|
252
|
+
// Only match active rows — a soft-deleted row's key+namespace should
|
|
253
|
+
// not be resurrected by an unrelated upsert; that would both restore
|
|
254
|
+
// history a delete intentionally cleared and mask the deletion.
|
|
255
|
+
const existingIdResult = db.exec("SELECT id, created_at FROM memory_entries WHERE key = ? AND namespace = ? AND status = 'active' LIMIT 1", [key, namespace]);
|
|
256
|
+
const existingRow = existingIdResult[0]?.values?.[0];
|
|
257
|
+
const existingId = existingRow?.[0];
|
|
258
|
+
if (typeof existingId === 'string') {
|
|
259
|
+
id = existingId;
|
|
260
|
+
const createdAt = existingRow?.[1];
|
|
261
|
+
if (typeof createdAt === 'number')
|
|
262
|
+
existingCreatedAt = createdAt;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
242
265
|
const now = Date.now();
|
|
266
|
+
const createdAt = existingCreatedAt ?? now;
|
|
243
267
|
let embeddingJson = null;
|
|
244
268
|
let embeddingDimensions = null;
|
|
245
269
|
let embeddingModel = null;
|
|
@@ -270,7 +294,7 @@ export async function storeEntry(options) {
|
|
|
270
294
|
embeddingModel,
|
|
271
295
|
tags.length > 0 ? JSON.stringify(tags) : null,
|
|
272
296
|
'{}',
|
|
273
|
-
|
|
297
|
+
createdAt,
|
|
274
298
|
now,
|
|
275
299
|
ttl ? now + (ttl * 1000) : null
|
|
276
300
|
]);
|
|
@@ -110,8 +110,9 @@ async function activateControllerRegistry(dbPath, verbose) {
|
|
|
110
110
|
console.log(`ControllerRegistry: ${activated.length} controllers activated`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
113
|
+
catch (e) {
|
|
114
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
115
|
+
console.error('[activateControllers] ControllerRegistry activation failed:', e);
|
|
115
116
|
}
|
|
116
117
|
return { activated, failed, initTimeMs: performance.now() - startTime };
|
|
117
118
|
}
|
|
@@ -57,6 +57,8 @@ export async function ensureSchemaColumns(dbPath) {
|
|
|
57
57
|
}
|
|
58
58
|
catch (e) {
|
|
59
59
|
// Column might already exist or other error - continue
|
|
60
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
61
|
+
console.error(`[ensureSchemaColumns] failed to add column '${col.name}':`, e);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -41,7 +41,10 @@ export async function recordCommand(baseDir, cmd) {
|
|
|
41
41
|
await fs.writeFile(path, lines.slice(-MAX_COMMAND_RECORDS).join('\n') + '\n', 'utf8');
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
catch {
|
|
44
|
+
catch (e) {
|
|
45
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
46
|
+
console.error('[recordCommand] failed to record command outcome:', e);
|
|
47
|
+
}
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
50
|
* Derive a measured success signal from recent command outcomes.
|
|
@@ -36,7 +36,10 @@ export class OrgBus {
|
|
|
36
36
|
this.pending = this.pending.then(async () => {
|
|
37
37
|
await mkdir(this.dir, { recursive: true });
|
|
38
38
|
await appendFile(this.file, JSON.stringify(e) + '\n', 'utf8');
|
|
39
|
-
}).catch(() => {
|
|
39
|
+
}).catch((err) => {
|
|
40
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
41
|
+
console.error('[org-bus] event write failed:', err);
|
|
42
|
+
});
|
|
40
43
|
for (const fn of this.listeners) {
|
|
41
44
|
try {
|
|
42
45
|
fn(e);
|
|
@@ -10,6 +10,7 @@ import { attachForwarder } from './forwarder.js';
|
|
|
10
10
|
import { BrokerLease, lookupOrg } from './broker.js';
|
|
11
11
|
import { queueMessage, drainInbox } from './inbox.js';
|
|
12
12
|
import { OrgDefSchema, ORG_DIR } from './types.js';
|
|
13
|
+
import { summarizeRun, readRunEvents, readHistory, historyFile } from './reporting.js';
|
|
13
14
|
export class OrgDaemon {
|
|
14
15
|
root;
|
|
15
16
|
opts;
|
|
@@ -66,33 +67,74 @@ export class OrgDaemon {
|
|
|
66
67
|
const mailbox = new Mailbox();
|
|
67
68
|
const policy = new PolicyEngine(role.id, { maxTokens: perRoleBudget, ...(role.policy ?? {}) }, bus, cwd);
|
|
68
69
|
const runtime = { mailbox, policy, status: 'running', done: Promise.resolve() };
|
|
69
|
-
|
|
70
|
+
const sessionOpts = {
|
|
70
71
|
org: name, role, bus, policy, mailbox, cwd, def,
|
|
71
72
|
maxTurns: def.run_config.max_turns_per_message,
|
|
72
73
|
deliver: (from, to, subject, body) => this.deliver(name, from, to, subject, body),
|
|
73
|
-
askHuman: (
|
|
74
|
+
askHuman: (r, question) => this.askHuman(name, r, question),
|
|
75
|
+
onComplete: (r, outcome, summary) => {
|
|
76
|
+
bus.emit({ type: 'status', from: r, reason: 'org-complete', msg: `run outcome: ${outcome}`, data: { outcome, summary } });
|
|
77
|
+
},
|
|
74
78
|
queryFn: this.opts.queryFn,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
};
|
|
80
|
+
// Supervised session: transient crashes (provider blips, network) restart
|
|
81
|
+
// with backoff; a crash with the mailbox already closed, or one that
|
|
82
|
+
// exhausts the retry budget, is terminal. runAgentSession already emits a
|
|
83
|
+
// 'status' event for the raw error; the terminal 'audit' event is for
|
|
84
|
+
// dashboards/alerts that filter on actionable failures (not routine
|
|
85
|
+
// status chatter) so a dead agent surfaces instead of a run that
|
|
86
|
+
// silently never progresses.
|
|
87
|
+
const BACKOFFS_MS = [1000, 5000, 15000];
|
|
88
|
+
runtime.done = (async () => {
|
|
89
|
+
for (let attempt = 0;; attempt++) {
|
|
90
|
+
try {
|
|
91
|
+
await runAgentSession(sessionOpts);
|
|
92
|
+
runtime.status = 'ended';
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
+
const crash = () => {
|
|
98
|
+
runtime.status = 'crashed';
|
|
99
|
+
runtime.error = message;
|
|
100
|
+
bus.emit({
|
|
101
|
+
type: 'audit', from: role.id,
|
|
102
|
+
msg: `agent "${role.id}" crashed: ${message}`,
|
|
103
|
+
reason: 'agent-session-crash',
|
|
104
|
+
data: { agentId: role.id, error: message, restarts: attempt },
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
if (mailbox.isClosed || attempt >= BACKOFFS_MS.length) {
|
|
108
|
+
crash();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
bus.emit({
|
|
112
|
+
type: 'status', from: role.id, reason: 'agent-restart',
|
|
113
|
+
msg: `agent "${role.id}" crashed (${message}) — restarting in ${BACKOFFS_MS[attempt]}ms (attempt ${attempt + 1}/${BACKOFFS_MS.length})`,
|
|
114
|
+
});
|
|
115
|
+
await new Promise(r => { const t = setTimeout(r, BACKOFFS_MS[attempt]); t.unref?.(); });
|
|
116
|
+
if (mailbox.isClosed) {
|
|
117
|
+
crash();
|
|
118
|
+
return;
|
|
119
|
+
} // org stopped during backoff — never recovered
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
91
123
|
running.agents.set(role.id, runtime);
|
|
92
124
|
}
|
|
93
125
|
const boss = def.roles.find(r => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
|
|
126
|
+
// Cross-run memory: brief the coordinator on the previous run so scheduled
|
|
127
|
+
// orgs accumulate instead of starting cold every interval.
|
|
128
|
+
const prev = readHistory(this.root, name).at(-1);
|
|
129
|
+
const prevBrief = prev
|
|
130
|
+
? `\n\nPrevious run (${prev.run}${prev.endedAt ? `, ${new Date(prev.endedAt).toISOString()}` : ''}): ` +
|
|
131
|
+
(prev.outcome
|
|
132
|
+
? `outcome "${prev.outcome.status}" — ${prev.outcome.summary}`
|
|
133
|
+
: `no recorded outcome (${prev.messages} messages, ${prev.assets.length} assets${prev.crashes.length ? `, ${prev.crashes.length} crashed agent(s)` : ''})`) +
|
|
134
|
+
`\nBuild on that work — do not redo what is already done.`
|
|
135
|
+
: '';
|
|
94
136
|
running.agents.get(boss.id).mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
|
|
95
|
-
`Coordinate your team via org_send.
|
|
137
|
+
`Coordinate your team via org_send. When the goal is achieved (or clearly can't be), record it with org_complete, then end your turn.${prevBrief}`);
|
|
96
138
|
bus.emit({ type: 'status', msg: `org started (${def.roles.length} agents)`, data: { goal: taskOverride ?? def.goal } });
|
|
97
139
|
this.persistState(name, 'running', run);
|
|
98
140
|
if (this.opts.crossProcess && this.opts.inboxUrl) {
|
|
@@ -336,6 +378,19 @@ export class OrgDaemon {
|
|
|
336
378
|
}
|
|
337
379
|
org.bus.emit({ type: 'status', msg: 'org stopped' });
|
|
338
380
|
await org.bus.flush();
|
|
381
|
+
// Append this run's summary to <org>/history.jsonl — read back from the
|
|
382
|
+
// flushed bus.jsonl (the full durable record) rather than the bounded
|
|
383
|
+
// in-memory buffer, so long runs summarize completely.
|
|
384
|
+
try {
|
|
385
|
+
const events = readRunEvents(this.root, name, org.run);
|
|
386
|
+
if (events.length) {
|
|
387
|
+
const { appendFileSync } = await import('node:fs');
|
|
388
|
+
appendFileSync(historyFile(this.root, name), JSON.stringify(summarizeRun(events)) + '\n', 'utf8');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
console.error(`org ${name}: could not write run history:`, err instanceof Error ? err.message : err);
|
|
393
|
+
}
|
|
339
394
|
// the "org stopped" event above triggers the forwarder's final org:complete /
|
|
340
395
|
// session:complete POST — without waiting for it here, the CLI process can exit
|
|
341
396
|
// (and kill the in-flight fetch) before that last event reaches the dashboard,
|
|
@@ -23,7 +23,9 @@ export function drainInbox(root, orgName) {
|
|
|
23
23
|
try {
|
|
24
24
|
renameSync(path, draining);
|
|
25
25
|
}
|
|
26
|
-
catch {
|
|
26
|
+
catch (e) {
|
|
27
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
28
|
+
console.error('[inbox] drainInbox rename failed:', e);
|
|
27
29
|
return [];
|
|
28
30
|
}
|
|
29
31
|
const raw = readFileSync(draining, 'utf8').trim();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BusEvent } from './types.js';
|
|
2
|
+
export interface RoleStats {
|
|
3
|
+
messagesSent: number;
|
|
4
|
+
toolsAllowed: number;
|
|
5
|
+
toolsDenied: number;
|
|
6
|
+
tokens: number;
|
|
7
|
+
costUsd: number;
|
|
8
|
+
crashed: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface RunSummary {
|
|
11
|
+
org: string;
|
|
12
|
+
run: string;
|
|
13
|
+
startedAt: number | null;
|
|
14
|
+
endedAt: number | null;
|
|
15
|
+
durationMs: number | null;
|
|
16
|
+
events: number;
|
|
17
|
+
messages: number;
|
|
18
|
+
xorgMessages: number;
|
|
19
|
+
assets: string[];
|
|
20
|
+
crashes: string[];
|
|
21
|
+
outcome: {
|
|
22
|
+
status: string;
|
|
23
|
+
summary: string;
|
|
24
|
+
by: string;
|
|
25
|
+
} | null;
|
|
26
|
+
roles: Record<string, RoleStats>;
|
|
27
|
+
totalTokens: number;
|
|
28
|
+
totalCostUsd: number;
|
|
29
|
+
}
|
|
30
|
+
/** Aggregate one run's bus events into a summary. */
|
|
31
|
+
export declare function summarizeRun(events: BusEvent[]): RunSummary;
|
|
32
|
+
/** run directories for an org, newest first (by name — run-YYYYMMDDHHMMSS-xxxx sorts naturally). */
|
|
33
|
+
export declare function listRunDirs(cwd: string, org: string): string[];
|
|
34
|
+
export declare function readRunEvents(cwd: string, org: string, run: string): BusEvent[];
|
|
35
|
+
/** history.jsonl — one RunSummary line per completed run, appended by the daemon at stopOrg. */
|
|
36
|
+
export declare function historyFile(cwd: string, org: string): string;
|
|
37
|
+
export declare function readHistory(cwd: string, org: string): RunSummary[];
|
|
38
|
+
/** One bus event as a compact human-readable log line. */
|
|
39
|
+
export declare function formatEvent(e: BusEvent): string;
|
|
40
|
+
//# sourceMappingURL=reporting.d.ts.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/orgrt/reporting.ts
|
|
2
|
+
// Read-side aggregation over an org run's bus.jsonl — powers `org report`,
|
|
3
|
+
// `org logs`, and the per-run summary line appended to <org>/history.jsonl.
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { ORG_DIR } from './types.js';
|
|
7
|
+
const roleStats = () => ({ messagesSent: 0, toolsAllowed: 0, toolsDenied: 0, tokens: 0, costUsd: 0, crashed: false });
|
|
8
|
+
/** Aggregate one run's bus events into a summary. */
|
|
9
|
+
export function summarizeRun(events) {
|
|
10
|
+
const s = {
|
|
11
|
+
org: events[0]?.org ?? '', run: events[0]?.run ?? '',
|
|
12
|
+
startedAt: events.length ? events[0].ts : null,
|
|
13
|
+
endedAt: events.length ? events[events.length - 1].ts : null,
|
|
14
|
+
durationMs: null, events: events.length,
|
|
15
|
+
messages: 0, xorgMessages: 0, assets: [], crashes: [], outcome: null,
|
|
16
|
+
roles: {}, totalTokens: 0, totalCostUsd: 0,
|
|
17
|
+
};
|
|
18
|
+
if (s.startedAt !== null && s.endedAt !== null)
|
|
19
|
+
s.durationMs = s.endedAt - s.startedAt;
|
|
20
|
+
const role = (id) => {
|
|
21
|
+
const key = id ?? '(system)';
|
|
22
|
+
return (s.roles[key] ??= roleStats());
|
|
23
|
+
};
|
|
24
|
+
for (const e of events) {
|
|
25
|
+
switch (e.type) {
|
|
26
|
+
case 'message':
|
|
27
|
+
s.messages++;
|
|
28
|
+
role(e.from).messagesSent++;
|
|
29
|
+
break;
|
|
30
|
+
case 'xorg':
|
|
31
|
+
s.xorgMessages++;
|
|
32
|
+
role(e.from?.includes(':') ? e.from.split(':')[1] : e.from).messagesSent++;
|
|
33
|
+
break;
|
|
34
|
+
case 'tool':
|
|
35
|
+
if (e.decision === 'deny')
|
|
36
|
+
role(e.from).toolsDenied++;
|
|
37
|
+
else
|
|
38
|
+
role(e.from).toolsAllowed++;
|
|
39
|
+
break;
|
|
40
|
+
case 'asset':
|
|
41
|
+
if (e.path && !s.assets.includes(e.path))
|
|
42
|
+
s.assets.push(e.path);
|
|
43
|
+
break;
|
|
44
|
+
case 'usage': {
|
|
45
|
+
const tokens = Number(e.data?.tokens ?? 0);
|
|
46
|
+
const cost = Number(e.data?.cost_usd ?? 0);
|
|
47
|
+
const r = role(e.from);
|
|
48
|
+
r.tokens += tokens;
|
|
49
|
+
s.totalTokens += tokens;
|
|
50
|
+
if (Number.isFinite(cost)) {
|
|
51
|
+
r.costUsd += cost;
|
|
52
|
+
s.totalCostUsd += cost;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'audit':
|
|
57
|
+
if (e.reason === 'agent-session-crash' && e.from) {
|
|
58
|
+
s.crashes.push(e.from);
|
|
59
|
+
role(e.from).crashed = true;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case 'status': {
|
|
63
|
+
const d = e.data;
|
|
64
|
+
if (e.reason === 'org-complete' && d?.outcome)
|
|
65
|
+
s.outcome = { status: d.outcome, summary: d.summary ?? '', by: e.from ?? '' };
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return s;
|
|
71
|
+
}
|
|
72
|
+
/** run directories for an org, newest first (by name — run-YYYYMMDDHHMMSS-xxxx sorts naturally). */
|
|
73
|
+
export function listRunDirs(cwd, org) {
|
|
74
|
+
const base = join(cwd, ORG_DIR, org);
|
|
75
|
+
if (!existsSync(base))
|
|
76
|
+
return [];
|
|
77
|
+
return readdirSync(base).filter(d => d.startsWith('run-')).sort().reverse();
|
|
78
|
+
}
|
|
79
|
+
export function readRunEvents(cwd, org, run) {
|
|
80
|
+
const f = join(cwd, ORG_DIR, org, run, 'bus.jsonl');
|
|
81
|
+
if (!existsSync(f))
|
|
82
|
+
return [];
|
|
83
|
+
return readFileSync(f, 'utf8').split('\n').filter(Boolean)
|
|
84
|
+
.map(l => { try {
|
|
85
|
+
return JSON.parse(l);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
} })
|
|
90
|
+
.filter((e) => e !== null);
|
|
91
|
+
}
|
|
92
|
+
/** history.jsonl — one RunSummary line per completed run, appended by the daemon at stopOrg. */
|
|
93
|
+
export function historyFile(cwd, org) {
|
|
94
|
+
return join(cwd, ORG_DIR, org, 'history.jsonl');
|
|
95
|
+
}
|
|
96
|
+
export function readHistory(cwd, org) {
|
|
97
|
+
const f = historyFile(cwd, org);
|
|
98
|
+
if (!existsSync(f))
|
|
99
|
+
return [];
|
|
100
|
+
return readFileSync(f, 'utf8').split('\n').filter(Boolean)
|
|
101
|
+
.map(l => { try {
|
|
102
|
+
return JSON.parse(l);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
} })
|
|
107
|
+
.filter((s) => s !== null);
|
|
108
|
+
}
|
|
109
|
+
/** One bus event as a compact human-readable log line. */
|
|
110
|
+
export function formatEvent(e) {
|
|
111
|
+
const t = new Date(e.ts).toISOString().slice(11, 19);
|
|
112
|
+
const from = e.from ?? '·';
|
|
113
|
+
switch (e.type) {
|
|
114
|
+
case 'message':
|
|
115
|
+
case 'xorg':
|
|
116
|
+
return `${t} ${e.type === 'xorg' ? '⇄' : '→'} ${from} → ${e.to}: [${e.subject ?? ''}] ${trim(e.msg)}`;
|
|
117
|
+
case 'chat': return `${t} 💬 ${from}: ${trim(e.msg)}`;
|
|
118
|
+
case 'tool': return `${t} 🔧 ${from} ${e.tool} ${e.decision === 'deny' ? `DENIED (${e.reason})` : 'ok'}`;
|
|
119
|
+
case 'asset': return `${t} 📄 ${from} wrote ${e.path}`;
|
|
120
|
+
case 'usage': return `${t} 🪙 ${from} +${e.data?.tokens ?? 0} tokens`;
|
|
121
|
+
case 'audit': return `${t} ⚠️ ${from} ${e.msg ?? e.reason ?? ''}`;
|
|
122
|
+
case 'question': return `${t} ❓ ${from}: ${trim(e.msg)}`;
|
|
123
|
+
default: return `${t} ▪ ${from} ${e.type}: ${trim(e.msg ?? '')}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const trim = (s, n = 120) => !s ? '' : s.length > n ? `${s.slice(0, n - 1)}…` : s.replace(/\n/g, ' ');
|
|
127
|
+
//# sourceMappingURL=reporting.js.map
|
|
@@ -13,6 +13,8 @@ export interface SessionOpts {
|
|
|
13
13
|
cwd: string;
|
|
14
14
|
deliver: DeliverFn;
|
|
15
15
|
askHuman?: (role: string, question: string) => Promise<string>;
|
|
16
|
+
/** Coordinator-only: records the run's outcome (daemon persists it to run history). */
|
|
17
|
+
onComplete?: (role: string, outcome: 'achieved' | 'partial' | 'failed', summary: string) => void;
|
|
16
18
|
def?: OrgDef;
|
|
17
19
|
maxTurns?: number;
|
|
18
20
|
queryFn?: typeof query;
|
|
@@ -4,17 +4,20 @@ import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk'
|
|
|
4
4
|
import { resolveProviderEnv } from './provider.js';
|
|
5
5
|
/** Role briefing given to each agent session (SDK systemPrompt option). */
|
|
6
6
|
export function buildRolePrompt(role, def, roster) {
|
|
7
|
+
const isCoordinator = role.reports_to == null;
|
|
7
8
|
return [
|
|
8
9
|
`You are agent "${role.id}" (${role.title || role.type}) in the org "${def.name}".`,
|
|
9
10
|
`Org goal: ${def.goal}`,
|
|
10
|
-
|
|
11
|
+
isCoordinator ? `You are the coordinator of this org.` : `You report to "${role.reports_to}".`,
|
|
11
12
|
role.responsibilities?.length ? `Your responsibilities:\n- ${role.responsibilities.join('\n- ')}` : '',
|
|
12
13
|
`## Communication protocol`,
|
|
13
14
|
`The ONLY way to communicate with other agents is the org_send tool.`,
|
|
14
15
|
`Roster: ${roster.join(', ')}. Address another org's agent as "<org-name>:<role-id>".`,
|
|
15
16
|
`If you need a human decision, call ask_human with your question, then end your turn — you'll receive the human's answer as a new message when it arrives. Do not call ask_human for anything you can resolve yourself.`,
|
|
16
17
|
`When you receive a message, act on it, then org_send your result to the requester.`,
|
|
17
|
-
|
|
18
|
+
isCoordinator
|
|
19
|
+
? `When the org's goal for this run is achieved (or clearly can't be), call org_complete exactly once with the outcome and a concise summary of what was done — it is recorded in the org's run history and briefed to the next run. Then end your turn.`
|
|
20
|
+
: `When your current work is complete and no reply is needed, end your turn without further tool calls.`,
|
|
18
21
|
].filter(Boolean).join('\n\n');
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
@@ -44,10 +47,15 @@ export async function runAgentSession(opts) {
|
|
|
44
47
|
async function runOneSession(opts) {
|
|
45
48
|
const { org, role, bus, policy, mailbox, cwd, deliver } = opts;
|
|
46
49
|
const queryFn = opts.queryFn ?? query;
|
|
50
|
+
const isCoordinator = role.reports_to == null;
|
|
47
51
|
const orgServer = createSdkMcpServer({
|
|
48
52
|
name: 'org',
|
|
49
53
|
version: '1.0.0',
|
|
50
54
|
tools: [
|
|
55
|
+
...(isCoordinator && opts.onComplete ? [tool('org_complete', 'Record the outcome of this run. Call exactly once, when the goal is achieved or clearly cannot be. The outcome and summary are persisted to the org run history and briefed to the next run.', { outcome: z.enum(['achieved', 'partial', 'failed']), summary: z.string() }, async (args) => {
|
|
56
|
+
opts.onComplete(role.id, args.outcome, args.summary);
|
|
57
|
+
return { content: [{ type: 'text', text: `outcome "${args.outcome}" recorded` }] };
|
|
58
|
+
})] : []),
|
|
51
59
|
tool('org_send', 'Send a message to another agent (role id) or another org ("org:role"). This is the only inter-agent channel.', { to: z.string(), subject: z.string(), message: z.string() }, async (args) => {
|
|
52
60
|
const receipt = await deliver(role.id, args.to, args.subject, args.message);
|
|
53
61
|
return { content: [{ type: 'text', text: receipt }] };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type OrgDef } from './types.js';
|
|
2
|
+
interface TemplateRole {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
type: string;
|
|
6
|
+
reports_to: string | null;
|
|
7
|
+
responsibilities: string[];
|
|
8
|
+
}
|
|
9
|
+
interface Template {
|
|
10
|
+
goal: string;
|
|
11
|
+
roles: TemplateRole[];
|
|
12
|
+
}
|
|
13
|
+
export declare const ORG_TEMPLATES: Record<string, Template>;
|
|
14
|
+
export declare function buildFromTemplate(templateName: string, orgName: string, goal?: string): OrgDef | null;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=templates.d.ts.map
|