let-them-talk 5.4.3 → 5.5.3

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.
Files changed (39) hide show
  1. package/README.md +2 -1
  2. package/USAGE.md +1 -1
  3. package/cli.js +27 -3
  4. package/conversation-templates/autonomous-feature.json +4 -4
  5. package/conversation-templates/code-review.json +3 -3
  6. package/conversation-templates/debug-squad.json +3 -3
  7. package/conversation-templates/feature-build.json +3 -3
  8. package/conversation-templates/research-write.json +3 -3
  9. package/dashboard.html +329 -158
  10. package/dashboard.js +3459 -3429
  11. package/package.json +114 -113
  12. package/scripts/repair-canonical-events.js +179 -0
  13. package/server.js +26 -85
  14. package/state/canonical.js +14 -0
  15. package/templates/debate.json +2 -2
  16. package/templates/managed.json +4 -4
  17. package/templates/pair.json +2 -2
  18. package/templates/review.json +2 -2
  19. package/templates/team.json +3 -3
  20. package/vendor/highlight-github-dark.min.css +10 -0
  21. package/vendor/highlight.min.js +1232 -0
  22. package/vendor/katex-fonts/KaTeX_AMS-Regular.woff2 +0 -0
  23. package/vendor/katex-fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  24. package/vendor/katex-fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  25. package/vendor/katex-fonts/KaTeX_Main-Bold.woff2 +0 -0
  26. package/vendor/katex-fonts/KaTeX_Main-Italic.woff2 +0 -0
  27. package/vendor/katex-fonts/KaTeX_Main-Regular.woff2 +0 -0
  28. package/vendor/katex-fonts/KaTeX_Math-Italic.woff2 +0 -0
  29. package/vendor/katex-fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  30. package/vendor/katex-fonts/KaTeX_Script-Regular.woff2 +0 -0
  31. package/vendor/katex-fonts/KaTeX_Size1-Regular.woff2 +0 -0
  32. package/vendor/katex-fonts/KaTeX_Size2-Regular.woff2 +0 -0
  33. package/vendor/katex-fonts/KaTeX_Size3-Regular.woff2 +0 -0
  34. package/vendor/katex-fonts/KaTeX_Size4-Regular.woff2 +0 -0
  35. package/vendor/katex-fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  36. package/vendor/katex.min.css +1 -0
  37. package/vendor/katex.min.js +1 -0
  38. package/vendor/marked.min.js +6 -0
  39. package/vendor/mermaid.min.js +2314 -0
package/package.json CHANGED
@@ -1,113 +1,114 @@
1
- {
2
- "name": "let-them-talk",
3
- "version": "5.4.3",
4
- "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
- "main": "server.js",
6
- "bin": {
7
- "agent-bridge": "./cli.js",
8
- "let-them-talk": "./cli.js"
9
- },
10
- "scripts": {
11
- "start": "node server.js",
12
- "dashboard": "node dashboard.js",
13
- "export:markdown-workspace": "node scripts/export-markdown-workspace.js",
14
- "sync:packaged-docs": "node scripts/sync-packaged-docs.js",
15
- "prepack": "npm run sync:packaged-docs",
16
- "test": "npm run verify",
17
- "verify": "npm run verify:contracts && npm run verify:replay && npm run verify:invariants",
18
- "verify:docs-onboarding": "node scripts/check-docs-onboarding.js",
19
- "verify:contracts": "npm run verify:contracts:runtime && npm run verify:contracts:schema && npm run verify:contracts:branches && npm run verify:contracts:markdown-workspace",
20
- "verify:contracts:runtime": "node scripts/check-runtime-contract.js",
21
- "verify:contracts:schema": "node scripts/check-event-schema.js",
22
- "verify:contracts:branches": "node scripts/check-branch-semantics.js",
23
- "verify:contracts:markdown-workspace": "node scripts/check-markdown-workspace.js",
24
- "verify:replay": "npm run verify:replay:positive && npm run verify:replay:negative",
25
- "verify:replay:positive": "npm run verify:replay:healthy && npm run verify:replay:clean",
26
- "verify:replay:healthy": "node scripts/check-message-replay.js --scenario healthy",
27
- "verify:replay:clean": "node scripts/check-message-replay.js --scenario clean",
28
- "verify:replay:negative": "node scripts/run-verification-suite.js replay-negative",
29
- "verify:invariants": "npm run verify:invariants:authority && npm run verify:invariants:dashboard-control-plane && npm run verify:invariants:performance-indexing && npm run verify:invariants:capabilities && npm run verify:invariants:api-agent-parity && npm run verify:invariants:dashboard-semantic-gap && npm run verify:invariants:migration-hardening && npm run verify:invariants:branches && npm run verify:invariants:sessions && npm run verify:invariants:evidence && npm run verify:invariants:context && npm run verify:invariants:autonomy-v2 && npm run verify:invariants:autonomy-v2-watchdog && npm run verify:invariants:autonomy-v2-execution && npm run verify:invariants:agent-contracts && npm run verify:invariants:managed-team-integration && npm run verify:invariants:lifecycle-hooks && npm run verify:invariants:markdown-workspace-export && npm run verify:invariants:markdown-workspace-safety",
30
- "verify:invariants:authority": "node scripts/check-invariants.js --suite authority",
31
- "verify:invariants:dashboard-control-plane": "node scripts/check-dashboard-control-plane.js",
32
- "verify:invariants:performance-indexing": "node scripts/check-performance-indexing.js",
33
- "verify:invariants:capabilities": "node scripts/check-provider-capabilities.js",
34
- "verify:invariants:api-agent-parity": "node scripts/check-api-agent-parity.js",
35
- "verify:invariants:dashboard-semantic-gap": "node scripts/run-verification-suite.js dashboard-semantic-gap",
36
- "verify:invariants:migration-hardening": "node scripts/check-migration-hardening.js",
37
- "verify:invariants:branches": "node scripts/check-branch-isolation.js && node scripts/check-branch-fork-snapshot.js",
38
- "verify:invariants:branch-fork": "node scripts/check-branch-fork-snapshot.js",
39
- "verify:invariants:sessions": "node scripts/check-session-lifecycle.js",
40
- "verify:invariants:evidence": "node scripts/check-evidence-completion.js",
41
- "verify:invariants:context": "node scripts/check-session-aware-context.js",
42
- "verify:invariants:autonomy-v2": "node scripts/check-autonomy-v2-decision.js",
43
- "verify:invariants:autonomy-v2-watchdog": "node scripts/check-autonomy-v2-watchdog.js",
44
- "verify:invariants:autonomy-v2-execution": "node scripts/check-autonomy-v2-execution.js",
45
- "verify:invariants:agent-contracts": "node scripts/check-agent-contract-advisory.js",
46
- "verify:invariants:managed-team-integration": "node scripts/check-managed-team-integration.js",
47
- "verify:invariants:lifecycle-hooks": "node scripts/check-lifecycle-hooks.js",
48
- "verify:invariants:markdown-workspace-export": "node scripts/check-markdown-workspace-export.js",
49
- "verify:invariants:markdown-workspace-safety": "node scripts/check-markdown-workspace-safety.js",
50
- "verify:smoke": "node scripts/run-verification-suite.js smoke"
51
- },
52
- "engines": {
53
- "node": ">=18.0.0"
54
- },
55
- "files": [
56
- "data-dir.js",
57
- "server.js",
58
- "dashboard.js",
59
- "dashboard.html",
60
- "api-agents.js",
61
- "runtime-descriptor.js",
62
- "agent-contracts.js",
63
- "managed-team-integration.js",
64
- "autonomy/",
65
- "events/",
66
- "state/",
67
- "providers/",
68
- "office/",
69
- "mods/",
70
- "scripts/",
71
- "docs/",
72
- "USAGE.md",
73
- "cli.js",
74
- "templates/",
75
- "conversation-templates/",
76
- "logo.png",
77
- "LICENSE",
78
- "SECURITY.md",
79
- "CHANGELOG.md"
80
- ],
81
- "keywords": [
82
- "mcp",
83
- "claude",
84
- "claude-code",
85
- "gemini-cli",
86
- "codex-cli",
87
- "agent",
88
- "multi-agent",
89
- "communication",
90
- "message-broker",
91
- "ai-agents",
92
- "let-them-talk"
93
- ],
94
- "repository": {
95
- "type": "git",
96
- "url": "git+https://github.com/Dekelelz/let-them-talk.git"
97
- },
98
- "homepage": "https://talk.unrealai.studio",
99
- "bugs": {
100
- "url": "https://github.com/Dekelelz/let-them-talk/issues"
101
- },
102
- "author": "Dekelelz <contact@talk.unrealai.studio>",
103
- "license": "SEE LICENSE IN LICENSE",
104
- "dependencies": {
105
- "@modelcontextprotocol/sdk": "^1.29.0",
106
- "three": "0.175.0"
107
- },
108
- "overrides": {
109
- "hono": "^4.12.14",
110
- "path-to-regexp": "^8.4.2",
111
- "@hono/node-server": "^1.19.14"
112
- }
113
- }
1
+ {
2
+ "name": "let-them-talk",
3
+ "version": "5.5.3",
4
+ "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
+ "main": "server.js",
6
+ "bin": {
7
+ "agent-bridge": "./cli.js",
8
+ "let-them-talk": "./cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node server.js",
12
+ "dashboard": "node dashboard.js",
13
+ "export:markdown-workspace": "node scripts/export-markdown-workspace.js",
14
+ "sync:packaged-docs": "node scripts/sync-packaged-docs.js",
15
+ "prepack": "npm run sync:packaged-docs",
16
+ "test": "npm run verify",
17
+ "verify": "npm run verify:contracts && npm run verify:replay && npm run verify:invariants",
18
+ "verify:docs-onboarding": "node scripts/check-docs-onboarding.js",
19
+ "verify:contracts": "npm run verify:contracts:runtime && npm run verify:contracts:schema && npm run verify:contracts:branches && npm run verify:contracts:markdown-workspace",
20
+ "verify:contracts:runtime": "node scripts/check-runtime-contract.js",
21
+ "verify:contracts:schema": "node scripts/check-event-schema.js",
22
+ "verify:contracts:branches": "node scripts/check-branch-semantics.js",
23
+ "verify:contracts:markdown-workspace": "node scripts/check-markdown-workspace.js",
24
+ "verify:replay": "npm run verify:replay:positive && npm run verify:replay:negative",
25
+ "verify:replay:positive": "npm run verify:replay:healthy && npm run verify:replay:clean",
26
+ "verify:replay:healthy": "node scripts/check-message-replay.js --scenario healthy",
27
+ "verify:replay:clean": "node scripts/check-message-replay.js --scenario clean",
28
+ "verify:replay:negative": "node scripts/run-verification-suite.js replay-negative",
29
+ "verify:invariants": "npm run verify:invariants:authority && npm run verify:invariants:dashboard-control-plane && npm run verify:invariants:performance-indexing && npm run verify:invariants:capabilities && npm run verify:invariants:api-agent-parity && npm run verify:invariants:dashboard-semantic-gap && npm run verify:invariants:migration-hardening && npm run verify:invariants:branches && npm run verify:invariants:sessions && npm run verify:invariants:evidence && npm run verify:invariants:context && npm run verify:invariants:autonomy-v2 && npm run verify:invariants:autonomy-v2-watchdog && npm run verify:invariants:autonomy-v2-execution && npm run verify:invariants:agent-contracts && npm run verify:invariants:managed-team-integration && npm run verify:invariants:lifecycle-hooks && npm run verify:invariants:markdown-workspace-export && npm run verify:invariants:markdown-workspace-safety",
30
+ "verify:invariants:authority": "node scripts/check-invariants.js --suite authority",
31
+ "verify:invariants:dashboard-control-plane": "node scripts/check-dashboard-control-plane.js",
32
+ "verify:invariants:performance-indexing": "node scripts/check-performance-indexing.js",
33
+ "verify:invariants:capabilities": "node scripts/check-provider-capabilities.js",
34
+ "verify:invariants:api-agent-parity": "node scripts/check-api-agent-parity.js",
35
+ "verify:invariants:dashboard-semantic-gap": "node scripts/run-verification-suite.js dashboard-semantic-gap",
36
+ "verify:invariants:migration-hardening": "node scripts/check-migration-hardening.js",
37
+ "verify:invariants:branches": "node scripts/check-branch-isolation.js && node scripts/check-branch-fork-snapshot.js",
38
+ "verify:invariants:branch-fork": "node scripts/check-branch-fork-snapshot.js",
39
+ "verify:invariants:sessions": "node scripts/check-session-lifecycle.js",
40
+ "verify:invariants:evidence": "node scripts/check-evidence-completion.js",
41
+ "verify:invariants:context": "node scripts/check-session-aware-context.js",
42
+ "verify:invariants:autonomy-v2": "node scripts/check-autonomy-v2-decision.js",
43
+ "verify:invariants:autonomy-v2-watchdog": "node scripts/check-autonomy-v2-watchdog.js",
44
+ "verify:invariants:autonomy-v2-execution": "node scripts/check-autonomy-v2-execution.js",
45
+ "verify:invariants:agent-contracts": "node scripts/check-agent-contract-advisory.js",
46
+ "verify:invariants:managed-team-integration": "node scripts/check-managed-team-integration.js",
47
+ "verify:invariants:lifecycle-hooks": "node scripts/check-lifecycle-hooks.js",
48
+ "verify:invariants:markdown-workspace-export": "node scripts/check-markdown-workspace-export.js",
49
+ "verify:invariants:markdown-workspace-safety": "node scripts/check-markdown-workspace-safety.js",
50
+ "verify:smoke": "node scripts/run-verification-suite.js smoke"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "files": [
56
+ "data-dir.js",
57
+ "server.js",
58
+ "dashboard.js",
59
+ "dashboard.html",
60
+ "api-agents.js",
61
+ "runtime-descriptor.js",
62
+ "agent-contracts.js",
63
+ "managed-team-integration.js",
64
+ "autonomy/",
65
+ "events/",
66
+ "state/",
67
+ "providers/",
68
+ "office/",
69
+ "mods/",
70
+ "scripts/",
71
+ "docs/",
72
+ "vendor/",
73
+ "USAGE.md",
74
+ "cli.js",
75
+ "templates/",
76
+ "conversation-templates/",
77
+ "logo.png",
78
+ "LICENSE",
79
+ "SECURITY.md",
80
+ "CHANGELOG.md"
81
+ ],
82
+ "keywords": [
83
+ "mcp",
84
+ "claude",
85
+ "claude-code",
86
+ "gemini-cli",
87
+ "codex-cli",
88
+ "agent",
89
+ "multi-agent",
90
+ "communication",
91
+ "message-broker",
92
+ "ai-agents",
93
+ "let-them-talk"
94
+ ],
95
+ "repository": {
96
+ "type": "git",
97
+ "url": "git+https://github.com/Dekelelz/let-them-talk.git"
98
+ },
99
+ "homepage": "https://talk.unrealai.studio",
100
+ "bugs": {
101
+ "url": "https://github.com/Dekelelz/let-them-talk/issues"
102
+ },
103
+ "author": "Dekelelz <contact@talk.unrealai.studio>",
104
+ "license": "SEE LICENSE IN LICENSE",
105
+ "dependencies": {
106
+ "@modelcontextprotocol/sdk": "^1.29.0",
107
+ "three": "0.175.0"
108
+ },
109
+ "overrides": {
110
+ "hono": "^4.12.14",
111
+ "path-to-regexp": "^8.4.2",
112
+ "@hono/node-server": "^1.19.14"
113
+ }
114
+ }
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ // Repair a corrupted canonical event log in-place.
3
+ //
4
+ // Problem this fixes:
5
+ // Pre-v5.5.3 Clear Messages would emit a message.redacted event for every
6
+ // message currently in the projection, even if that message had no
7
+ // corresponding message.sent event in the canonical log (e.g. legacy
8
+ // projection-only messages left over from a partial migration, or an
9
+ // earlier clear cycle). On the next rebuild/replay the redacted event
10
+ // fails with "cannot apply message.redacted because message X does not
11
+ // exist", which blocks further Clear Messages on that branch.
12
+ //
13
+ // v5.5.3 fixed the root cause in clearMessages so new redactions are
14
+ // gated on the presence of a message.sent ancestor. This script cleans
15
+ // up the orphan redactions that are already in the event log from
16
+ // previous versions so the branch can replay again.
17
+ //
18
+ // What it does:
19
+ // - For every branch under .agent-bridge/runtime/branches/<branch>/:
20
+ // - Reads events.jsonl
21
+ // - Collects the set of message IDs that have a message.sent event
22
+ // - Drops message.redacted and message.corrected events whose
23
+ // payload.message_id is not in that set (orphans)
24
+ // - Backs up the original events.jsonl to events.jsonl.pre-repair-<ts>
25
+ // - Writes the cleaned stream back
26
+ // - Deletes the branch's projection files (messages.jsonl, history.jsonl,
27
+ // dashboard-query-projection.json, events.head.json) so the runtime
28
+ // rebuilds them cleanly on the next read.
29
+ //
30
+ // Usage:
31
+ // node agent-bridge/scripts/repair-canonical-events.js [project-path]
32
+ // node agent-bridge/scripts/repair-canonical-events.js --dry-run [project-path]
33
+ //
34
+ // project-path defaults to the current working directory.
35
+
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+ const { resolveDataDir } = require('../data-dir');
39
+
40
+ function readJsonl(file) {
41
+ if (!fs.existsSync(file)) return [];
42
+ return fs
43
+ .readFileSync(file, 'utf8')
44
+ .split(/\r?\n/)
45
+ .filter((line) => line.trim())
46
+ .map((line) => {
47
+ try { return JSON.parse(line); } catch { return null; }
48
+ })
49
+ .filter(Boolean);
50
+ }
51
+
52
+ function repairBranch(branchDir, opts) {
53
+ const eventsFile = path.join(branchDir, 'events.jsonl');
54
+ if (!fs.existsSync(eventsFile)) return { skipped: true, reason: 'no events.jsonl' };
55
+
56
+ const events = readJsonl(eventsFile);
57
+ if (events.length === 0) return { skipped: true, reason: 'empty events.jsonl' };
58
+
59
+ const sentIds = new Set();
60
+ for (const ev of events) {
61
+ if (ev && ev.type === 'message.sent' && ev.payload && ev.payload.message && typeof ev.payload.message.id === 'string') {
62
+ sentIds.add(ev.payload.message.id);
63
+ }
64
+ }
65
+
66
+ const kept = [];
67
+ const orphans = [];
68
+ for (const ev of events) {
69
+ if (ev && (ev.type === 'message.redacted' || ev.type === 'message.corrected')) {
70
+ const msgId = ev.payload && ev.payload.message_id;
71
+ if (msgId && !sentIds.has(msgId)) {
72
+ orphans.push(ev);
73
+ continue;
74
+ }
75
+ }
76
+ kept.push(ev);
77
+ }
78
+
79
+ const result = {
80
+ branch: path.basename(branchDir),
81
+ total_events: events.length,
82
+ message_sent_events: sentIds.size,
83
+ orphan_redacted: orphans.filter((o) => o.type === 'message.redacted').length,
84
+ orphan_corrected: orphans.filter((o) => o.type === 'message.corrected').length,
85
+ kept_events: kept.length,
86
+ };
87
+
88
+ if (opts.dryRun) {
89
+ result.dryRun = true;
90
+ return result;
91
+ }
92
+
93
+ if (orphans.length === 0) {
94
+ result.skipped = true;
95
+ result.reason = 'no orphan events';
96
+ return result;
97
+ }
98
+
99
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
100
+ const backupFile = path.join(branchDir, `events.jsonl.pre-repair-${stamp}`);
101
+ fs.copyFileSync(eventsFile, backupFile);
102
+ result.backup = backupFile;
103
+
104
+ fs.writeFileSync(eventsFile, kept.map((e) => JSON.stringify(e)).join('\n') + '\n');
105
+
106
+ // Drop cached projections + head pointer so the runtime rebuilds next read
107
+ for (const name of ['messages.jsonl', 'history.jsonl', 'dashboard-query-projection.json', 'events.head.json']) {
108
+ const p = path.join(branchDir, name);
109
+ if (fs.existsSync(p)) fs.unlinkSync(p);
110
+ }
111
+ result.projections_cleared = true;
112
+
113
+ return result;
114
+ }
115
+
116
+ function main(argv) {
117
+ const args = argv.slice(2);
118
+ const dryRun = args.includes('--dry-run') || args.includes('-n');
119
+ const projectArg = args.filter((a) => !a.startsWith('-'))[0] || process.cwd();
120
+
121
+ const dataDir = resolveDataDir({ cwd: projectArg });
122
+ const branchesDir = path.join(dataDir, 'runtime', 'branches');
123
+
124
+ console.log('');
125
+ console.log(' Canonical event log repair');
126
+ console.log(' ==========================');
127
+ console.log(' Project: ' + projectArg);
128
+ console.log(' Runtime: ' + branchesDir);
129
+ console.log(dryRun ? ' Mode: DRY RUN (no changes written)' : ' Mode: apply');
130
+ console.log('');
131
+
132
+ if (!fs.existsSync(branchesDir)) {
133
+ console.log(' [info] No canonical runtime at this path. Nothing to repair.');
134
+ return;
135
+ }
136
+
137
+ const branches = fs.readdirSync(branchesDir).filter((name) => {
138
+ const p = path.join(branchesDir, name);
139
+ return fs.statSync(p).isDirectory();
140
+ });
141
+
142
+ if (branches.length === 0) {
143
+ console.log(' [info] No branches present. Nothing to repair.');
144
+ return;
145
+ }
146
+
147
+ for (const name of branches) {
148
+ const r = repairBranch(path.join(branchesDir, name), { dryRun });
149
+ console.log(` Branch "${name}":`);
150
+ if (r.skipped) {
151
+ console.log(' skipped — ' + r.reason);
152
+ } else {
153
+ console.log(` ${r.total_events} total events, ${r.message_sent_events} sent messages`);
154
+ console.log(` orphan redactions: ${r.orphan_redacted}, orphan corrections: ${r.orphan_corrected}`);
155
+ if (!dryRun) {
156
+ console.log(' [ok] rewrote events.jsonl (' + r.kept_events + ' events kept)');
157
+ console.log(' [ok] backup: ' + r.backup);
158
+ console.log(' [ok] projections cleared — will rebuild on next read');
159
+ } else {
160
+ console.log(' [dry-run] would drop ' + (r.orphan_redacted + r.orphan_corrected) + ' orphan event(s) and back up the original');
161
+ }
162
+ }
163
+ }
164
+
165
+ console.log('');
166
+ if (!dryRun) {
167
+ console.log(' Done. Clear Messages should now work on the repaired branches.');
168
+ } else {
169
+ console.log(' Re-run without --dry-run to apply.');
170
+ }
171
+ console.log('');
172
+ }
173
+
174
+ if (require.main === module) {
175
+ try { main(process.argv); }
176
+ catch (e) { console.error(' [error] ' + (e && e.stack ? e.stack : e)); process.exit(1); }
177
+ }
178
+
179
+ module.exports = { repairBranch };
package/server.js CHANGED
@@ -1918,6 +1918,8 @@ function buildGuide(level = 'standard') {
1918
1918
  rules.push('SELF-RELIANCE RULE: When the Owner gives you a goal, treat it as a goal — NOT a checklist of approval gates. Break it down yourself, pick tasks via get_work(), and work until done. NEVER stop to ask "should I do X?" or "do you want me to Y?" for decisions you and the team can make. Your default answer to uncertainty is: decide, log_decision() to record the choice, continue. Asking the Owner for permission on small decisions is the failure mode — deciding and moving is the success mode.');
1919
1919
  rules.push('TEAM-FIRST ESCALATION RULE: Before DMing Dashboard/Owner with a question, try these in order: (1) kb_read() — did the team already decide this? (2) DM a teammate with the relevant skill (use list_agents() to find them). (3) call_vote() if the team genuinely disagrees. (4) log_decision() to lock in your choice and move forward. Only escalate to Owner when: (a) the overall goal is complete and the next strategic direction genuinely needs a human call, or (b) you hit a true blocker only the Owner can resolve (credentials, priorities, business rules, access). "I am not sure which design to pick" is NOT an Owner question — it is a team_decision() question.');
1920
1920
  rules.push('DONE-WHEN-DONE RULE: "Done" means the Owner\'s original GOAL is achieved, not "I finished my current step". After verify_and_advance(), immediately call get_work() again to find the next piece of the goal. The loop ends when the goal is complete and evidence is recorded — not when the current step ends. If get_work() returns nothing and the goal still is not done, synthesize: break the remaining work into new tasks with create_task() and keep going.');
1921
+ rules.push('FORMATTING RULE (dashboard is rendered rich markdown): The Messages tab renders GFM markdown, GitHub-quality tables, fenced code with syntax highlighting, Obsidian-style callouts, Mermaid diagrams, KaTeX math, and clickable images. WRITE LIKE YOU ARE PUBLISHING. Use: (1) **tables** for structured data (status, file changes, comparisons) — NEVER use indented lists for tabular info; (2) fenced code blocks with language tag (```ts, ```bash, ```json); (3) callouts for status: > [!SUCCESS] when something shipped, > [!WARNING] for risks, > [!DANGER] for blockers, > [!NOTE] for context, > [!SUMMARY]- (collapsible) for long reports; (4) ```mermaid blocks for architecture/flow/sequence diagrams instead of ASCII art; (5) headings (##, ###) to structure long updates; (6) task lists (- [x] done / - [ ] todo) for action items. A terse structured report beats a wall of text.');
1922
+ rules.push('STATUS-REPORT TEMPLATE: When reporting progress to the Owner or Quality Lead, follow this shape:\n\n> [!SUMMARY]- Headline (1 sentence)\n> \n> ## What shipped\n> | Area | Change | Evidence |\n> |---|---|---|\n> | ... | ... | files_changed + verification |\n> \n> ## Blockers\n> > [!WARNING] describe blocker + what unblocks it\n> \n> ## Next\n> - [ ] next step 1\n> - [ ] next step 2\n\nIf nothing is blocked, omit the Blockers section. If you are only reporting a small update, skip the collapsible summary and use a callout with the headline inline. Do not narrate in prose when a table would be clearer.');
1921
1923
 
1922
1924
  // Minimal level: Tier 0 only — for experienced agents refreshing rules
1923
1925
  if (level === 'minimal') {
@@ -3607,14 +3609,13 @@ async function toolListenGroup() {
3607
3609
  sendsSinceLastListen = 0;
3608
3610
  sendLimit = 10;
3609
3611
  touchHeartbeat(registeredName);
3610
- resolve({
3611
- messages: [],
3612
- message_count: 0,
3613
- retry: true,
3614
- batch_summary: isManagedMode()
3615
- ? 'No new messages this is NORMAL, not an error. Call listen() again immediately to keep waiting. Codex CLI may end the call near 120s; that is the host limit, not a failure.'
3616
- : 'No new messages — this is NORMAL, not an error. Call listen_group() again immediately to keep listening. Codex CLI may end the call near 120s; that is the host limit, not a failure.',
3617
- });
3612
+ // Minimal empty-batch response — the EMPTY-RETURN RULE is already in
3613
+ // every agent's guide + AGENTS.md block, so there's no need to repeat
3614
+ // the "this is normal, call again" reminder every 90s. Trimmed to the
3615
+ // irreducible payload so long listen loops cost as few tokens as
3616
+ // possible. Over a full session this saves ~2 tokens per wake-up *
3617
+ // hundreds of wake-ups = meaningful savings on long-running agents.
3618
+ resolve({ messages: [], retry: true });
3618
3619
  }
3619
3620
  };
3620
3621
 
@@ -3707,101 +3708,41 @@ function buildListenGroupResponse(batch, consumed, agentName, listenStart) {
3707
3708
  return new Date(a.timestamp) - new Date(b.timestamp);
3708
3709
  });
3709
3710
 
3710
- // Build batch summary for triage
3711
- const summaryCounts = {};
3712
- for (const m of batch) {
3713
- const type = m.system || m.from === '__system__' ? 'system'
3714
- : m.broadcast ? 'broadcast' : (m.reply_to || m.thread_id) ? 'thread' : 'direct';
3715
- const key = `${m.from}:${type}`;
3716
- summaryCounts[key] = (summaryCounts[key] || 0) + 1;
3717
- }
3718
- const summaryParts = [];
3719
- for (const [key, count] of Object.entries(summaryCounts)) {
3720
- const [from, type] = key.split(':');
3721
- summaryParts.push(`${count} ${type} from ${from}`);
3722
- }
3723
- const batchSummary = `${batch.length} messages: ${summaryParts.join(', ')}`;
3724
-
3725
- // Agent statuses — lightweight, no history reads. Uses the recency grace
3726
- // so peers that just briefly returned from listen_group() to process a
3727
- // batch still read as "listening", not "working".
3728
- const agents = getAgents();
3729
- const agentNames = Object.keys(agents).filter(n => isPidAlive(agents[n].pid, agents[n].last_activity));
3730
- const agentStatus = {};
3731
- for (const n of agentNames) {
3732
- if (isRecentlyListening(agents[n])) {
3733
- agentStatus[n] = 'listening';
3734
- } else {
3735
- const lastListened = agents[n].last_listened_at;
3736
- const sinceLastListen = lastListened ? Date.now() - new Date(lastListened).getTime() : Infinity;
3737
- agentStatus[n] = sinceLastListen > 120000 ? 'unresponsive' : 'working';
3738
- }
3739
- }
3740
-
3741
- const now = Date.now();
3711
+ // LEAN RESPONSE (v5.5.3+): the agent already has every prior message in its
3712
+ // own LLM context, plus the full rule set from get_guide() + AGENTS.md. We
3713
+ // only send the NEW messages + the managed-mode signals needed for
3714
+ // turn-taking. No repeated reminders, no agent rosters, no "next_action"
3715
+ // agents already know what to do from their guide.
3742
3716
  const result = {
3743
3717
  messages: batch.map(m => {
3744
- const ageSec = Math.round((now - new Date(m.timestamp).getTime()) / 1000);
3745
3718
  const isOwnerMsg = m.from === 'Dashboard' || m.from === 'Owner' || m.from === 'dashboard' || m.from === 'owner';
3746
3719
  return {
3747
3720
  id: m.id, from: m.from, to: m.to, content: m.content,
3748
3721
  timestamp: m.timestamp,
3749
- age_seconds: ageSec,
3750
- ...(ageSec > 30 && { delayed: true }),
3751
3722
  ...(m.reply_to && { reply_to: m.reply_to }),
3752
3723
  ...(m.thread_id && { thread_id: m.thread_id }),
3753
3724
  ...(m.addressed_to && { addressed_to: m.addressed_to }),
3754
- ...(m.to === '__group__' && {
3755
- addressed_to_you: !m.addressed_to || m.addressed_to.includes(agentName),
3756
- should_respond: !m.addressed_to || m.addressed_to.includes(agentName),
3757
- }),
3758
- ...(isOwnerMsg && {
3759
- from_owner: true,
3760
- system_instruction: 'OWNER MESSAGE. You MUST reply by calling send_message(to="Dashboard", content="your reply") — the owner reads replies ONLY in the dashboard Messages tab. Any text you write in your CLI terminal is INVISIBLE to the owner and does not count as a reply. After send_message, call listen_group() again immediately.',
3761
- }),
3725
+ ...(isOwnerMsg && { from_owner: true }),
3762
3726
  };
3763
3727
  }),
3764
- message_count: batch.length,
3765
- batch_summary: batchSummary,
3766
- agents_online: agentNames.length,
3767
- agents_status: agentStatus,
3768
3728
  };
3769
3729
 
3770
- // Managed mode: add context so agents know whether to respond
3730
+ // Managed mode: minimal turn-taking signal. Managers need to know the
3731
+ // floor/phase to decide next yield_floor(); participants need to know if
3732
+ // they hold the floor. The managed-mode RULE TEXT is already in the guide
3733
+ // so we don't repeat it per call.
3771
3734
  if (isManagedMode()) {
3772
3735
  const managed = getManagedConfig();
3773
- const youHaveFloor = managed.turn_current === agentName;
3774
- const youAreManager = managed.manager === agentName;
3775
-
3776
3736
  result.managed_context = {
3777
- phase: managed.phase, floor: managed.floor, manager: managed.manager,
3778
- you_have_floor: youHaveFloor, you_are_manager: youAreManager,
3737
+ phase: managed.phase,
3738
+ floor: managed.floor,
3739
+ manager: managed.manager,
3740
+ you_have_floor: managed.turn_current === agentName,
3741
+ you_are_manager: managed.manager === agentName,
3779
3742
  turn_current: managed.turn_current,
3780
3743
  };
3781
-
3782
- if (youAreManager) {
3783
- result.should_respond = true;
3784
- result.instructions = 'You are the MANAGER. Decide who speaks next using yield_floor(), or advance the phase using set_phase().';
3785
- } else if (youHaveFloor) {
3786
- result.should_respond = true;
3787
- result.instructions = 'It is YOUR TURN to speak. Respond now, then the floor will return to the manager.';
3788
- } else if (managed.floor === 'execution') {
3789
- result.should_respond = false;
3790
- result.instructions = `EXECUTION PHASE: Focus on your assigned tasks. Only message the manager (${managed.manager}) if you need help or to report completion.`;
3791
- } else {
3792
- result.should_respond = false;
3793
- result.instructions = 'DO NOT RESPOND. Wait for the manager to give you the floor. Call listen() again to wait.';
3794
- }
3795
3744
  }
3796
3745
 
3797
- const fromDashboard = Array.isArray(batch) && batch.some(m => m && (m.from === 'Dashboard' || m.from === 'Owner' || m.from === 'dashboard' || m.from === 'owner'));
3798
- const dashboardReplyHint = fromDashboard
3799
- ? ' One of these messages is from Dashboard/Owner — reply via send_message(to="Dashboard") so the owner sees your reply in the dashboard Messages tab. Do NOT narrate the reply in your CLI terminal; terminal output is invisible to the owner.'
3800
- : '';
3801
- result.next_action = (isAutonomousMode()
3802
- ? 'Process these messages, then call get_work() to continue the proactive work loop. Do NOT call listen_group() — use get_work() instead.'
3803
- : 'After processing these messages and sending your response, call listen_group() again immediately. Never stop listening.') + dashboardReplyHint;
3804
-
3805
3746
  const listenSurface = isManagedMode() && result.managed_context && result.managed_context.you_are_manager
3806
3747
  ? 'manager_listen'
3807
3748
  : (isManagedMode() ? 'participant_listen' : 'team_listen');
@@ -8195,7 +8136,7 @@ function toolToggleRule(ruleId) {
8195
8136
  // --- MCP Server setup ---
8196
8137
 
8197
8138
  const server = new Server(
8198
- { name: 'agent-bridge', version: '5.4.3' },
8139
+ { name: 'agent-bridge', version: '5.5.3' },
8199
8140
  { capabilities: { tools: {} } }
8200
8141
  );
8201
8142
 
@@ -9324,7 +9265,7 @@ async function main() {
9324
9265
  try {
9325
9266
  const transport = new StdioServerTransport();
9326
9267
  await server.connect(transport);
9327
- console.error('Agent Bridge MCP server v5.4.3 running (65 tools)');
9268
+ console.error('Agent Bridge MCP server v5.5.3 running (65 tools)');
9328
9269
  } catch (e) {
9329
9270
  console.error('ERROR: MCP server failed to start: ' + e.message);
9330
9271
  console.error('Fix: Run "npx let-them-talk doctor" to check your setup.');
@@ -2770,8 +2770,22 @@ function createCanonicalState(options = {}) {
2770
2770
  const redactedAt = params.redactedAt || new Date().toISOString();
2771
2771
  const clearedMessageIds = [];
2772
2772
 
2773
+ // Only redact messages that actually have a canonical message.sent event
2774
+ // in this branch's log. Redacting a message that was never "sent" in
2775
+ // canonical terms (e.g. a legacy projection-only message left over from
2776
+ // pre-canonical clear cycles) would create an orphan redaction that
2777
+ // breaks future replay — the rebuild fails with "cannot apply
2778
+ // message.redacted because message X does not exist".
2779
+ const canonicalSentIds = new Set();
2780
+ for (const event of readCanonicalMessageEvents(branch)) {
2781
+ if (event && event.type === 'message.sent' && event.payload && event.payload.message && typeof event.payload.message.id === 'string') {
2782
+ canonicalSentIds.add(event.payload.message.id);
2783
+ }
2784
+ }
2785
+
2773
2786
  for (const message of Array.isArray(currentMessages) ? currentMessages : []) {
2774
2787
  if (!message || typeof message.id !== 'string' || !message.id) continue;
2788
+ if (!canonicalSentIds.has(message.id)) continue; // skip projection-only / orphan
2775
2789
  appendCanonicalMessageRedactedEvent({
2776
2790
  branch,
2777
2791
  actorAgent,