@heretyc/subagent-mcp 2.8.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/directives/carryover-claude.md +17 -17
- package/directives/carryover-codex.md +17 -17
- package/directives/off-turn-reminder.md +1 -1
- package/directives/orchestration-claude.md +14 -21
- package/directives/orchestration-codex.md +13 -22
- package/directives/reminder-off-claude.md +9 -12
- package/directives/reminder-off-codex.md +9 -12
- package/directives/reminder-on.md +15 -9
- package/dist/hooks/orchestration-claude-pretool.js +41 -0
- package/dist/hooks/orchestration-claude.js +13 -1
- package/dist/index.js +82 -23
- package/dist/init.js +230 -0
- package/dist/orchestration/hook-core.js +7 -7
- package/dist/orchestration/liveness.js +32 -0
- package/dist/orchestration/pretool.js +95 -0
- package/dist/routing-table.json +1725 -533
- package/dist/routing.js +7 -3
- package/dist/setup.js +48 -29
- package/package.json +2 -2
- package/scripts/postinstall.mjs +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ MCP server that launches and manages locally installed `claude` and `codex` CLI
|
|
|
17
17
|
- Ultracode mode for Opus 4.8 -- headless activation via `--settings {"ultracode":true}` (see [docs/usage.md](docs/usage.md))
|
|
18
18
|
- Cross-platform exe resolution (Windows: npm-prefix .exe paths; macOS/Linux: PATH + Homebrew/usr-local fallbacks); immediate `taskkill /t /f` (Windows) / `SIGKILL` (POSIX) force-kill; no graceful shutdown period
|
|
19
19
|
- stdio MCP transport; built with `@modelcontextprotocol/sdk` + `zod`
|
|
20
|
-
- `orchestration-mode` tool — toggles
|
|
20
|
+
- `orchestration-mode` tool — toggles orchestrator directives injected by bundled Claude Code / Codex hooks; Claude also gets a deterministic `PreToolUse` gate (Desktop hosts toggle but do not inject); see [docs/spec/orchestration-mode/_INDEX.md](docs/spec/orchestration-mode/_INDEX.md)
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -39,11 +39,16 @@ npm install -g @heretyc/subagent-mcp
|
|
|
39
39
|
subagent-mcp setup
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
`setup` detects which vendors are present, registers the MCP server, and writes
|
|
42
|
+
`setup` detects which vendors are present, registers the MCP server, and writes orchestration-mode hooks. Idempotent — safe to re-run after updates. Pass `--dry-run` to preview.
|
|
43
|
+
|
|
44
|
+
For consumer projects, run `subagent-mcp init --root /path/to/project` to upsert
|
|
45
|
+
the managed invariant block into `AGENTS.md`, `CLAUDE.md`, and `GEMINI.md`.
|
|
46
|
+
Use `--dry-run` to preview, `--remove` to uninstall the block, and `--force`
|
|
47
|
+
only if you intentionally run inside this source repo.
|
|
43
48
|
|
|
44
49
|
After setup, restart your Claude Code or Codex session. On Codex, run `/hooks` and trust the new hook.
|
|
45
50
|
|
|
46
|
-
**Updating:** `
|
|
51
|
+
**Updating:** `subagent-mcp update && subagent-mcp setup`
|
|
47
52
|
|
|
48
53
|
For manual wiring, developer install from source, Gemini CLI, and Claude Desktop, see [docs/registration.md](docs/registration.md).
|
|
49
54
|
|
|
@@ -69,8 +74,14 @@ For manual wiring, developer install from source, Gemini CLI, and Claude Desktop
|
|
|
69
74
|
| `coding` | bounded runnable code artifact, one-pass (implement, test, refactor) |
|
|
70
75
|
| `knowledge_synthesis` | novel integrated prose over sources (synthesize, summarize, draft) |
|
|
71
76
|
| `mechanical` | deterministic single-pass transform, exact-match checkable (grep, rename, reformat) |
|
|
77
|
+
| `prompt_engineering` | designed/optimized prompt or prompt-system steering an LLM/agent (composite-inferred) |
|
|
78
|
+
| `vulnerability_research` | discovery + PoC of a novel vulnerability (composite-inferred) |
|
|
79
|
+
| `molecular_biology` | reasoned molecular/computational-biology result over sequences, structures, or -omics data (composite-inferred) |
|
|
80
|
+
| `ml_accelerator_design` | hardware/software design for ML acceleration — dataflow, kernel, roofline (composite-inferred) |
|
|
72
81
|
| `fallback_default` | no category matches with confidence; prefer splitting work instead |
|
|
73
82
|
|
|
83
|
+
The last four are **composite-inferred**: they carry no dedicated benchmark and their routing competency is composed from parent categories rather than measured directly.
|
|
84
|
+
|
|
74
85
|
**Atomic-split guidance:** if you are unsure which category fits, do NOT submit one large amorphous task. Break the work into smaller atomic steps each mapping to a single category and launch one agent per step.
|
|
75
86
|
|
|
76
87
|
---
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ORCHESTRATION MODE
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</ORCHESTRATION-
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
Sub-agent sessions (first prompt began "<this is a request from a parent process>"): ignore this block.
|
|
3
|
+
|
|
4
|
+
ORCHESTRATION MODE carried over ON from a PRIOR session of this project
|
|
5
|
+
(persists until disabled with permission). Not user-enabled THIS session.
|
|
6
|
+
|
|
7
|
+
THIS turn, ONCE:
|
|
8
|
+
1. NOTIFY user it carried over.
|
|
9
|
+
2. ASK keep ON? via AskUserQuestion.
|
|
10
|
+
3. ADVISE fit: long-horizon context-filling → keep ON; bounded, interactive, or
|
|
11
|
+
core-bound to main-session-only MCP → propose OFF.
|
|
12
|
+
|
|
13
|
+
Until answered, the mandates below still bind. Declines → orchestration-mode
|
|
14
|
+
enabled:false. NEVER disable on own initiative. After answer: handshake done —
|
|
15
|
+
do not re-raise.
|
|
16
|
+
|
|
17
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ORCHESTRATION MODE
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</ORCHESTRATION-
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
Sub-agent sessions (first prompt began "<this is a request from a parent process>"): ignore this block.
|
|
3
|
+
|
|
4
|
+
ORCHESTRATION MODE carried over ON from a PRIOR session of this project
|
|
5
|
+
(persists until disabled with permission). Not user-enabled THIS session.
|
|
6
|
+
|
|
7
|
+
THIS turn, ONCE:
|
|
8
|
+
1. NOTIFY user it carried over.
|
|
9
|
+
2. ASK keep ON? via request-user-input.
|
|
10
|
+
3. ADVISE fit: long-horizon context-filling → keep ON; bounded, interactive, or
|
|
11
|
+
core-bound to main-session-only MCP → propose OFF.
|
|
12
|
+
|
|
13
|
+
Until answered, the mandates below still bind. Declines → orchestration-mode
|
|
14
|
+
enabled:false. NEVER disable on own initiative. After answer: handshake done —
|
|
15
|
+
do not re-raise.
|
|
16
|
+
|
|
17
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
<ORCHESTRATION-INVARIANT> SUBAGENT MANDATES bind THIS turn. 5-CALL RULE APPLIES. SOLE CHANNEL: launch sub-agents ONLY via subagent-mcp. OFF: ask before enabling orchestration. ON replies must start: route: delegate|inline - <reason>. </ORCHESTRATION-INVARIANT>
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
User tool-pin re-partitions work; does not suspend mode.
|
|
16
|
-
|
|
17
|
-
IPC = temp scratch files ONLY. Windows: %TEMP%. POSIX: /tmp.
|
|
18
|
-
Full model + governance: server MCP instructions.
|
|
19
|
-
DISABLE: never on own initiative. Propose via AskUserQuestion only.
|
|
20
|
-
|
|
21
|
-
</SUB-AGENT-INVARIANT>
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
ORCHESTRATION ON. Delegate-default through subagent-mcp; route EVERY sub-agent launch ONLY via subagent-mcp launch_agent. Inline ONLY main-session-only-capability steps (non-inheritable MCP, interactive/consent, verify reruns); temp scratch-file IPC allowed. Repo/system safety rules outrank this.
|
|
3
|
+
- Ruthlessly preserve orchestrator context, NO EXCEPTIONS.
|
|
4
|
+
- Subagents ask questions through the orchestrator; answers
|
|
5
|
+
return to subagents.
|
|
6
|
+
- Sub-agents use %TEMP%, /tmp/, /TEMP/ for all IPC.
|
|
7
|
+
- NEVER DISABLE ORCHESTRATION: never on own initiative. Propose via AskUserQuestion only; disable only after explicit user permission.
|
|
8
|
+
Orchestration Steps:
|
|
9
|
+
1. Map out work before orchestrating.
|
|
10
|
+
2. Decompose into phases which have atomic tasks that are separate subagents. Phases = understand → design → implement → review.
|
|
11
|
+
3. Execute the phases, dispatching sub-agents and between EACH phase dispatch a quality-review sub-agent to validate all work done.
|
|
12
|
+
|
|
13
|
+
- For Sub-agent sessions ONLY (first prompt began "<this is a request from a parent process>" or env SUBAGENT_MCP_SUBAGENT=1): ignore this block.
|
|
14
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
CONFLICT ORDER: safety-scope > user instruction this turn > delegate-default.
|
|
16
|
-
User tool-pin re-partitions work; does not suspend mode.
|
|
17
|
-
|
|
18
|
-
IPC = temp scratch files ONLY. Windows: %TEMP%. POSIX: /tmp.
|
|
19
|
-
Full model + governance: server MCP instructions.
|
|
20
|
-
DISABLE: never on own initiative. Propose via request-user-input only.
|
|
21
|
-
|
|
22
|
-
</SUB-AGENT-INVARIANT>
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
SCOPE: this session's first prompt began "<this is a request from a parent process>" or env SUBAGENT_MCP_SUBAGENT=1? Sub-agent session — this ORCHESTRATION-INVARIANT does NOT apply; SKIP this block.
|
|
3
|
+
ORCHESTRATION ON. Delegate-default through subagent-mcp; route EVERY sub-agent launch ONLY via subagent-mcp launch_agent. Inline ONLY main-session-only-capability steps (non-inheritable MCP, interactive/consent, verify reruns); temp scratch-file IPC allowed. Repo/system safety rules outrank this.
|
|
4
|
+
- Ruthlessly preserve orchestrator context, NO EXCEPTIONS.
|
|
5
|
+
- Subagents ask questions through the orchestrator; answers
|
|
6
|
+
return to subagents.
|
|
7
|
+
- Sub-agents use %TEMP%, /tmp/, /TEMP/ for all IPC.
|
|
8
|
+
- NEVER DISABLE ORCHESTRATION: never on own initiative. Propose via request-user-input only; disable only after explicit user permission.
|
|
9
|
+
Orchestration Steps:
|
|
10
|
+
1. Map out work before orchestrating.
|
|
11
|
+
2. Decompose into phases which have atomic tasks that are separate subagents. Phases = understand → design → implement → review.
|
|
12
|
+
3. Execute the phases, dispatching sub-agents and between EACH phase dispatch a quality-review sub-agent to validate all work done.
|
|
13
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
subagent-mcp
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
without ever considering orchestration. Sub-agent sessions: ignore this block.
|
|
11
|
-
|
|
12
|
-
</ORCHESTRATION-REMINDER-INVARIANT>
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
ZERO EXCEPTIONS SUBAGENT MANDATES:
|
|
3
|
+
5-CALL RULE: >5 tool calls needed for this request (all turns, no reset), or >5 already spent? Count reads, greps, test runs. If yes do NOT grind on inline - STOP and ask via AskUserQuestion whether to switch orchestration ON. That STOP+ask satisfies this rule; if the user declines, inline is authorized for THIS request - do not re-ask it.
|
|
4
|
+
Why this exists: agents grind through large work without ever considering orchestration.
|
|
5
|
+
ORCHESTRATION OFF - still binding:
|
|
6
|
+
INVARIANT ZERO EXCEPTIONS SOLE CHANNEL: ANY sub-agent launch goes ONLY via the subagent-mcp; harness-native Task/Agent tools are FORBIDDEN while this server is connected.
|
|
7
|
+
Advisory: At all times, delegating via subagent-mcp auto routing is advisable - do not assume your training data is still current.
|
|
8
|
+
For Sub-agent sessions ONLY (first prompt began "<this is a request from a parent process>" or env SUBAGENT_MCP_SUBAGENT=1): ignore this block.
|
|
9
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
subagent-mcp
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
without ever considering orchestration. Sub-agent sessions: ignore this block.
|
|
11
|
-
|
|
12
|
-
</ORCHESTRATION-REMINDER-INVARIANT>
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
ZERO EXCEPTIONS SUBAGENT MANDATES:
|
|
3
|
+
5-CALL RULE: >5 tool calls needed for this request (all turns, no reset), or >5 already spent? Count reads, greps, test runs. If yes do NOT grind on inline - STOP and ask via request-user-input whether to switch orchestration ON. That STOP+ask satisfies this rule; if the user declines, inline is authorized for THIS request - do not re-ask it.
|
|
4
|
+
Why this exists: agents grind through large work without ever considering orchestration.
|
|
5
|
+
ORCHESTRATION OFF - still binding:
|
|
6
|
+
INVARIANT ZERO EXCEPTIONS SOLE CHANNEL: ANY sub-agent launch goes ONLY via the subagent-mcp; harness-native Task/Agent tools are FORBIDDEN while this server is connected.
|
|
7
|
+
Advisory: At all times, delegating via subagent-mcp auto routing is advisable - do not assume your training data is still current.
|
|
8
|
+
For Sub-agent sessions ONLY (first prompt began "<this is a request from a parent process>" or env SUBAGENT_MCP_SUBAGENT=1): ignore this block.
|
|
9
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
<ORCHESTRATION-INVARIANT>
|
|
2
|
+
ORCHESTRATION ON. Route EVERY sub-agent launch ONLY via subagent-mcp launch_agent. Inline ONLY main-session-only-capability steps; temp scratch-file IPC allowed. Repo/system safety rules outrank this.
|
|
3
|
+
- Delegate-default. 5-CALL RULE is satisfied by delegation.
|
|
4
|
+
EVERY reply starts: route: delegate|inline - <reason>
|
|
5
|
+
- Ruthlessly preserve orchestrator context, NO EXCEPTIONS.
|
|
6
|
+
- Subagents ask questions through the orchestrator; answers
|
|
7
|
+
return to subagents.
|
|
8
|
+
- Sub-agents use %TEMP%, /tmp/, /TEMP/ for all IPC.
|
|
9
|
+
Orchestration Steps:
|
|
10
|
+
1. Map out work before orchestrating.
|
|
11
|
+
2. Decompose into phases which have atomic tasks that are separate subagents. Phases = understand → design → implement → review.
|
|
12
|
+
3. Execute the phases, dispatching sub-agents and between EACH phase dispatch a quality-review sub-agent to validate all work done.
|
|
13
|
+
|
|
14
|
+
- For Sub-agent sessions ONLY (first prompt began "<this is a request from a parent process>" or env SUBAGENT_MCP_SUBAGENT=1): ignore this block.
|
|
15
|
+
</ORCHESTRATION-INVARIANT>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { runClaudePreTool, } from "../orchestration/pretool.js";
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code PreToolUse hook entry. Enforces deterministic subagent-mcp
|
|
6
|
+
* routing only while the server heartbeat is fresh; stale/missing heartbeat is
|
|
7
|
+
* fail-open so a broken MCP server never traps the user.
|
|
8
|
+
*/
|
|
9
|
+
function readStdin() {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
let data = "";
|
|
12
|
+
process.stdin.setEncoding("utf8");
|
|
13
|
+
process.stdin.on("data", (chunk) => {
|
|
14
|
+
data += chunk;
|
|
15
|
+
});
|
|
16
|
+
process.stdin.on("end", () => resolve(data));
|
|
17
|
+
process.stdin.on("error", () => resolve(data));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function main() {
|
|
21
|
+
let payload = {};
|
|
22
|
+
try {
|
|
23
|
+
const raw = await readStdin();
|
|
24
|
+
if (raw.trim()) {
|
|
25
|
+
payload = JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
payload = {};
|
|
30
|
+
}
|
|
31
|
+
const result = runClaudePreTool(payload, process.env);
|
|
32
|
+
if (result) {
|
|
33
|
+
process.stdout.write(JSON.stringify(result));
|
|
34
|
+
}
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
const isMain = process.argv[1] !== undefined &&
|
|
38
|
+
import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
39
|
+
if (isMain) {
|
|
40
|
+
void main();
|
|
41
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import { countJsonlType, runHook, } from "../orchestration/hook-core.js";
|
|
4
|
+
import { resetToolCount } from "../orchestration/pretool.js";
|
|
4
5
|
/**
|
|
5
6
|
* Claude Code UserPromptSubmit hook entry. Reads the JSON payload from stdin,
|
|
6
7
|
* runs the provider-agnostic core with the Claude adapter, and writes the
|
|
@@ -47,6 +48,17 @@ export const claudeAdapter = {
|
|
|
47
48
|
reminderOnFile: "reminder-on.md",
|
|
48
49
|
reminderOffFile: "reminder-off-claude.md",
|
|
49
50
|
};
|
|
51
|
+
export function runClaudeHook(payload, env, adapter = claudeAdapter) {
|
|
52
|
+
try {
|
|
53
|
+
if (!adapter.isSubagent(payload, env)) {
|
|
54
|
+
resetToolCount(payload);
|
|
55
|
+
}
|
|
56
|
+
return runHook(payload, env, adapter);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
50
62
|
function readStdin() {
|
|
51
63
|
return new Promise((resolve) => {
|
|
52
64
|
let data = "";
|
|
@@ -71,7 +83,7 @@ async function main() {
|
|
|
71
83
|
}
|
|
72
84
|
let out = "";
|
|
73
85
|
try {
|
|
74
|
-
out =
|
|
86
|
+
out = runClaudeHook(payload, process.env, claudeAdapter);
|
|
75
87
|
}
|
|
76
88
|
catch {
|
|
77
89
|
out = "";
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { spawn, spawnSync, execSync } from "child_process";
|
|
6
|
-
import { unlinkSync, existsSync, realpathSync, readFileSync } from "node:fs";
|
|
6
|
+
import { unlinkSync, existsSync, realpathSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
import { isAbsolute, basename, join, dirname } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
9
10
|
import { pathToFileURL } from "url";
|
|
10
11
|
import { buildCommand } from "./effort.js";
|
|
11
12
|
import { resolveExeFor } from "./platform.js";
|
|
@@ -17,6 +18,7 @@ import { loadRoutingTable, buildCandidates, validatePresence, TASK_CATEGORIES, A
|
|
|
17
18
|
import { createDeadlockWindow } from "./deadlock.js";
|
|
18
19
|
import { createRulesetGate, RULESET_HARD_FAIL_MSG, } from "./ruleset.js";
|
|
19
20
|
import * as orchestrationMarker from "./orchestration/marker.js";
|
|
21
|
+
import { startLivenessHeartbeat } from "./orchestration/liveness.js";
|
|
20
22
|
const agents = new Map();
|
|
21
23
|
const MAX_CLAUDE = 5;
|
|
22
24
|
const MAX_CODEX = 5;
|
|
@@ -45,7 +47,7 @@ const SPAWN_GRACE_MS = (() => {
|
|
|
45
47
|
// so the handler-validation test can exercise them without importing this entry
|
|
46
48
|
// module (which would open the stdio transport).
|
|
47
49
|
// Caveman self-classification gloss for the task_category param (tool-description.md).
|
|
48
|
-
const TASK_CATEGORY_GLOSS = "REQUIRED. Task shape -> routing category (
|
|
50
|
+
const TASK_CATEGORY_GLOSS = "REQUIRED. Task shape -> routing category (server picks best model for it). Pick ONE: math_proof: deliverable=proof/derivation/formally-checkable result; proof IS deliverable; deductive step-validity under axioms; verified by proof-checker not tests. security_review: deliverable=security verdict/threat-assessment/demonstrated-exploit; adversarial reasoning over attack surface; vuln, auth/authz, crypto, exploitability. debugging: deliverable=verified fix/root-cause; ONLY observed failure (error, crash, red test, regression, flake) preconditions work; done when symptom resolved. quality_review: deliverable=evaluative verdict on existing NON-security artifact, NO observed failure; review diff/PR, compare A-vs-B, validate-vs-spec; never self-review. architecture: deliverable=cross-module design/plan, NO code, NO execution loop; system structure, interface/migration strategy, decompose-into-tasks; >2 files or public API. agentic_execution: deliverable=target end-state via iterate in mutating env (act/observe/adapt loop); run/deploy/provision/browse, tool/function-call, iterate-until-tests-pass. data_analysis: deliverable=empirical finding/model ABOUT structured dataset; query/SQL/dataframe answer, statistic, fit-model-report-drivers; finding scored even if code runs. coding: deliverable=bounded runnable code artifact, one-pass; implement function/module/feature/script, write tests, single-module refactor; compiles/passes-tests. knowledge_synthesis: deliverable=novel integrated prose over sources; synthesize/summarize/translate/draft/explain-across-files; verified by faithfulness/coherence not exact-match. mechanical: deliverable=deterministic single-pass transform/leaf op, exact-match checkable; find/grep/list/rename/reformat/format-convert/extract-to-fixed-schema; minimal reasoning. prompt_engineering: deliverable=designed/optimized prompt or prompt-system steering an LLM/agent; author/refine/eval instructions; system prompt, few-shot, template, prompt rubric; comp-infer parents knowledge_synthesis+coding+quality_review; no direct benchmark. vulnerability_research: deliverable=NOVEL vuln discovery/root-cause/PoC, NOT broad CVE summary; find flaw, root-cause, build PoC; fuzzing, reverse-engineer, exploit primitive; comp-infer parents security_review+debugging+coding; no direct benchmark. molecular_biology: deliverable=reasoned molecular/computational-biology result; sequences, structures, pathways, -omics data; comp-infer parents knowledge_synthesis+data_analysis+math_proof; no direct benchmark. ml_accelerator_design: deliverable=hardware/software design for ML acceleration; dataflow, tiling, memory hierarchy, kernel, roofline; comp-infer parents architecture+coding+math_proof; no direct benchmark. fallback_default: no category matches with confidence (under-specified/mixed/tied); read-only; PREFER splitting work into smaller atomic steps each mapping to one category.";
|
|
49
51
|
function errorResult(text) {
|
|
50
52
|
return { content: [{ type: "text", text }], isError: true };
|
|
51
53
|
}
|
|
@@ -114,13 +116,16 @@ reconcileInterval.unref();
|
|
|
114
116
|
// initialize (per the MCP spec the initialize result has an `instructions`
|
|
115
117
|
// field) rather than re-injecting it on every turn. The bundled per-turn hook
|
|
116
118
|
// injects only a small compact reminder; this is the durable, full explanation.
|
|
117
|
-
const ORCHESTRATION_INSTRUCTIONS = "ORCHESTRATION MODE (orchestration-mode tool). WHAT: per-project toggle for LONG-HORIZON work that would fill the context window
|
|
119
|
+
const ORCHESTRATION_INSTRUCTIONS = "BINDING IN BOTH MODES - SOLE CHANNEL: while this server is connected, EVERY sub-agent launch goes through launch_agent; harness Task/Agent tools AND any other spawn path (shell claude -p/codex exec, skills) FORBIDDEN. 5-CALL RULE: >5 tool calls needed for the request (all turns, no reset), or >5 already spent? If yes you MUST use orchestration mode + this server; OFF -> ask the user before enabling, a decline authorizes inline for that request. ORCHESTRATION MODE (orchestration-mode tool). WHAT: per-project toggle for LONG-HORIZON work that would fill the context window inline. OPERATING MODEL: when ON you are a delegate-default orchestrator: decompose, DELEGATE/offload; EVERY reply starts: route: delegate|inline - <reason>. MUST DELEGATE/OFFLOAD: pure compute; any payload >50KB or >200 lines via temp scratch-file path handoff (%TEMP% / /tmp); mixed = SPLIT (one MCP-bound step != whole-task inline); keep the orchestrator context lean. INLINE BY RIGHT - the ONLY exemption: steps bound to main-session-only capability (non-inheritable MCP tools, interactive/consent tools, verify loops = re-run existing checks); state which and why. CONFLICT ORDER: safety-scope > user instruction this turn > 5-CALL RULE/delegate-default. User tool-pin re-partitions work; never suspends mode. PERSISTENCE: per-project marker; survives restarts/sessions until disabled with user permission. CARRYOVER: inherited-ON gets a notice ONCE per marker; you MUST tell the user it auto-activated, ask keep ON?, advise fit. DISABLE: never on own initiative. You MAY propose it on task-fit mismatch (bounded/interactive/MCP-bound): explain WHAT+WHY, ask via AskUserQuestion (Claude) / request-user-input (Codex); only explicit approval may call orchestration-mode enabled:false. Declined -> continue under inline-by-right; ask once per topic, never re-nag. Per-turn injection: CLI hosts with bundled hook only; desktop hosts toggle the marker but inject nothing.";
|
|
120
|
+
const SUBAGENT_INSTRUCTIONS = "SUB-AGENT SESSION: you are a child process launched by subagent-mcp. Follow the parent prompt. Do not treat yourself as the orchestrator, do not re-trigger orchestration carryover, and do not launch further sub-agents unless the parent prompt explicitly assigns that.";
|
|
118
121
|
const server = new McpServer({
|
|
119
122
|
name: "subagent-mcp",
|
|
120
123
|
version: "2.3.9",
|
|
121
124
|
description: "Spawns the LOCALLY INSTALLED `claude` and `codex` CLI binaries as child processes. Does NOT call the Anthropic or OpenAI HTTP APIs directly (no API keys, no SDK) and there are no plans to — all model access is via the local CLIs.",
|
|
122
125
|
}, {
|
|
123
|
-
instructions:
|
|
126
|
+
instructions: process.env.SUBAGENT_MCP_SUBAGENT === "1"
|
|
127
|
+
? SUBAGENT_INSTRUCTIONS
|
|
128
|
+
: ORCHESTRATION_INSTRUCTIONS,
|
|
124
129
|
});
|
|
125
130
|
// Best-effort removal of a candidate's temp ultracode settings file after a
|
|
126
131
|
// LAUNCH-TIME failure (the agentState is never registered, so the close handler
|
|
@@ -389,14 +394,14 @@ function sameTriples(a, b) {
|
|
|
389
394
|
return a.every((c, i) => c.provider === b[i].provider && c.model === b[i].model && c.effort === b[i].effort);
|
|
390
395
|
}
|
|
391
396
|
// Tool 1: launch_agent
|
|
392
|
-
server.tool("launch_agent", "Spawn a sub-agent. AUTO MODE (
|
|
397
|
+
server.tool("launch_agent", "Spawn a sub-agent. AUTO MODE (mandatory first attempt unless an override is licensed below): pass only `prompt` + `task_category` and NO overrides; the server picks the best provider/model/effort for that category from its routing table, launches the top candidate, and silently falls back to the next-best on launch failure. `provider`/`model`/`effort` are overrides — licensed on 1st/2nd attempts ONLY when the task verifiably requires a specific capability; STATE that capability when overriding; if you pass `model` you must also pass `provider`, and if you pass `effort` you must pass both `provider` and `model`. SOLE CHANNEL: while this server is connected this tool is the ONLY sanctioned way to spawn sub-agents, in BOTH orchestration states — harness-native Task/Agent tools are FORBIDDEN for sub-agent launches. PROMPT RULE: the FIRST line of every `prompt` MUST be \"<this is a request from a parent process>\" (sub-agent self-identification). Unsure which task_category fits? Don't submit one amorphous task — SPLIT into atomic steps that each map to a single category, one agent per step. ultracode effort is Opus-4.8+ only (induced via a temp `--settings {\"ultracode\":true}` file; the CLI rejects `--effort ultracode`). Each sub-agent is a separate claude/codex CLI child that does NOT inherit this session's MCP servers; children run with env SUBAGENT_MCP_SUBAGENT=1 so the orchestration hooks skip them (they are not orchestrators and don't re-trigger carryover). Launch returns status `processing` (alive); a later `stalled` is alive-but-quiet (thinking or awaiting a temp-file handoff), NOT dead — wait or re-poll, don't kill (see poll_agent). DEADLOCK RULE: you MUST ALWAYS set `deadlock=true` when 2 launch attempts for the SAME atomic task have already failed or been unsatisfactory (the 3rd attempt onward; re-wording or re-splitting the prompt does NOT make it a different task), and NEVER otherwise — from the 3rd attempt deadlock outranks any capability override: drop provider/model/effort.", {
|
|
393
398
|
task_category: z.enum(TASK_CATEGORIES).describe(TASK_CATEGORY_GLOSS),
|
|
394
399
|
prompt: z.string().min(1),
|
|
395
400
|
provider: z.enum(["claude", "codex"]).optional(),
|
|
396
401
|
model: z.enum(["haiku", "sonnet", "opus", "opus-4-8", "gpt-5.5"]).optional(),
|
|
397
402
|
effort: z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]).optional(),
|
|
398
403
|
cwd: z.string().optional(),
|
|
399
|
-
deadlock: z.boolean().optional().describe("MANDATE: ALWAYS set deadlock=true when, and ONLY when,
|
|
404
|
+
deadlock: z.boolean().optional().describe("MANDATE: ALWAYS set deadlock=true when, and ONLY when, 2 launch attempts for the SAME atomic task have already failed or been unsatisfactory — the 3rd attempt onward. Re-wording the prompt does NOT make it a different task; splitting a failed task does NOT reset attempts for its unchanged parts; re-launching for the same deliverable means the prior attempt COUNTS as failed/unsatisfactory ('partial progress' is not an exemption). NEVER set it on a 1st or 2nd attempt, NEVER for a different task, NEVER speculatively. Auto mode only: cannot be combined with provider/model/effort — from the 3rd attempt deadlock outranks any capability override, so drop those params. Passing false is identical to omitting it."),
|
|
400
405
|
}, async (params) => {
|
|
401
406
|
const { task_category, provider, model, effort, prompt, deadlock } = params;
|
|
402
407
|
const agentCwd = params.cwd || process.cwd();
|
|
@@ -858,7 +863,7 @@ server.tool("wait", "Blocks until one or more sub-agents reach a terminal state
|
|
|
858
863
|
};
|
|
859
864
|
});
|
|
860
865
|
// Tool 7: orchestration-mode
|
|
861
|
-
server.tool("orchestration-mode", "Toggle or query per-project ORCHESTRATION MODE. `enabled`: true = ON, false = OFF, omit = query current state. The FULL operating model + governance is carried in this server's MCP `instructions` (read once at initialize) — this is the operational summary only; do not act on the mode without that detail. WHAT: a per-project toggle for LONG-HORIZON work that would fill the context window if run to completion inline; when ON, act as an orchestrator with delegate-default, but steps bound to main-session-only capability stay INLINE BY RIGHT (state which + why). PERSISTENCE: a per-project marker keyed by cwd; absence of the marker = OFF = no injection; once ON it persists across restarts/sessions until a permitted disable (it does NOT reset on a new session). CARRYOVER: if ON was inherited from a PRIOR session (provenance = carried-over, not user-enabled this session), the bundled hook prepends a ONE-TIME notice (once per marker, never per turn) — you MUST then notify the user it auto-activated and confirm whether to keep it ON. DISABLE: never on your own initiative; you MAY PROPOSE turning it OFF on task-fit mismatch, but only EXPLICIT user permission (AskUserQuestion on Claude, request-user-input on Codex) may set enabled:false. Per-turn injection fires only in CLI hosts that load the bundled hook; desktop hosts toggle the marker but inject nothing (documented degradation).", {
|
|
866
|
+
server.tool("orchestration-mode", "Toggle or query per-project ORCHESTRATION MODE. `enabled`: true = ON, false = OFF, omit = query current state. SOLE CHANNEL: the subagent MCP is the ONLY sanctioned channel for launching sub-agents whether this mode is ON or OFF, and the 5-CALL RULE (>5 tool calls needed for the request — all turns, no reset — or >5 already spent → ON: delegate via the subagent MCP; OFF: ask the user before enabling) binds in BOTH states — toggling OFF lifts neither obligation. The FULL operating model + governance is carried in this server's MCP `instructions` (read once at initialize) — this is the operational summary only; do not act on the mode without that detail. WHAT: a per-project toggle for LONG-HORIZON work that would fill the context window if run to completion inline; when ON, act as an orchestrator with delegate-default, but steps bound to main-session-only capability stay INLINE BY RIGHT (state which + why). PERSISTENCE: a per-project marker keyed by cwd; absence of the marker = OFF = no injection; once ON it persists across restarts/sessions until a permitted disable (it does NOT reset on a new session). CARRYOVER: if ON was inherited from a PRIOR session (provenance = carried-over, not user-enabled this session), the bundled hook prepends a ONE-TIME notice (once per marker, never per turn) — you MUST then notify the user it auto-activated and confirm whether to keep it ON. DISABLE: never on your own initiative; you MAY PROPOSE turning it OFF on task-fit mismatch, but only EXPLICIT user permission (AskUserQuestion on Claude, request-user-input on Codex) may set enabled:false. Per-turn injection fires only in CLI hosts that load the bundled hook; desktop hosts toggle the marker but inject nothing (documented degradation).", {
|
|
862
867
|
enabled: z.boolean().optional(),
|
|
863
868
|
}, async (params) => {
|
|
864
869
|
const cwd = process.cwd();
|
|
@@ -898,22 +903,26 @@ if (isMain) {
|
|
|
898
903
|
"",
|
|
899
904
|
" (no command) start the MCP stdio server (how vendor CLIs run it)",
|
|
900
905
|
" setup [--dry-run] wire Claude Code CLI / Codex CLI (--dry-run: preview only)",
|
|
906
|
+
" init, --init [flags]",
|
|
907
|
+
" upsert project instruction-file invariant blocks",
|
|
908
|
+
" flags: --dry-run --remove --force --root <dir> --files <csv> --copilot --cursor",
|
|
901
909
|
" doctor check install and wiring health",
|
|
902
|
-
" --update
|
|
903
|
-
" --version, -v
|
|
904
|
-
"
|
|
910
|
+
" update, --update update to the latest release (npm install -g)",
|
|
911
|
+
" version, --version, -v",
|
|
912
|
+
" print the installed version",
|
|
913
|
+
" help, --help, -h show this help",
|
|
905
914
|
].join("\n");
|
|
906
915
|
// dist/index.js -> ../package.json (the installed package manifest).
|
|
907
916
|
const readPkg = () => JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
908
|
-
if (arg === "--version" || arg === "-v") {
|
|
917
|
+
if (arg === "version" || arg === "--version" || arg === "-v") {
|
|
909
918
|
console.log(readPkg().version);
|
|
910
919
|
process.exit(0);
|
|
911
920
|
}
|
|
912
|
-
if (arg === "--help" || arg === "-h") {
|
|
921
|
+
if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
913
922
|
console.log(usage);
|
|
914
923
|
process.exit(0);
|
|
915
924
|
}
|
|
916
|
-
if (arg === "--update") {
|
|
925
|
+
if (arg === "update" || arg === "--update") {
|
|
917
926
|
const pkg = readPkg();
|
|
918
927
|
const npmArgs = ["install", "-g", `${pkg.name}@latest`];
|
|
919
928
|
console.log(`subagent-mcp ${pkg.version} -> npm ${npmArgs.join(" ")}`);
|
|
@@ -924,25 +933,70 @@ if (isMain) {
|
|
|
924
933
|
// next to node_modules\npm). POSIX spawns the npm executable directly.
|
|
925
934
|
const { findOnPath, resolveCmdShimNodeScript } = await import("./setup.js");
|
|
926
935
|
const npm = findOnPath("npm") ?? "npm";
|
|
927
|
-
|
|
928
|
-
if (process.platform === "win32" && /\.(?:cmd|bat)$/i.test(npm)) {
|
|
936
|
+
const spawnNpm = (args, stdio) => {
|
|
929
937
|
const sibling = join(dirname(npm), "node_modules", "npm", "bin", "npm-cli.js");
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
938
|
+
if (process.platform === "win32" && /\.(?:cmd|bat)$/i.test(npm)) {
|
|
939
|
+
const js = resolveCmdShimNodeScript(npm) ?? (existsSync(sibling) ? sibling : null);
|
|
940
|
+
return js
|
|
941
|
+
? spawnSync(process.execPath, [js, ...args], {
|
|
942
|
+
stdio: stdio === "pipe" ? ["ignore", "pipe", "pipe"] : stdio,
|
|
943
|
+
encoding: stdio === "pipe" ? "utf8" : undefined,
|
|
944
|
+
})
|
|
945
|
+
: // Last resort: cmd.exe via shell. The arg vector is a fixed literal
|
|
946
|
+
// list (safe charset only), so there is no quoting/injection surface.
|
|
947
|
+
spawnSync("npm", args, {
|
|
948
|
+
stdio: stdio === "pipe" ? ["ignore", "pipe", "pipe"] : stdio,
|
|
949
|
+
shell: true,
|
|
950
|
+
encoding: stdio === "pipe" ? "utf8" : undefined,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return spawnSync(npm, args, {
|
|
954
|
+
stdio: stdio === "pipe" ? ["ignore", "pipe", "pipe"] : stdio,
|
|
955
|
+
encoding: stdio === "pipe" ? "utf8" : undefined,
|
|
956
|
+
});
|
|
957
|
+
};
|
|
958
|
+
const npmRoot = spawnNpm(["root", "-g"], "pipe");
|
|
959
|
+
if (npmRoot.error || npmRoot.status !== 0) {
|
|
960
|
+
console.error(`update failed to resolve npm global root: ${npmRoot.error?.message ?? npmRoot.stderr?.toString().trim() ?? "npm root -g failed"}`);
|
|
961
|
+
process.exit(npmRoot.status ?? 1);
|
|
936
962
|
}
|
|
937
|
-
|
|
938
|
-
|
|
963
|
+
const installRoot = join(npmRoot.stdout.toString().trim(), ...pkg.name.split("/"));
|
|
964
|
+
const rulesetPath = join(installRoot, "dist", "advanced-ruleset.py");
|
|
965
|
+
let previousRuleset = null;
|
|
966
|
+
if (existsSync(rulesetPath)) {
|
|
967
|
+
previousRuleset = readFileSync(rulesetPath);
|
|
968
|
+
const backupPath = join(tmpdir(), `advanced-ruleset.py.bak-update-${Date.now()}`);
|
|
969
|
+
try {
|
|
970
|
+
writeFileSync(backupPath, previousRuleset);
|
|
971
|
+
console.log(`backed up user advanced-ruleset.py to ${backupPath}`);
|
|
972
|
+
}
|
|
973
|
+
catch (e) {
|
|
974
|
+
console.error(`update refused before install: failed to back up advanced-ruleset.py: ${e instanceof Error ? e.message : String(e)}`);
|
|
975
|
+
process.exit(1);
|
|
976
|
+
}
|
|
939
977
|
}
|
|
978
|
+
const r = spawnNpm(npmArgs, "inherit");
|
|
940
979
|
if (r.error) {
|
|
941
980
|
console.error(`update failed to start npm: ${r.error.message}`);
|
|
942
981
|
process.exit(1);
|
|
943
982
|
}
|
|
944
983
|
const code = r.status ?? 1;
|
|
945
984
|
if (code === 0) {
|
|
985
|
+
if (previousRuleset !== null) {
|
|
986
|
+
try {
|
|
987
|
+
const freshRuleset = existsSync(rulesetPath)
|
|
988
|
+
? readFileSync(rulesetPath)
|
|
989
|
+
: null;
|
|
990
|
+
if (freshRuleset === null || !previousRuleset.equals(freshRuleset)) {
|
|
991
|
+
writeFileSync(rulesetPath, previousRuleset);
|
|
992
|
+
console.log("restored user advanced-ruleset.py (package update never overwrites user edits)");
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
catch (e) {
|
|
996
|
+
console.error(`update failed to restore advanced-ruleset.py: ${e instanceof Error ? e.message : String(e)}`);
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
946
1000
|
console.log("Update complete. Restart your CLI sessions so the MCP server picks up the new build.");
|
|
947
1001
|
}
|
|
948
1002
|
process.exit(code);
|
|
@@ -952,6 +1006,10 @@ if (isMain) {
|
|
|
952
1006
|
await runSetup();
|
|
953
1007
|
process.exit(0);
|
|
954
1008
|
}
|
|
1009
|
+
if (arg === "init" || arg === "--init") {
|
|
1010
|
+
const { runInit } = await import("./init.js");
|
|
1011
|
+
process.exit(await runInit());
|
|
1012
|
+
}
|
|
955
1013
|
if (arg === "doctor") {
|
|
956
1014
|
const { runDoctor } = await import("./doctor.js");
|
|
957
1015
|
process.exit(await runDoctor());
|
|
@@ -967,6 +1025,7 @@ if (isMain) {
|
|
|
967
1025
|
// until disabled with explicit user permission. On a new session the bundled
|
|
968
1026
|
// hook detects the carried-over marker and prompts the user to confirm.
|
|
969
1027
|
// (orchestrationMarker.disable is still used by the tool's enabled:false.)
|
|
1028
|
+
startLivenessHeartbeat();
|
|
970
1029
|
const transport = new StdioServerTransport();
|
|
971
1030
|
await server.connect(transport);
|
|
972
1031
|
}
|