claude-multi-session 2.5.1 → 2.7.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/bin/setup.js +24 -14
- package/package.json +1 -1
- package/src/delegate.js +16 -0
- package/src/mcp-server.js +475 -51
- package/src/prompts.js +41 -20
package/bin/setup.js
CHANGED
|
@@ -102,25 +102,30 @@ IMPORTANT: Spawn ALL independent workers in a SINGLE message with multiple tool
|
|
|
102
102
|
### Rule 4: Post-Phase Verification (MANDATORY)
|
|
103
103
|
After ALL workers in a phase complete, BEFORE spawning the next phase, STOP and fill in this checklist:
|
|
104
104
|
|
|
105
|
-
=== PHASE GATE CHECKPOINT (
|
|
105
|
+
=== PHASE GATE CHECKPOINT (use phase_gate tool before EVERY team_spawn after Phase 0) ===
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
Instead of manually running 4 separate tool calls, use the \`phase_gate\` tool which does ALL checks in one call:
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
\`\`\`
|
|
110
|
+
mcp__multi-session__phase_gate({
|
|
111
|
+
phase_completing: "Phase 0: Foundation",
|
|
112
|
+
phase_starting: "Phase 1: Routes",
|
|
113
|
+
expected_artifacts: ["project-foundation", "shared-conventions"],
|
|
114
|
+
expected_idle: ["setup"],
|
|
115
|
+
expected_readers: { "shared-conventions": ["api-dev", "db-dev"] }
|
|
116
|
+
})
|
|
117
|
+
\`\`\`
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
The tool automatically:
|
|
120
|
+
1. Checks all expected artifacts exist
|
|
121
|
+
2. Validates artifact content and tracks the read as "orchestrator"
|
|
122
|
+
3. Verifies all previous-phase workers are idle
|
|
123
|
+
4. Confirms expected consumers actually read the artifacts
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
Returns a structured pass/fail report with recommendation.
|
|
126
|
+
PROCEED ONLY IF the report says ALL CHECKS PASSED.
|
|
121
127
|
|
|
122
|
-
|
|
123
|
-
If any is NO → diagnose and fix before continuing.
|
|
128
|
+
**ENFORCED:** \`team_spawn\` will return an error if \`phase_gate\` was not called between spawn batches. The first batch (Phase 0) is free; every subsequent batch requires a passing \`phase_gate\` call first.
|
|
124
129
|
|
|
125
130
|
Count your phases upfront. If you have N phases, fill in this checkpoint exactly N-1 times (between every adjacent pair of phases). Skipping verification for later phases is the #1 cause of test failures.
|
|
126
131
|
|
|
@@ -182,12 +187,17 @@ NEVER trust a worker's self-reported completion — verify the artifact exists y
|
|
|
182
187
|
| See task completion status | \`contract_list\` |
|
|
183
188
|
| Send a correction to a worker | \`send_message\` to that session |
|
|
184
189
|
| Check who read an artifact | \`artifact_readers\` |
|
|
190
|
+
| Verify phase completion | \`phase_gate\` |
|
|
191
|
+
| Clean up between runs | \`team_reset\` |
|
|
185
192
|
|
|
186
193
|
### When NOT to Delegate
|
|
187
194
|
- Simple tasks (< 5 min, < 3 files) — do it yourself
|
|
188
195
|
- Just reading/exploring — use Read, Grep, Glob directly
|
|
189
196
|
- Tightly coupled changes — must happen atomically
|
|
190
197
|
|
|
198
|
+
### Resetting Between Runs
|
|
199
|
+
Call \`team_reset({ confirm: true })\` to clean up all team state between orchestration runs. This clears artifacts, contracts, roster, and messages.
|
|
200
|
+
|
|
191
201
|
${CLAUDE_MD_END_MARKER}
|
|
192
202
|
`;
|
|
193
203
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-multi-session",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Multi-session orchestrator for Claude Code CLI — spawn, control, pause, resume, and send multiple inputs to Claude Code sessions programmatically",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/delegate.js
CHANGED
|
@@ -278,13 +278,29 @@ class Delegate {
|
|
|
278
278
|
|
|
279
279
|
/**
|
|
280
280
|
* Handle a permission denial by sending approval.
|
|
281
|
+
* Max 2 retries to prevent infinite permission loops.
|
|
281
282
|
*/
|
|
282
283
|
async _handlePermissionRetry(name, deniedText) {
|
|
284
|
+
// Track retry count per session
|
|
285
|
+
if (!this._permRetries) this._permRetries = new Map();
|
|
286
|
+
const retries = (this._permRetries.get(name) || 0) + 1;
|
|
287
|
+
this._permRetries.set(name, retries);
|
|
288
|
+
|
|
289
|
+
if (retries > 2) {
|
|
290
|
+
return null; // Give up after 2 retries
|
|
291
|
+
}
|
|
292
|
+
|
|
283
293
|
try {
|
|
284
294
|
const response = await this.manager.send(
|
|
285
295
|
name,
|
|
286
296
|
'Yes, you have permission. Go ahead and proceed with all file operations. Do not ask for permission again — you are fully authorized.'
|
|
287
297
|
);
|
|
298
|
+
|
|
299
|
+
// Check if response still indicates permission denial
|
|
300
|
+
if (response && this._isPermissionDenied(response.text)) {
|
|
301
|
+
return null; // Still denied, don't retry further
|
|
302
|
+
}
|
|
303
|
+
|
|
288
304
|
return response;
|
|
289
305
|
} catch (err) {
|
|
290
306
|
return null;
|
package/src/mcp-server.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
const fs = require('fs');
|
|
29
29
|
const path = require('path');
|
|
30
|
+
const os = require('os');
|
|
30
31
|
const readline = require('readline');
|
|
31
32
|
const SessionManager = require('./manager');
|
|
32
33
|
const Delegate = require('./delegate');
|
|
@@ -75,6 +76,10 @@ let pipelineEngine = null;
|
|
|
75
76
|
let snapshotEngine = null;
|
|
76
77
|
let currentTeamName = null;
|
|
77
78
|
|
|
79
|
+
// Phase gate enforcement state — tracks gate calls vs spawn batches per team
|
|
80
|
+
// team -> { gateCount: number, spawnBatchCount: number, lastSpawnTime: number }
|
|
81
|
+
const _gateState = new Map();
|
|
82
|
+
|
|
78
83
|
/**
|
|
79
84
|
* Get or create Team Hub instances for a given team
|
|
80
85
|
* This function lazily creates the instances on first use
|
|
@@ -84,6 +89,11 @@ let currentTeamName = null;
|
|
|
84
89
|
* @returns {Object} Object with all team instances
|
|
85
90
|
*/
|
|
86
91
|
function getTeamInstances(teamName = 'default') {
|
|
92
|
+
// Validate team name to prevent path traversal attacks
|
|
93
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(teamName)) {
|
|
94
|
+
throw new Error(`Invalid team name "${teamName}": must contain only alphanumeric characters, hyphens, and underscores`);
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
// If team name changed or instances not yet created, recreate them
|
|
88
98
|
if (!teamHub || currentTeamName !== teamName) {
|
|
89
99
|
teamHub = new TeamHub(teamName);
|
|
@@ -428,6 +438,7 @@ const TOOLS = [
|
|
|
428
438
|
model: { type: 'string', enum: ['sonnet', 'opus', 'haiku'], description: 'Model to use (default: sonnet)' },
|
|
429
439
|
permission_mode: { type: 'string', enum: ['default', 'acceptEdits', 'bypassPermissions', 'plan'], description: 'Permission mode. Use bypassPermissions to allow sessions to write files without approval (default: bypassPermissions)' },
|
|
430
440
|
team: { type: 'string', description: 'Team name (default: "default")' },
|
|
441
|
+
work_dir: { type: 'string', description: 'Working directory for the session (default: current directory)' },
|
|
431
442
|
},
|
|
432
443
|
required: ['name', 'prompt'],
|
|
433
444
|
},
|
|
@@ -951,6 +962,43 @@ const TOOLS = [
|
|
|
951
962
|
},
|
|
952
963
|
},
|
|
953
964
|
|
|
965
|
+
// ── Phase Gate & Team Reset ─────────────────────────────────────────
|
|
966
|
+
{
|
|
967
|
+
name: 'phase_gate',
|
|
968
|
+
description:
|
|
969
|
+
'Run all 4 phase gate checks in a single call. Verifies: (1) expected artifacts exist, ' +
|
|
970
|
+
'(2) artifact content is valid, (3) all previous-phase workers are idle, (4) expected consumers read artifacts. ' +
|
|
971
|
+
'Returns a structured pass/fail report. Use this BETWEEN every pair of phases.',
|
|
972
|
+
inputSchema: {
|
|
973
|
+
type: 'object',
|
|
974
|
+
properties: {
|
|
975
|
+
phase_completing: { type: 'string', description: 'Name of the phase that just completed (e.g. "Phase 0: Foundation")' },
|
|
976
|
+
phase_starting: { type: 'string', description: 'Name of the phase about to start (e.g. "Phase 1: Routes")' },
|
|
977
|
+
expected_artifacts: { type: 'array', items: { type: 'string' }, description: 'Artifact IDs that should exist before proceeding' },
|
|
978
|
+
expected_idle: { type: 'array', items: { type: 'string' }, description: 'Worker names that should be idle (optional — if omitted, checks ALL roster members)' },
|
|
979
|
+
expected_readers: { type: 'object', description: 'Map of artifactId -> array of expected reader names. E.g. {"shared-conventions": ["api-dev", "db-dev"]}' },
|
|
980
|
+
team: { type: 'string', description: 'Team name (default: "default")' },
|
|
981
|
+
},
|
|
982
|
+
required: ['phase_completing', 'phase_starting', 'expected_artifacts'],
|
|
983
|
+
},
|
|
984
|
+
},
|
|
985
|
+
|
|
986
|
+
{
|
|
987
|
+
name: 'team_reset',
|
|
988
|
+
description:
|
|
989
|
+
'Reset all team state — clear artifacts, contracts, roster, messages. ' +
|
|
990
|
+
'Use this between orchestration runs to start fresh. Optionally preserve specific artifacts.',
|
|
991
|
+
inputSchema: {
|
|
992
|
+
type: 'object',
|
|
993
|
+
properties: {
|
|
994
|
+
team: { type: 'string', description: 'Team name (default: "default")' },
|
|
995
|
+
preserve_artifacts: { type: 'array', items: { type: 'string' }, description: 'Artifact IDs to keep (optional)' },
|
|
996
|
+
confirm: { type: 'boolean', description: 'Must be true to execute (safety check)' },
|
|
997
|
+
},
|
|
998
|
+
required: ['confirm'],
|
|
999
|
+
},
|
|
1000
|
+
},
|
|
1001
|
+
|
|
954
1002
|
// ── Session Continuity (Layer 0) ──────────────────────────────────────
|
|
955
1003
|
{
|
|
956
1004
|
name: 'continuity_snapshot',
|
|
@@ -1109,6 +1157,37 @@ const TOOLS = [
|
|
|
1109
1157
|
}
|
|
1110
1158
|
];
|
|
1111
1159
|
|
|
1160
|
+
// =============================================================================
|
|
1161
|
+
// Staleness Warning — cached check for version drift
|
|
1162
|
+
// =============================================================================
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Check if the server is stale and return a warning string if so.
|
|
1166
|
+
* Called on every tool response to ensure stale servers are noticed.
|
|
1167
|
+
*/
|
|
1168
|
+
function getStalenessWarning() {
|
|
1169
|
+
try {
|
|
1170
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
1171
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
1172
|
+
if (LOADED_VERSION !== pkg.version) {
|
|
1173
|
+
return `\n\n⚠️ STALE SERVER: Running v${LOADED_VERSION} but v${pkg.version} is installed. Restart Claude Code to load updated tools.`;
|
|
1174
|
+
}
|
|
1175
|
+
} catch (e) {
|
|
1176
|
+
// Ignore — can't check staleness
|
|
1177
|
+
}
|
|
1178
|
+
return '';
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Cache the staleness check result for 60 seconds to avoid reading package.json on every call
|
|
1182
|
+
let _stalenessCache = { warning: '', checkedAt: 0 };
|
|
1183
|
+
function getCachedStalenessWarning() {
|
|
1184
|
+
const now = Date.now();
|
|
1185
|
+
if (now - _stalenessCache.checkedAt > 60000) {
|
|
1186
|
+
_stalenessCache = { warning: getStalenessWarning(), checkedAt: now };
|
|
1187
|
+
}
|
|
1188
|
+
return _stalenessCache.warning;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1112
1191
|
// =============================================================================
|
|
1113
1192
|
// Tool Handlers — execute each tool and return result
|
|
1114
1193
|
// =============================================================================
|
|
@@ -1252,6 +1331,12 @@ async function executeTool(toolName, args) {
|
|
|
1252
1331
|
case 'team_replay':
|
|
1253
1332
|
return await handleTeamReplay(args);
|
|
1254
1333
|
|
|
1334
|
+
// ── Phase Gate & Team Reset ──
|
|
1335
|
+
case 'phase_gate':
|
|
1336
|
+
return handlePhaseGate(args);
|
|
1337
|
+
case 'team_reset':
|
|
1338
|
+
return handleTeamReset(args);
|
|
1339
|
+
|
|
1255
1340
|
// ── Session Continuity (Layer 0) handlers ──
|
|
1256
1341
|
case 'continuity_snapshot': {
|
|
1257
1342
|
const snap = new SessionSnapshot(args.projectPath);
|
|
@@ -1667,6 +1752,31 @@ async function handleTeamSpawn(args) {
|
|
|
1667
1752
|
const teamName = args.team || 'default';
|
|
1668
1753
|
const { teamHub } = getTeamInstances(teamName);
|
|
1669
1754
|
|
|
1755
|
+
// Gate enforcement: require phase_gate between spawn batches
|
|
1756
|
+
const state = _gateState.get(teamName) || { gateCount: 0, spawnBatchCount: 0, lastSpawnTime: 0 };
|
|
1757
|
+
const now = Date.now();
|
|
1758
|
+
|
|
1759
|
+
// Detect new spawn batch (>5s since last spawn = new batch)
|
|
1760
|
+
const isNewBatch = (now - state.lastSpawnTime) > 5000;
|
|
1761
|
+
if (isNewBatch) {
|
|
1762
|
+
state.spawnBatchCount++;
|
|
1763
|
+
}
|
|
1764
|
+
state.lastSpawnTime = now;
|
|
1765
|
+
_gateState.set(teamName, state);
|
|
1766
|
+
|
|
1767
|
+
// Rule: spawnBatchCount must be <= gateCount + 1
|
|
1768
|
+
// (first batch is free, every subsequent batch needs a gate)
|
|
1769
|
+
if (state.spawnBatchCount > state.gateCount + 1) {
|
|
1770
|
+
return errorResult(
|
|
1771
|
+
`PHASE GATE REQUIRED: You have spawned ${state.spawnBatchCount - 1} batch(es) of workers ` +
|
|
1772
|
+
`but only called phase_gate ${state.gateCount} time(s). ` +
|
|
1773
|
+
`Call phase_gate() to verify the previous phase completed before spawning more workers.\n\n` +
|
|
1774
|
+
`Example:\n` +
|
|
1775
|
+
`phase_gate({ phase_completing: "Phase N", phase_starting: "Phase N+1", ` +
|
|
1776
|
+
`expected_artifacts: ["..."] })`
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1670
1780
|
// Join the team roster
|
|
1671
1781
|
teamHub.joinTeam(args.name, {
|
|
1672
1782
|
role: args.role || 'team member',
|
|
@@ -1687,6 +1797,7 @@ async function handleTeamSpawn(args) {
|
|
|
1687
1797
|
model: args.model,
|
|
1688
1798
|
systemPrompt: teamSystemPrompt,
|
|
1689
1799
|
permissionMode: args.permission_mode || 'bypassPermissions',
|
|
1800
|
+
workDir: args.work_dir || process.cwd(),
|
|
1690
1801
|
});
|
|
1691
1802
|
|
|
1692
1803
|
const result = {
|
|
@@ -1704,6 +1815,12 @@ async function handleTeamSpawn(args) {
|
|
|
1704
1815
|
result.turns = response.turns;
|
|
1705
1816
|
}
|
|
1706
1817
|
|
|
1818
|
+
// Auto version check on first spawn
|
|
1819
|
+
const staleness = getCachedStalenessWarning();
|
|
1820
|
+
if (staleness) {
|
|
1821
|
+
result._staleness_warning = `Server is stale! ${staleness.trim()}`;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1707
1824
|
return textResult(JSON.stringify(result, null, 2));
|
|
1708
1825
|
} catch (err) {
|
|
1709
1826
|
return errorResult(err.message);
|
|
@@ -1953,9 +2070,9 @@ function handleArtifactGet(args) {
|
|
|
1953
2070
|
readBy: artifactStore.getReads(args.artifactId),
|
|
1954
2071
|
};
|
|
1955
2072
|
|
|
1956
|
-
// Add
|
|
2073
|
+
// Add prominent warning if reader param was not provided
|
|
1957
2074
|
if (!args.reader) {
|
|
1958
|
-
response.
|
|
2075
|
+
response._WARNING = '⚠️ UNTRACKED READ: You did not pass the "reader" parameter. This read will NOT be tracked. The orchestrator cannot verify you consumed this artifact. Fix: artifact_get({ artifactId: "' + args.artifactId + '", reader: "YOUR-SESSION-NAME" })';
|
|
1959
2076
|
}
|
|
1960
2077
|
|
|
1961
2078
|
return textResult(JSON.stringify(response, null, 2));
|
|
@@ -1979,18 +2096,22 @@ function handleArtifactList(args) {
|
|
|
1979
2096
|
return textResult(JSON.stringify({
|
|
1980
2097
|
team: teamName,
|
|
1981
2098
|
count: artifacts.length,
|
|
1982
|
-
artifacts: artifacts.map(a =>
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2099
|
+
artifacts: artifacts.map(a => {
|
|
2100
|
+
const reads = artifactStore.getReads(a.artifactId);
|
|
2101
|
+
const readers = [...new Set(reads.map(r => r.reader))];
|
|
2102
|
+
return {
|
|
2103
|
+
artifactId: a.artifactId,
|
|
2104
|
+
type: a.type,
|
|
2105
|
+
name: a.name,
|
|
2106
|
+
publisher: a.publisher,
|
|
2107
|
+
latestVersion: a.latestVersion,
|
|
2108
|
+
createdAt: a.createdAt,
|
|
2109
|
+
updatedAt: a.updatedAt,
|
|
2110
|
+
tags: a.tags,
|
|
2111
|
+
readCount: reads.length,
|
|
2112
|
+
readers: readers,
|
|
2113
|
+
};
|
|
2114
|
+
}),
|
|
1994
2115
|
}, null, 2));
|
|
1995
2116
|
} catch (err) {
|
|
1996
2117
|
return errorResult(err.message);
|
|
@@ -2543,37 +2664,300 @@ async function handleTeamReplay(args) {
|
|
|
2543
2664
|
}
|
|
2544
2665
|
|
|
2545
2666
|
// =============================================================================
|
|
2546
|
-
//
|
|
2667
|
+
// Phase Gate & Team Reset Handlers
|
|
2547
2668
|
// =============================================================================
|
|
2548
2669
|
|
|
2549
|
-
function
|
|
2550
|
-
|
|
2551
|
-
|
|
2670
|
+
function handlePhaseGate(args) {
|
|
2671
|
+
try {
|
|
2672
|
+
const teamName = args.team || 'default';
|
|
2673
|
+
const { artifactStore, teamHub } = getTeamInstances(teamName);
|
|
2552
2674
|
|
|
2553
|
-
|
|
2554
|
-
|
|
2675
|
+
const report = {
|
|
2676
|
+
gate: `${args.phase_completing} → ${args.phase_starting}`,
|
|
2677
|
+
timestamp: new Date().toISOString(),
|
|
2678
|
+
checks: [],
|
|
2679
|
+
passed: true,
|
|
2680
|
+
};
|
|
2681
|
+
|
|
2682
|
+
// Check 1: Expected artifacts exist
|
|
2683
|
+
const allArtifacts = artifactStore.list({});
|
|
2684
|
+
const existingIds = new Set(allArtifacts.map(a => a.artifactId));
|
|
2685
|
+
const artifactCheck = {
|
|
2686
|
+
check: 'artifacts_exist',
|
|
2687
|
+
expected: args.expected_artifacts,
|
|
2688
|
+
found: [],
|
|
2689
|
+
missing: [],
|
|
2690
|
+
passed: true,
|
|
2691
|
+
};
|
|
2692
|
+
|
|
2693
|
+
for (const id of args.expected_artifacts) {
|
|
2694
|
+
if (existingIds.has(id)) {
|
|
2695
|
+
artifactCheck.found.push(id);
|
|
2696
|
+
} else {
|
|
2697
|
+
artifactCheck.missing.push(id);
|
|
2698
|
+
artifactCheck.passed = false;
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
report.checks.push(artifactCheck);
|
|
2702
|
+
|
|
2703
|
+
// Check 2: Artifact content valid (get each artifact with reader="orchestrator")
|
|
2704
|
+
const contentCheck = {
|
|
2705
|
+
check: 'artifacts_valid',
|
|
2706
|
+
results: [],
|
|
2707
|
+
passed: true,
|
|
2708
|
+
};
|
|
2709
|
+
|
|
2710
|
+
for (const id of artifactCheck.found) {
|
|
2711
|
+
const artifact = artifactStore.get(id);
|
|
2712
|
+
// Track read as orchestrator
|
|
2713
|
+
artifactStore.trackRead(id, 'orchestrator', artifact?.version);
|
|
2714
|
+
|
|
2715
|
+
if (!artifact) {
|
|
2716
|
+
contentCheck.results.push({ artifactId: id, valid: false, reason: 'Could not read artifact' });
|
|
2717
|
+
contentCheck.passed = false;
|
|
2718
|
+
} else if (!artifact.data || (typeof artifact.data === 'object' && Object.keys(artifact.data).length === 0)) {
|
|
2719
|
+
contentCheck.results.push({ artifactId: id, valid: false, reason: 'Artifact data is empty' });
|
|
2720
|
+
contentCheck.passed = false;
|
|
2721
|
+
} else {
|
|
2722
|
+
contentCheck.results.push({
|
|
2723
|
+
artifactId: id,
|
|
2724
|
+
valid: true,
|
|
2725
|
+
version: artifact.version,
|
|
2726
|
+
publisher: artifact.publisher,
|
|
2727
|
+
summary: artifact.summary || '(no summary)',
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
report.checks.push(contentCheck);
|
|
2732
|
+
|
|
2733
|
+
// Check 3: Workers idle
|
|
2734
|
+
const roster = teamHub.getRoster();
|
|
2735
|
+
const idleCheck = {
|
|
2736
|
+
check: 'workers_idle',
|
|
2737
|
+
results: [],
|
|
2738
|
+
passed: true,
|
|
2739
|
+
};
|
|
2740
|
+
|
|
2741
|
+
const workersToCheck = args.expected_idle
|
|
2742
|
+
? roster.filter(m => args.expected_idle.includes(m.name))
|
|
2743
|
+
: roster;
|
|
2744
|
+
|
|
2745
|
+
for (const member of workersToCheck) {
|
|
2746
|
+
const isIdle = member.status === 'idle';
|
|
2747
|
+
idleCheck.results.push({
|
|
2748
|
+
name: member.name,
|
|
2749
|
+
status: member.status,
|
|
2750
|
+
task: member.task,
|
|
2751
|
+
idle: isIdle,
|
|
2752
|
+
});
|
|
2753
|
+
if (!isIdle) {
|
|
2754
|
+
idleCheck.passed = false;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
report.checks.push(idleCheck);
|
|
2758
|
+
|
|
2759
|
+
// Check 4: Artifact readers verification
|
|
2760
|
+
const readerCheck = {
|
|
2761
|
+
check: 'artifact_readers',
|
|
2762
|
+
results: [],
|
|
2763
|
+
passed: true,
|
|
2764
|
+
};
|
|
2765
|
+
|
|
2766
|
+
if (args.expected_readers) {
|
|
2767
|
+
for (const [artifactId, expectedReaders] of Object.entries(args.expected_readers)) {
|
|
2768
|
+
const reads = artifactStore.getReads(artifactId);
|
|
2769
|
+
const actualReaders = [...new Set(reads.map(r => r.reader))];
|
|
2770
|
+
const missing = expectedReaders.filter(r => !actualReaders.includes(r));
|
|
2771
|
+
|
|
2772
|
+
readerCheck.results.push({
|
|
2773
|
+
artifactId,
|
|
2774
|
+
expectedReaders,
|
|
2775
|
+
actualReaders,
|
|
2776
|
+
missingReaders: missing,
|
|
2777
|
+
allRead: missing.length === 0,
|
|
2778
|
+
});
|
|
2779
|
+
|
|
2780
|
+
if (missing.length > 0) {
|
|
2781
|
+
readerCheck.passed = false;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
} else {
|
|
2785
|
+
// If no expected readers specified, just show who read what
|
|
2786
|
+
for (const id of artifactCheck.found) {
|
|
2787
|
+
const reads = artifactStore.getReads(id);
|
|
2788
|
+
const readers = [...new Set(reads.map(r => r.reader))];
|
|
2789
|
+
readerCheck.results.push({
|
|
2790
|
+
artifactId: id,
|
|
2791
|
+
readers,
|
|
2792
|
+
readCount: reads.length,
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
report.checks.push(readerCheck);
|
|
2797
|
+
|
|
2798
|
+
// Overall pass/fail
|
|
2799
|
+
report.passed = report.checks.every(c => c.passed);
|
|
2800
|
+
|
|
2801
|
+
// Track gate pass for spawn enforcement
|
|
2802
|
+
if (report.passed) {
|
|
2803
|
+
const state = _gateState.get(teamName) || { gateCount: 0, spawnBatchCount: 0, lastSpawnTime: 0 };
|
|
2804
|
+
state.gateCount++;
|
|
2805
|
+
_gateState.set(teamName, state);
|
|
2806
|
+
report._gate_generation = state.gateCount;
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// Action recommendation
|
|
2810
|
+
if (report.passed) {
|
|
2811
|
+
report.recommendation = `ALL CHECKS PASSED. Safe to proceed to ${args.phase_starting}.`;
|
|
2812
|
+
} else {
|
|
2813
|
+
const failures = report.checks.filter(c => !c.passed).map(c => c.check);
|
|
2814
|
+
report.recommendation = `BLOCKED: ${failures.join(', ')} failed. Fix these before proceeding to ${args.phase_starting}.`;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
return textResult(JSON.stringify(report, null, 2));
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
return errorResult(err.message);
|
|
2820
|
+
}
|
|
2555
2821
|
}
|
|
2556
2822
|
|
|
2557
|
-
|
|
2558
|
-
* Append a staleness warning to tool results if the server version is outdated.
|
|
2559
|
-
* Reads package.json from disk on each call to detect post-install version drift.
|
|
2560
|
-
* @param {Object} result - The tool result object
|
|
2561
|
-
* @returns {Object} The result, possibly with a staleness warning appended
|
|
2562
|
-
*/
|
|
2563
|
-
function appendStalenessWarning(result) {
|
|
2823
|
+
function handleTeamReset(args) {
|
|
2564
2824
|
try {
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2825
|
+
if (!args.confirm) {
|
|
2826
|
+
return errorResult('Must pass confirm: true to reset team state. This is destructive and cannot be undone.');
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
const teamName = args.team || 'default';
|
|
2830
|
+
const baseDir = path.join(os.homedir(), '.claude-multi-session', 'team', teamName);
|
|
2831
|
+
|
|
2832
|
+
const summary = {
|
|
2833
|
+
team: teamName,
|
|
2834
|
+
cleared: [],
|
|
2835
|
+
preserved: args.preserve_artifacts || [],
|
|
2836
|
+
};
|
|
2837
|
+
|
|
2838
|
+
// Clear artifacts (except preserved ones)
|
|
2839
|
+
const artifactsDir = path.join(baseDir, 'artifacts');
|
|
2840
|
+
if (fs.existsSync(artifactsDir)) {
|
|
2841
|
+
const indexPath = path.join(artifactsDir, 'index.json');
|
|
2842
|
+
if (fs.existsSync(indexPath)) {
|
|
2843
|
+
try {
|
|
2844
|
+
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
2845
|
+
const preserveSet = new Set(args.preserve_artifacts || []);
|
|
2846
|
+
|
|
2847
|
+
if (preserveSet.size > 0) {
|
|
2848
|
+
// Filter out preserved artifacts
|
|
2849
|
+
const filtered = {};
|
|
2850
|
+
for (const [id, entry] of Object.entries(index)) {
|
|
2851
|
+
if (preserveSet.has(id)) {
|
|
2852
|
+
filtered[id] = entry;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
fs.writeFileSync(indexPath, JSON.stringify(filtered, null, 2));
|
|
2856
|
+
summary.cleared.push(`artifacts (kept ${preserveSet.size} preserved)`);
|
|
2857
|
+
} else {
|
|
2858
|
+
fs.writeFileSync(indexPath, '{}');
|
|
2859
|
+
summary.cleared.push('artifacts');
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
// Clean data directory (version files and reads)
|
|
2863
|
+
const dataDir = path.join(artifactsDir, 'data');
|
|
2864
|
+
if (fs.existsSync(dataDir)) {
|
|
2865
|
+
const artifactDirs = fs.readdirSync(dataDir);
|
|
2866
|
+
for (const dir of artifactDirs) {
|
|
2867
|
+
if (!preserveSet.has(dir)) {
|
|
2868
|
+
const dirPath = path.join(dataDir, dir);
|
|
2869
|
+
// Remove all files in the directory
|
|
2870
|
+
const files = fs.readdirSync(dirPath);
|
|
2871
|
+
for (const file of files) {
|
|
2872
|
+
fs.unlinkSync(path.join(dirPath, file));
|
|
2873
|
+
}
|
|
2874
|
+
fs.rmdirSync(dirPath);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
} catch (e) {
|
|
2879
|
+
summary.cleared.push(`artifacts (error: ${e.message})`);
|
|
2880
|
+
}
|
|
2571
2881
|
}
|
|
2572
2882
|
}
|
|
2573
|
-
|
|
2574
|
-
//
|
|
2883
|
+
|
|
2884
|
+
// Clear contracts
|
|
2885
|
+
const contractsPath = path.join(baseDir, 'contracts.json');
|
|
2886
|
+
if (fs.existsSync(contractsPath)) {
|
|
2887
|
+
fs.writeFileSync(contractsPath, '{}');
|
|
2888
|
+
summary.cleared.push('contracts');
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// Clear roster
|
|
2892
|
+
const rosterPath = path.join(baseDir, 'roster.json');
|
|
2893
|
+
if (fs.existsSync(rosterPath)) {
|
|
2894
|
+
fs.writeFileSync(rosterPath, '{}');
|
|
2895
|
+
summary.cleared.push('roster');
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// Clear messages
|
|
2899
|
+
const messagesDir = path.join(baseDir, 'messages');
|
|
2900
|
+
if (fs.existsSync(messagesDir)) {
|
|
2901
|
+
const files = fs.readdirSync(messagesDir);
|
|
2902
|
+
for (const file of files) {
|
|
2903
|
+
fs.unlinkSync(path.join(messagesDir, file));
|
|
2904
|
+
}
|
|
2905
|
+
summary.cleared.push('messages');
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
// Clear pipelines
|
|
2909
|
+
const pipelinesPath = path.join(baseDir, 'pipelines.json');
|
|
2910
|
+
if (fs.existsSync(pipelinesPath)) {
|
|
2911
|
+
fs.writeFileSync(pipelinesPath, '{}');
|
|
2912
|
+
summary.cleared.push('pipelines');
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// Clear locks
|
|
2916
|
+
const locksDir = path.join(baseDir, 'locks');
|
|
2917
|
+
if (fs.existsSync(locksDir)) {
|
|
2918
|
+
const lockFiles = fs.readdirSync(locksDir);
|
|
2919
|
+
for (const file of lockFiles) {
|
|
2920
|
+
fs.unlinkSync(path.join(locksDir, file));
|
|
2921
|
+
}
|
|
2922
|
+
summary.cleared.push('locks');
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
// Reset in-memory team instances
|
|
2926
|
+
teamHub = null;
|
|
2927
|
+
artifactStore = null;
|
|
2928
|
+
contractStore = null;
|
|
2929
|
+
resolver = null;
|
|
2930
|
+
lineageGraph = null;
|
|
2931
|
+
pipelineEngine = null;
|
|
2932
|
+
snapshotEngine = null;
|
|
2933
|
+
currentTeamName = null;
|
|
2934
|
+
|
|
2935
|
+
// Clear gate enforcement state
|
|
2936
|
+
_gateState.delete(teamName);
|
|
2937
|
+
|
|
2938
|
+
summary.message = `Team "${teamName}" has been reset. ${summary.cleared.length} stores cleared.`;
|
|
2939
|
+
|
|
2940
|
+
return textResult(JSON.stringify(summary, null, 2));
|
|
2941
|
+
} catch (err) {
|
|
2942
|
+
return errorResult(err.message);
|
|
2575
2943
|
}
|
|
2576
|
-
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
// =============================================================================
|
|
2947
|
+
// Result Helpers
|
|
2948
|
+
// =============================================================================
|
|
2949
|
+
|
|
2950
|
+
function textResult(text) {
|
|
2951
|
+
return {
|
|
2952
|
+
content: [{ type: 'text', text: text + getCachedStalenessWarning() }],
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
function errorResult(message) {
|
|
2957
|
+
return {
|
|
2958
|
+
content: [{ type: 'text', text: `Error: ${message}` + getCachedStalenessWarning() }],
|
|
2959
|
+
isError: true,
|
|
2960
|
+
};
|
|
2577
2961
|
}
|
|
2578
2962
|
|
|
2579
2963
|
// =============================================================================
|
|
@@ -2581,10 +2965,18 @@ function appendStalenessWarning(result) {
|
|
|
2581
2965
|
// =============================================================================
|
|
2582
2966
|
|
|
2583
2967
|
/**
|
|
2584
|
-
*
|
|
2968
|
+
* Structured log to stderr (NEVER stdout — stdout is for MCP protocol only).
|
|
2969
|
+
* Includes ISO timestamp and server version for debugging.
|
|
2585
2970
|
*/
|
|
2586
|
-
function log(msg) {
|
|
2587
|
-
|
|
2971
|
+
function log(msg, level = 'info') {
|
|
2972
|
+
const entry = JSON.stringify({
|
|
2973
|
+
ts: new Date().toISOString(),
|
|
2974
|
+
level,
|
|
2975
|
+
server: 'multi-session-mcp',
|
|
2976
|
+
version: LOADED_VERSION,
|
|
2977
|
+
msg,
|
|
2978
|
+
});
|
|
2979
|
+
process.stderr.write(entry + '\n');
|
|
2588
2980
|
}
|
|
2589
2981
|
|
|
2590
2982
|
/**
|
|
@@ -2623,7 +3015,7 @@ async function handleMessage(message) {
|
|
|
2623
3015
|
},
|
|
2624
3016
|
serverInfo: {
|
|
2625
3017
|
name: 'claude-multi-session',
|
|
2626
|
-
version:
|
|
3018
|
+
version: LOADED_VERSION,
|
|
2627
3019
|
},
|
|
2628
3020
|
});
|
|
2629
3021
|
break;
|
|
@@ -2645,9 +3037,7 @@ async function handleMessage(message) {
|
|
|
2645
3037
|
break;
|
|
2646
3038
|
}
|
|
2647
3039
|
try {
|
|
2648
|
-
|
|
2649
|
-
// Append staleness warning if server version is outdated
|
|
2650
|
-
result = appendStalenessWarning(result);
|
|
3040
|
+
const result = await executeTool(params.name, params.arguments || {});
|
|
2651
3041
|
sendResponse(id, result);
|
|
2652
3042
|
} catch (err) {
|
|
2653
3043
|
sendResponse(id, errorResult(err.message));
|
|
@@ -2718,17 +3108,51 @@ function startServer() {
|
|
|
2718
3108
|
process.exit(0);
|
|
2719
3109
|
});
|
|
2720
3110
|
|
|
2721
|
-
//
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
3111
|
+
// Graceful shutdown handler — works on Windows (SIGINT, SIGBREAK) and Unix (SIGTERM)
|
|
3112
|
+
let shuttingDown = false;
|
|
3113
|
+
function gracefulShutdown(signal) {
|
|
3114
|
+
if (shuttingDown) return; // Prevent double-shutdown
|
|
3115
|
+
shuttingDown = true;
|
|
3116
|
+
log(`${signal} received. Graceful shutdown starting...`);
|
|
3117
|
+
|
|
3118
|
+
// Stop accepting new work
|
|
3119
|
+
rl.close();
|
|
3120
|
+
|
|
3121
|
+
// Kill all spawned child sessions
|
|
3122
|
+
try {
|
|
3123
|
+
manager.stopAll();
|
|
3124
|
+
log('All sessions stopped.');
|
|
3125
|
+
} catch (err) {
|
|
3126
|
+
log(`Error stopping sessions: ${err.message}`, 'error');
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// Force exit after 5 second timeout if cleanup hangs
|
|
3130
|
+
const forceTimer = setTimeout(() => {
|
|
3131
|
+
log('Shutdown timeout exceeded. Force exiting.', 'warn');
|
|
3132
|
+
process.exit(1);
|
|
3133
|
+
}, 5000);
|
|
3134
|
+
forceTimer.unref(); // Don't keep process alive just for this timer
|
|
3135
|
+
|
|
2725
3136
|
process.exit(0);
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
3140
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
3141
|
+
|
|
3142
|
+
// Windows-specific: SIGBREAK is sent on Ctrl+Break
|
|
3143
|
+
if (process.platform === 'win32') {
|
|
3144
|
+
process.on('SIGBREAK', () => gracefulShutdown('SIGBREAK'));
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
// Handle uncaught errors gracefully
|
|
3148
|
+
process.on('uncaughtException', (err) => {
|
|
3149
|
+
log(`Uncaught exception: ${err.message}\n${err.stack}`, 'error');
|
|
3150
|
+
gracefulShutdown('uncaughtException');
|
|
2726
3151
|
});
|
|
2727
3152
|
|
|
2728
|
-
process.on('
|
|
2729
|
-
log(
|
|
2730
|
-
|
|
2731
|
-
process.exit(0);
|
|
3153
|
+
process.on('unhandledRejection', (reason) => {
|
|
3154
|
+
log(`Unhandled rejection: ${reason}`, 'error');
|
|
3155
|
+
// Don't shutdown on unhandled rejections — log and continue
|
|
2732
3156
|
});
|
|
2733
3157
|
|
|
2734
3158
|
log('MCP server ready. Waiting for messages...');
|
package/src/prompts.js
CHANGED
|
@@ -295,7 +295,11 @@ function buildDelegatePrompt(task, context, name) {
|
|
|
295
295
|
|
|
296
296
|
You are "${name || 'worker'}" — an autonomous delegated worker session. You were spawned to complete a specific task independently, with no team communication tools. Your only job is to finish this task thoroughly and report back.
|
|
297
297
|
|
|
298
|
-
IMPORTANT: You are operating under safety limits
|
|
298
|
+
IMPORTANT: You are operating under STRICT safety limits. Your session will be AUTO-KILLED without warning if you exceed:
|
|
299
|
+
- **Cost limit:** ~$2.00 USD (default)
|
|
300
|
+
- **Turn limit:** ~50 agent turns (default)
|
|
301
|
+
- **Time limit:** ~5 minutes (default)
|
|
302
|
+
Work efficiently — do not waste turns on unnecessary exploration or over-engineering.
|
|
299
303
|
|
|
300
304
|
=== CRITICAL: MANDATORY WORKFLOW ===
|
|
301
305
|
|
|
@@ -581,29 +585,30 @@ NEVER assume workers will independently agree on conventions. Define them explic
|
|
|
581
585
|
|
|
582
586
|
### Phase Gate: VERIFY Before Spawning
|
|
583
587
|
|
|
584
|
-
=== PHASE GATE CHECKPOINT (
|
|
588
|
+
=== PHASE GATE CHECKPOINT (use phase_gate tool before EVERY team_spawn after Phase 0) ===
|
|
585
589
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
Phase completing: ___ → Phase starting: ___
|
|
589
|
-
|
|
590
|
-
1. artifact_list()
|
|
591
|
-
Expected artifacts: [___]
|
|
592
|
-
All present? YES / NO
|
|
593
|
-
|
|
594
|
-
2. artifact_get({ artifactId: "___", reader: "orchestrator" })
|
|
595
|
-
Content valid and complete? YES / NO
|
|
590
|
+
Instead of manually running 4 separate tool calls, use the \`phase_gate\` tool which does ALL checks in one call:
|
|
596
591
|
|
|
597
|
-
|
|
598
|
-
|
|
592
|
+
\`\`\`
|
|
593
|
+
mcp__multi-session__phase_gate({
|
|
594
|
+
phase_completing: "Phase 0: Foundation",
|
|
595
|
+
phase_starting: "Phase 1: Routes",
|
|
596
|
+
expected_artifacts: ["project-foundation", "shared-conventions"],
|
|
597
|
+
expected_idle: ["setup"],
|
|
598
|
+
expected_readers: { "shared-conventions": ["api-dev", "db-dev"] }
|
|
599
|
+
})
|
|
600
|
+
\`\`\`
|
|
599
601
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
+
The tool automatically:
|
|
603
|
+
1. Checks all expected artifacts exist
|
|
604
|
+
2. Validates artifact content and tracks the read as "orchestrator"
|
|
605
|
+
3. Verifies all previous-phase workers are idle
|
|
606
|
+
4. Confirms expected consumers actually read the artifacts
|
|
602
607
|
|
|
603
|
-
|
|
604
|
-
|
|
608
|
+
Returns a structured pass/fail report with recommendation.
|
|
609
|
+
PROCEED ONLY IF the report says ALL CHECKS PASSED.
|
|
605
610
|
|
|
606
|
-
|
|
611
|
+
**ENFORCED:** \`team_spawn\` will return an error if \`phase_gate\` was not called between spawn batches. The first batch (Phase 0) is free; every subsequent batch requires a passing \`phase_gate\` call first.
|
|
607
612
|
|
|
608
613
|
=== PHASE COUNTING RULE ===
|
|
609
614
|
At the start of planning, count and list your phases explicitly:
|
|
@@ -676,6 +681,8 @@ When all workers are done:
|
|
|
676
681
|
| Workers need to communicate | \`team_spawn\` (has team tools) | \`delegate_task\` (isolated) |
|
|
677
682
|
| Quick one-off task | \`delegate_task\` | \`team_spawn\` |
|
|
678
683
|
| Need safety limits (cost/turns) | \`delegate_task\` | \`team_spawn\` |
|
|
684
|
+
| Verify phase completion | \`phase_gate\` |
|
|
685
|
+
| Clean up between runs | \`team_reset\` |
|
|
679
686
|
|
|
680
687
|
## WHAT GOES WRONG (And How to Avoid It)
|
|
681
688
|
|
|
@@ -912,7 +919,9 @@ IMPORTANT: You are the ORCHESTRATOR. Your job is to PLAN, SPAWN, and MONITOR —
|
|
|
912
919
|
|
|
913
920
|
4.5. **Phase Gate** — Before spawning workers that depend on previous workers' output, VERIFY the dependency artifact exists by calling \`artifact_list()\` and \`artifact_get()\`. Never trust self-reported completion — verify the artifact.
|
|
914
921
|
|
|
915
|
-
5. **Post-Phase Verification** — After each phase completes,
|
|
922
|
+
5. **Post-Phase Verification** — After each phase completes, call \`phase_gate()\` which runs ALL verification checks in one call: confirms artifacts exist, validates content, checks workers are idle, and verifies artifact readers. Only proceed when it reports ALL CHECKS PASSED. If you have N phases, verify N-1 times.
|
|
923
|
+
|
|
924
|
+
**Note:** \`team_spawn\` will return an error if \`phase_gate\` was not called between spawn batches. The first batch (Phase 0) is free; every subsequent batch requires a passing \`phase_gate\` call first.
|
|
916
925
|
|
|
917
926
|
6. **Collect** — When all workers are idle, check \`artifact_list\` for published outputs and summarize results for the user.
|
|
918
927
|
|
|
@@ -973,6 +982,8 @@ Use \`delegate_task\` for SINGLE, isolated tasks that don't need team communicat
|
|
|
973
982
|
| Single isolated task | \`delegate_task\` |
|
|
974
983
|
| Quick one-off task | \`delegate_task\` |
|
|
975
984
|
| Need safety limits (cost/turns) | \`delegate_task\` |
|
|
985
|
+
| Verify phase completion | \`phase_gate\` |
|
|
986
|
+
| Clean up between runs | \`team_reset\` |
|
|
976
987
|
|
|
977
988
|
### Lifecycle: delegate_task → continue_task → finish_task
|
|
978
989
|
|
|
@@ -1053,6 +1064,16 @@ NEVER do these:
|
|
|
1053
1064
|
- Do NOT fix bugs found by one worker — tell that worker to fix them
|
|
1054
1065
|
- Do NOT act as a message router — workers can talk directly via team_ask
|
|
1055
1066
|
- Do NOT keep sending corrections endlessly — if 3 corrections don't work, abort and re-spawn
|
|
1067
|
+
|
|
1068
|
+
### Resetting Between Runs
|
|
1069
|
+
Use \`team_reset\` to clean up all team state between orchestration runs:
|
|
1070
|
+
\`\`\`
|
|
1071
|
+
mcp__multi-session__team_reset({ confirm: true })
|
|
1072
|
+
\`\`\`
|
|
1073
|
+
This clears artifacts, contracts, roster, messages, and pipelines. Optionally preserve specific artifacts:
|
|
1074
|
+
\`\`\`
|
|
1075
|
+
mcp__multi-session__team_reset({ confirm: true, preserve_artifacts: ["shared-conventions"] })
|
|
1076
|
+
\`\`\`
|
|
1056
1077
|
`;
|
|
1057
1078
|
|
|
1058
1079
|
const ORCHESTRATOR_WHEN_TO_USE = `
|