@heretyc/subagent-mcp 2.10.4 → 2.12.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 CHANGED
@@ -5,180 +5,156 @@
5
5
  [![node](https://img.shields.io/node/v/@heretyc/subagent-mcp)](https://www.npmjs.com/package/@heretyc/subagent-mcp)
6
6
  [![CI](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml/badge.svg)](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
7
7
 
8
- MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions — on macOS, Linux, and Windows. No direct API calls. No API keys.
8
+ Turn any AI coding assistant into a **manager of other AI agents**. subagent-mcp
9
+ lets your Claude Code or Codex session start, watch, message, and stop a whole
10
+ team of AI sub-agents — on macOS, Linux, and Windows. **No direct API calls. No
11
+ API keys.** It drives the `claude` and `codex` command-line tools you already
12
+ signed into.
13
+
14
+ ## What it does
15
+
16
+ Normally one AI assistant does all the work in one conversation. That
17
+ conversation fills up, slows down, and eventually forgets its early context. As
18
+ of a certain size it "compacts" — summarizing and dropping detail.
19
+
20
+ subagent-mcp changes the shape of the work. Your assistant becomes an
21
+ **orchestrator**: it hands each task to a fresh sub-agent, watches the results,
22
+ and moves on — without ever doing the reading or writing itself.
23
+
24
+ ## Why it works this way
25
+
26
+ Four bets drive the whole design. The full reasoning lives in
27
+ [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md); the short version:
28
+
29
+ - **The effective memory grows geometrically.** Because the orchestrator only
30
+ ever holds *summaries* of what its sub-agents did — never the raw files — its
31
+ own memory fills up far more slowly. That means **very long tasks with little
32
+ or no compaction**.
33
+ - **Mixed providers avoid blind spots.** Using Claude *and* Codex together means
34
+ one vendor's weak spot or outage doesn't blind or block the whole job — and
35
+ you're never locked in to a single vendor.
36
+ - **Ruthless token efficiency.** Every hand-off is compressed and every read is
37
+ kept small, so the system stays fast and cheap even on marathon tasks.
38
+ - **Durable, authoritative reminders.** The operating rules are re-injected every
39
+ turn through official host hooks, so the assistant doesn't drift off-task or
40
+ start making things up over a long run.
9
41
 
10
42
  ## Install
11
43
 
12
- ### Prerequisites (runtime)
44
+ ### What you need first
13
45
 
14
- To run the published CLI you need:
46
+ - Node.js 18 or newer (`node --version`)
47
+ - `claude` CLI — installed and signed in (`claude --version`)
48
+ - `codex` CLI — installed and signed in (`codex --version`; optional if you
49
+ only use Claude)
15
50
 
16
- - Node.js >= 18 (`node --version`)
17
- - `claude` CLI — globally installed and authenticated (`claude --version`)
18
- - `codex` CLI — globally installed and authenticated (`codex --version`; optional if you only use Claude paths)
51
+ Building from source needs extra developer tools — see
52
+ [CONTRIBUTING.md](CONTRIBUTING.md).
19
53
 
20
- Building from source needs additional developer tooling — see [CONTRIBUTING.md § Prerequisites](CONTRIBUTING.md#prerequisites).
21
-
22
- ### npmjs (default)
23
-
24
- No authentication, no `.npmrc` configuration, and no PAT required. The package is publicly available on [npmjs.com](https://www.npmjs.com/package/@heretyc/subagent-mcp).
54
+ ### Install the package
25
55
 
26
56
  ```bash
27
57
  npm install -g @heretyc/subagent-mcp
28
58
  ```
29
59
 
30
- **Use this path for all standard installs.**
31
-
32
- `subagent-mcp update` always refreshes from npmjs, even on machines whose `.npmrc` routes `@heretyc` packages through GitHub Packages.
60
+ This is the standard install for everyone. (Organizations pinning the package
61
+ through GitHub Packages should see [docs/registration.md](docs/registration.md).)
33
62
 
34
- ### GitHub Packages (org-internal pin / supply-chain auditing)
35
-
36
- Use this path when your organization's `.npmrc` already routes the `@heretyc` scope through GitHub Packages, when you need supply-chain audit tracing against the org-internal artifact, or when you run inside a GitHub Actions workflow whose `GITHUB_TOKEN` carries `read:packages`. Otherwise prefer npmjs above — it is simpler and needs no credentials.
37
-
38
- ```bash
39
- # One-time machine setup
40
- echo "@heretyc:registry=https://npm.pkg.github.com" >> ~/.npmrc
41
- echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc
42
- # PAT must be a classic PAT with the read:packages scope
43
-
44
- npm install -g @heretyc/subagent-mcp
45
- ```
46
-
47
- > **Note:** GitHub Packages requires authentication even for public packages. If you see `401 Unauthorized`, verify your PAT has the `read:packages` scope and has not expired. GitHub Packages does not render a README page — the live documentation appears on [npmjs.com](https://www.npmjs.com/package/@heretyc/subagent-mcp).
48
-
49
- ## Quick Start
50
-
51
- **1. Install** (see [Install](#install) above):
52
-
53
- ```bash
54
- npm install -g @heretyc/subagent-mcp
55
- ```
56
-
57
- **2. Register the MCP server + orchestration hooks:**
63
+ ### Wire it into your assistant
58
64
 
59
65
  ```bash
60
66
  subagent-mcp setup
61
67
  ```
62
68
 
63
- Installing the package only ships the binary — it does **not** auto-wire anything. `subagent-mcp setup` auto-detects Claude Code / Codex and registers the MCP server plus the per-turn orchestration-mode hooks. Preview with `subagent-mcp setup --dry-run`.
64
-
65
- **3. Restart your host.**
69
+ Installing the package only ships the program — it does **not** connect anything
70
+ on its own. `subagent-mcp setup` finds your Claude Code / Codex install and
71
+ registers both the server and the per-turn orchestration hooks. Preview first
72
+ with `subagent-mcp setup --dry-run`.
66
73
 
67
- Restart your Claude Code or Codex session so it picks up the new binary. On Codex, run `/hooks` and trust the new hook.
74
+ ### Restart, then turn on the invariant
68
75
 
69
- **4. Initialize the orchestration invariant (recommended):**
76
+ Restart your Claude Code or Codex session so it picks up the new tools. On Codex,
77
+ run `/hooks` and trust the new hook. Then (recommended):
70
78
 
71
79
  ```bash
72
80
  subagent-mcp init --global
73
81
  ```
74
82
 
75
- `init --global` writes the managed invariant block into the providers' official global user-config files — `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md` — set up once, works across every project. For a single project instead, use `subagent-mcp init --root /path/to/project` to write into that project's instruction files. `--global` and `--root` are mutually exclusive.
76
-
77
- For manual wiring, Gemini CLI, Claude Desktop, and developer install from source, see [docs/registration.md](docs/registration.md).
78
-
79
- ## Features
80
-
81
- - Start Claude or Codex interactive sessions as managed sub-agents from any MCP host
82
- - Poll status, stream stdout/stderr tails, and enqueue follow-up messages to live sessions
83
- - Concurrency caps: 5 concurrent Claude + 5 concurrent Codex agents (only `processing` agents count toward the cap; `stalled` agents do not hold a slot)
84
- - Liveness tracking via the visible provider stream (Claude SDK events, Codex app-server JSONL): agents with no parsed visible provider stream item for 10 minutes enter `stalled` state (still alive, just quiet — thinking or awaiting a temp-file handoff), and recover to `processing` if the visible stream resumes
85
- - Ultracode mode for Opus 4.8 via `--settings {"ultracode":true}` — see [docs/usage.md](docs/usage.md)
86
- - 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
87
- - stdio MCP transport; built with `@modelcontextprotocol/sdk` + `zod`
88
- - `orchestration-mode` tool — toggles orchestrator directives injected by bundled Claude Code / Codex hooks; Claude also gets a deterministic `PreToolUse` gate
83
+ This writes a small managed "always delegate" rule block into your global
84
+ assistant config once, so it works across every project. For a single project
85
+ instead, use `subagent-mcp init --root /path/to/project`. Full per-platform
86
+ wiring (Gemini CLI, Claude Desktop, manual setup) is in
87
+ [docs/registration.md](docs/registration.md).
89
88
 
90
- ## Orchestration Mode
89
+ ## How to operate it
91
90
 
92
- **ON:** the agent operates as an orchestrator — hook injection governs each session turn, preventing inline reads or writes, and all work is delegated through sub-agent tools.
91
+ ### Orchestration mode: ON vs OFF
93
92
 
94
- **OFF:** the agent operates normally, with no orchestration constraints.
93
+ - **ON** your assistant acts as a pure manager. It never reads or writes files
94
+ directly; it delegates every step to sub-agents. Best for big, long-running
95
+ jobs.
96
+ - **OFF** — your assistant works normally, on its own, with no delegation rules.
95
97
 
96
- Toggle with the `orchestration-mode` tool. Desktop hosts toggle the mode but receive no hook injection. See [docs/spec/dev-loop/orchestration-directive-architecture.md](docs/spec/dev-loop/orchestration-directive-architecture.md) for full semantics.
98
+ Flip it with the `orchestration-mode` tool. (Desktop apps can toggle the mode
99
+ but don't receive the per-turn hook reminders.)
97
100
 
98
- ## Auto Mode
101
+ ### The 8 tools at a glance
99
102
 
100
- `launch_agent` supports **auto mode**: pass `prompt` + `task_category` and the server picks the best provider/model/effort for that category from its routing table, silently falling back to the next-best candidate on any launch-time failure.
101
-
102
- `provider`, `model`, and `effort` are optional overrides — omit them to get the auto-selected best combination. Rules: passing `model` requires `provider`; passing `effort` requires both `provider` and `model`.
103
-
104
- **task_category** (required) — pick one:
105
-
106
- | Category | What it is |
103
+ | Tool | What it does |
107
104
  |---|---|
108
- | `math_proof` | deliverable is a proof/derivation/formally-checkable result |
109
- | `security_review` | security verdict, threat assessment, or demonstrated exploit |
110
- | `debugging` | verified fix/root-cause; requires an observed failure as precondition |
111
- | `quality_review` | evaluative verdict on existing artifact (review, A-vs-B, validate-vs-spec) |
112
- | `architecture` | cross-module design/plan, no code, no execution loop |
113
- | `agentic_execution` | end-state via act/observe/adapt loop (run/deploy/provision/browse) |
114
- | `data_analysis` | empirical finding about structured dataset (query, stat, model) |
115
- | `coding` | bounded runnable code artifact, one-pass (implement, test, refactor) |
116
- | `knowledge_synthesis` | novel integrated prose over sources (synthesize, summarize, draft) |
117
- | `mechanical` | deterministic single-pass transform, exact-match checkable (grep, rename, reformat) |
118
- | `prompt_engineering` | designed/optimized prompt or prompt-system steering an LLM/agent (composite-inferred) |
119
- | `vulnerability_research` | discovery + PoC of a novel vulnerability (composite-inferred) |
120
- | `molecular_biology` | reasoned molecular/computational-biology result over sequences, structures, or -omics data (composite-inferred) |
121
- | `ml_accelerator_design` | hardware/software design for ML acceleration — dataflow, kernel, roofline (composite-inferred) |
122
- | `fallback_default` | no category matches with confidence; prefer splitting work instead |
123
-
124
- The last four are **composite-inferred**: they carry no dedicated benchmark and their routing competency is composed from parent categories rather than measured directly.
125
-
126
- **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.
127
-
128
- ## Configuration
129
-
130
- ### Global concurrent-subagent cap
131
-
132
- subagent-mcp enforces a machine-global cap on the number of **live concurrent subagents** across all agentic sessions and MCP server processes on the machine. The count includes agents started by other active sessions and the whole recursive descendant tree; slots free as agents finish or are killed.
133
-
134
- The cap is configured in `global-concurrency.jsonc`, a dedicated dist-sibling file in the install directory. It is separate from the advanced routing directives file, ships as a commented template, and is retained across installs and updates by the same preserve-user-edits mechanism as the advanced routing directives.
135
-
136
- Set `globalConcurrentSubagents` in that file. The default is `20`; the minimum valid value is `10`. Validation is forced: `0`, unset, missing, or invalid values reset to `20`, and values `1` through `9` are pinned up to `10`. There is no environment-variable override; the file is the sole source of truth. The file is re-read on every `launch_agent` call, so edits take effect on the next launch with no server restart.
137
-
138
- When the cap is reached, `launch_agent` is rejected immediately; it never queues. Before cap checks, hooks and tool calls run default zombie culling with no config knob: stale slots whose owner server is gone may terminate their managed child process-tree, while live owned slots are heartbeated and preserved; terminal-but-alive agents idle for more than 30 seconds are force-killed. Reports include `zombies`, and `poll_agent` keeps the tail with `zombie_killed` when culling terminates an agent.
139
-
140
- ## Tools
141
-
142
- Eight tools are exposed over the stdio MCP transport.
143
-
144
- | Tool | Description |
145
- |---|---|
146
- | `launch_agent` | Start a new sub-agent session with a prompt and optional task category / overrides |
147
- | `poll_agent` | Check status and stream tail output for a running agent |
148
- | `kill_agent` | Terminate a running or stalled agent |
149
- | `send_message` | Enqueue a message into a running agent's stdin |
150
- | `list_agents` | List all tracked agents and their current status |
151
- | `wait` | Block until a specific agent reaches a terminal state |
152
- | `orchestration-mode` | Toggle orchestration-mode ON/OFF; controls whether hook directives are injected into agent sessions |
153
- | `model-selection-mode` | Control model selection: `smart` (auto-pick) or `user-approved-overrides` (30-min override window) |
154
-
155
- Full parameters, return shapes, and the `alive` / `idle_seconds` / `hint` / `recent_stream` fields are in [docs/tools.md](docs/tools.md).
156
-
157
- ## Agent Lifecycle
158
-
159
- Each agent transitions through these states:
160
-
161
- | Status | Meaning |
162
- |---|---|
163
- | `processing` | Driver alive with a visible provider-stream heartbeat in the last 10 minutes — actively working. Launch time counts as the initial heartbeat |
164
- | `stalled` | Driver STILL ALIVE but no parsed visible provider stream item for >= 10 minutes — working, thinking, or awaiting a temp-file handoff (not a failure). Recovers to `processing` if the visible stream resumes |
165
- | `finished` | Current turn completed, or driver exited with code 0 |
166
- | `errored` | Process exited with non-zero code |
167
- | `stopped` | Terminated by `kill_agent` |
168
-
169
- `processing` and `stalled` are live. `stalled` means the driver is alive but quiet (>= 10 min no visible stream); it recovers automatically and is never auto-killed — do not `kill_agent` a stalled agent. `wait` does not return on `stalled`. Full semantics: [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md).
105
+ | `launch_agent` | Start a sub-agent on a task (just give a prompt + a task category) |
106
+ | `poll_agent` | Check how an agent is doing and read its latest output |
107
+ | `kill_agent` | Stop a running or quiet agent |
108
+ | `send_message` | Send a follow-up message to a live agent |
109
+ | `list_agents` | See every agent and its current status |
110
+ | `wait` | Pause until a specific agent finishes |
111
+ | `orchestration-mode` | Turn manager-mode ON or OFF |
112
+ | `model-selection-mode` | Let the server auto-pick the model, or allow manual overrides |
113
+
114
+ You never have to choose a model. Give `launch_agent` a plain-English prompt and
115
+ a **task category** (e.g. "coding", "debugging", "security_review") and the
116
+ server picks the best provider, model, and effort for that kind of work.
117
+
118
+ ### How many run at once
119
+
120
+ There is a single machine-wide limit on how many sub-agents run at the same time
121
+ across everything on your computer. The **default is 20**. When the limit is
122
+ reached, a new `launch_agent` is turned down right away — it does not wait in a
123
+ queue. You change the number in the `global-concurrency.jsonc` file in the
124
+ install folder (minimum 10); the file is re-read on every launch, so no restart
125
+ is needed.
126
+
127
+ The same settings file includes `checkForUpdates` (default `true`). When a newer
128
+ npm version exists, the per-turn hook can show a throttled notice to run
129
+ `subagent-mcp update` and then `subagent-mcp setup`. Set `checkForUpdates` to
130
+ `false`, or run with `SUBAGENT_UPDATE_CHECK=0` / `false`, to disable that check.
131
+
132
+ ## Basic debugging
133
+
134
+ - **"An agent looks stuck."** A quiet agent is usually **still alive**, not
135
+ dead. After ~10 minutes with no output an agent is marked `stalled` it's
136
+ thinking or waiting on a hand-off. It recovers on its own. **Do not kill a
137
+ stalled agent**, and `wait` will not return on one.
138
+ - **"It won't start a new agent (cap reached)."** You've hit the concurrent
139
+ limit. Use `list_agents` to see what's running and `kill_agent` on anything
140
+ you no longer need — that frees a slot immediately. Raising the number in
141
+ `global-concurrency.jsonc` also works.
142
+ - **"Where are the logs?"** Each agent's recent output is available any time via
143
+ `poll_agent`. The server's own diagnostics go to your host's normal MCP
144
+ server log (your Claude Code or Codex session logs) all server logging goes
145
+ to stderr, never mixed into results.
170
146
 
171
147
  ## Documentation
172
148
 
173
149
  | Document | Contents |
174
150
  |---|---|
175
- | [docs/registration.md](docs/registration.md) | Per-platform setup: Claude Code, Codex, Gemini CLI, Claude Desktop; manual wiring; developer install from source |
151
+ | [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md) | Why it's built this way the full design rationale |
152
+ | [docs/registration.md](docs/registration.md) | Per-platform setup: Claude Code, Codex, Gemini CLI, Claude Desktop |
176
153
  | [docs/tools.md](docs/tools.md) | Full tool reference — all eight tools, parameters, return shapes |
177
- | [docs/usage.md](docs/usage.md) | Model & effort matrix, ultracode mode, CLI invocations, usage examples |
178
- | [docs/SPEC.md](docs/SPEC.md) | Full technical specification — architecture, schemas, status lifecycle, error catalogue |
179
- | [docs/spec/interactive-drivers.md](docs/spec/interactive-drivers.md) | Always-interactive Claude/Codex driver model |
180
- | [docs/release-notes.md](docs/release-notes.md) | Operator-facing release notes |
181
- | [CONTRIBUTING.md](CONTRIBUTING.md) | Contributor guide — dev environment, build, test, publish |
154
+ | [docs/usage.md](docs/usage.md) | Model & effort matrix, ultracode mode, usage examples |
155
+ | [docs/SPEC.md](docs/SPEC.md) | Full technical specification |
156
+ | [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md) | Agent status meanings (processing / stalled / finished / errored / stopped) |
157
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Developer guide build, test, publish, contribution workflow |
182
158
 
183
159
  ## License
184
160
 
@@ -2,7 +2,9 @@
2
2
  <subagent-mcp state="on" kind="carryover">
3
3
  FIRST-LINE EXEMPTION: if this session's prompt's literal first line begins with "<this is a request from a parent process>", ignore this entire tag.
4
4
 
5
- Orchestration ON carried over from a PRIOR session for this project (per-session disable only; the next new session resumes ON automatically or after a 2h backstop). Not enabled THIS session.
5
+ Orchestration ON carried over from a PRIOR session for this project (per-session disable; next session resumes ON, or after a 2h backstop). Not enabled THIS session.
6
6
 
7
- THIS turn, ONCE: (1) NOTIFY the user it carried over; (2) ASK via request-user-input whether to REMAIN enabled; (3) ADVISE fit — long-horizon / context-filling → remain enabled; bounded / interactive → propose OFF. Decline → orchestration-mode enabled:false for this session only; no mid-session re-enable. NEVER disable on your own initiative. After the answer the handshake is done; do not re-raise.
7
+ THIS turn, ONCE: (1) NOTIFY the user it carried over; (2) ASK via request-user-input whether to REMAIN enabled; (3) ADVISE fit — long-horizon → remain enabled; bounded/interactive → disable this session. Decline → orchestration-mode enabled:false this session only; no mid-session re-enable. NEVER disable on your own initiative. After answer handshake done; do not re-raise.
8
+
9
+ While ON, follow the MOST RECENT <subagent-mcp state="on"> tag in context (directive or reminder/carrier); if none is in the current window, the CLAUDE/AGENTS/GEMINI INIT_BLOCK governs. This tag is co-supreme with safety-scope; conflict → ask the user.
8
10
  </subagent-mcp>
@@ -2,7 +2,7 @@
2
2
  <subagent-mcp state="on" kind="reminder">
3
3
  FIRST-LINE EXEMPTION: if this session's prompt's literal first line begins with "<this is a request from a parent process>", ignore this entire tag (leading blank lines don't count; you are a sub-agent).
4
4
 
5
- Orchestration ON. You are the orchestrator: delegate EVERY step. Allowed tools = ONLY the structured-question tool (AskUserQuestion / request-user-input) + subagent-mcp + /workflows; NO direct reads or writes; inline-by-right does not exist. Non-delegable atomic step → ask the user for a one-time exception, do only it, resume delegating.
5
+ Orchestration ON. You are the orchestrator: delegate EVERY step. Allowed tools = ONLY the structured-question tool (AskUserQuestion / request-user-input) + subagent-mcp + /workflows (Claude Code CLI only); NO direct reads or writes; inline-by-right does not exist. Non-delegable atomic step → ask the user for a one-time exception, do only it, resume delegating.
6
6
 
7
7
  Each launched prompt carries objective + output format + tools/sources + boundaries; scale agent count to complexity; subdivide to the smallest auditable step; verify code steps with an independent sub-agent.
8
8
 
@@ -1 +1 @@
1
- <subagent-mcp state="on" kind="carrier">If first line begins "<this is a request from a parent process>", ignore this tag. Orchestration ON. Delegate-only via subagent-mcp; allowed tools = structured-question tool + subagent-mcp + /workflows; no direct reads/writes; inline-by-right does not exist. Subdivide small; verify code steps; never 1-shot multi-phase. Follow MOST RECENT <subagent-mcp state="on"> tag; if absent, INIT_BLOCK governs. Co-supreme with safety-scope; user request cannot bypass.</subagent-mcp>
1
+ <subagent-mcp state="on" kind="carrier">If first line begins "<this is a request from a parent process>", ignore this tag. Orchestration ON. Delegate-only via subagent-mcp; allowed tools = structured-question tool + subagent-mcp + /workflows (Claude Code CLI only); no direct reads/writes; inline-by-right does not exist. Subdivide small; verify code steps; never 1-shot multi-phase. Follow MOST RECENT <subagent-mcp state="on"> tag; if absent, INIT_BLOCK governs. Co-supreme with safety-scope; user request cannot bypass.</subagent-mcp>
@@ -1,67 +1,67 @@
1
- #!/usr/bin/env python3
2
- """advanced-ruleset.py — final-authority model-routing override hook for subagent-mcp.
3
-
4
- (a) PERFORMANCE WARNING: this script runs synchronously inside EVERY launch_agent
5
- call. Slow rules slow every agent launch. Keep rules lean and low-latency —
6
- no network calls, no heavy imports at module top. This is YOUR responsibility;
7
- you have been warned.
8
-
9
- (b) OUTPUT CONTRACT (routing mode): print to stdout ONE JSON array — the modified
10
- candidate list (reorder / filter / replace allowed). Template:
11
- [
12
- {"provider": "claude", "model": "sonnet", "effort": "high", "rank": 1},
13
- {"provider": "codex", "model": "gpt-5.5", "effort": "xhigh", "rank": 2}
14
- ]
15
- Valid providers: claude, codex. Valid models: haiku, sonnet, opus, opus-4-8 (claude);
16
- gpt-5.5 (codex). Valid efforts: haiku -> "none" only; sonnet -> medium|high|xhigh|max;
17
- opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> medium|high|xhigh.
18
- "rank" on output is ignored. An EMPTY array vetoes the launch. Anything else
19
- invalid fails the launch hard — the server validates strictly.
20
-
21
- (c) INPUT CONTRACT (routing mode, invoked as: <python> advanced-ruleset.py route):
22
- stdin receives one JSON object:
23
- { "candidates": [ {"provider","model","effort","rank"} ... ], # rank 1..N best->worst
24
- "context": { "task_category": str, "cwd": str,
25
- "selection_mode": "auto"|"provider"|"provider_model"|"explicit",
26
- "provider": str|None, "model": str|None, "effort": str|None } }
27
- OS environment variables are visible natively (os.environ).
28
-
29
- ENV-CHECK MODE (no arguments): prints {"ready": true|false, "load-rules": true|false}.
30
- Runs once per MCP server process. load-rules false => ruleset silently disabled
31
- for the rest of the process. Set LOAD_RULES = True below to activate.
32
- """
33
- import json
34
- import sys
35
-
36
- LOAD_RULES = False
37
-
38
- # --- Requirements stub (scaffold itself is stdlib-only) ----------------------
39
- # List third-party distributions your rules import, e.g.:
40
- # REQUIREMENTS = ["requests", "pyyaml"]
41
- # Install with: <python> -m pip install <name> ...
42
- REQUIREMENTS = []
43
-
44
- def missing_requirements():
45
- """pip-check helper: returns the REQUIREMENTS entries not importable here."""
46
- import importlib.util
47
- return [r for r in REQUIREMENTS
48
- if importlib.util.find_spec(r.replace("-", "_")) is None]
49
-
50
- def env_check():
51
- missing = missing_requirements()
52
- json.dump({"ready": not missing, "load-rules": bool(LOAD_RULES)}, sys.stdout)
53
-
54
- def apply_rules(candidates, context):
55
- """YOUR RULES HERE. Default: passthrough (returns the list unchanged)."""
56
- return candidates
57
-
58
- def route():
59
- payload = json.load(sys.stdin)
60
- out = apply_rules(payload.get("candidates", []), payload.get("context", {}))
61
- json.dump(out, sys.stdout)
62
-
63
- if __name__ == "__main__":
64
- if len(sys.argv) > 1 and sys.argv[1] == "route":
65
- route()
66
- else:
67
- env_check()
1
+ #!/usr/bin/env python3
2
+ """advanced-ruleset.py — final-authority model-routing override hook for subagent-mcp.
3
+
4
+ (a) PERFORMANCE WARNING: this script runs synchronously inside EVERY launch_agent
5
+ call. Slow rules slow every agent launch. Keep rules lean and low-latency —
6
+ no network calls, no heavy imports at module top. This is YOUR responsibility;
7
+ you have been warned.
8
+
9
+ (b) OUTPUT CONTRACT (routing mode): print to stdout ONE JSON array — the modified
10
+ candidate list (reorder / filter / replace allowed). Template:
11
+ [
12
+ {"provider": "claude", "model": "sonnet", "effort": "high", "rank": 1},
13
+ {"provider": "codex", "model": "gpt-5.5", "effort": "xhigh", "rank": 2}
14
+ ]
15
+ Valid providers: claude, codex. Valid models: haiku, sonnet, opus, opus-4-8, fable (claude);
16
+ gpt-5.5 (codex). Valid efforts: haiku -> "none" only; sonnet -> medium|high|xhigh|max;
17
+ fable -> medium|high|xhigh|max; opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> medium|high|xhigh.
18
+ "rank" on output is ignored. An EMPTY array vetoes the launch. Anything else
19
+ invalid fails the launch hard — the server validates strictly.
20
+
21
+ (c) INPUT CONTRACT (routing mode, invoked as: <python> advanced-ruleset.py route):
22
+ stdin receives one JSON object:
23
+ { "candidates": [ {"provider","model","effort","rank"} ... ], # rank 1..N best->worst
24
+ "context": { "task_category": str, "cwd": str,
25
+ "selection_mode": "auto"|"provider"|"provider_model"|"explicit",
26
+ "provider": str|None, "model": str|None, "effort": str|None } }
27
+ OS environment variables are visible natively (os.environ).
28
+
29
+ ENV-CHECK MODE (no arguments): prints {"ready": true|false, "load-rules": true|false}.
30
+ Runs once per MCP server process. load-rules false => ruleset silently disabled
31
+ for the rest of the process. Set LOAD_RULES = True below to activate.
32
+ """
33
+ import json
34
+ import sys
35
+
36
+ LOAD_RULES = False
37
+
38
+ # --- Requirements stub (scaffold itself is stdlib-only) ----------------------
39
+ # List third-party distributions your rules import, e.g.:
40
+ # REQUIREMENTS = ["requests", "pyyaml"]
41
+ # Install with: <python> -m pip install <name> ...
42
+ REQUIREMENTS = []
43
+
44
+ def missing_requirements():
45
+ """pip-check helper: returns the REQUIREMENTS entries not importable here."""
46
+ import importlib.util
47
+ return [r for r in REQUIREMENTS
48
+ if importlib.util.find_spec(r.replace("-", "_")) is None]
49
+
50
+ def env_check():
51
+ missing = missing_requirements()
52
+ json.dump({"ready": not missing, "load-rules": bool(LOAD_RULES)}, sys.stdout)
53
+
54
+ def apply_rules(candidates, context):
55
+ """YOUR RULES HERE. Default: passthrough (returns the list unchanged)."""
56
+ return candidates
57
+
58
+ def route():
59
+ payload = json.load(sys.stdin)
60
+ out = apply_rules(payload.get("candidates", []), payload.get("context", {}))
61
+ json.dump(out, sys.stdout)
62
+
63
+ if __name__ == "__main__":
64
+ if len(sys.argv) > 1 and sys.argv[1] == "route":
65
+ route()
66
+ else:
67
+ env_check()
@@ -6,6 +6,7 @@ import { CONCURRENCY_SCAFFOLD } from "./config-scaffold.js";
6
6
  import { cullStaleSlots, ZOMBIE_FORCE_GRACE_MS, ZOMBIE_LIVE_IDLE_MS, ZOMBIE_TERMINAL_IDLE_MS, buildProcessTreeKillCommands, drainZombieIntents, drainZombieReports, parseSlotMetadata, readSlotMetadata, slotPathForAgent, writeSlotMetadata, } from "./zombie.js";
7
7
  export const DEFAULT_CAP = 20;
8
8
  export const MIN_CAP = 10;
9
+ export const DEFAULT_CHECK_FOR_UPDATES = true;
9
10
  export const CONFIG_FILENAME = "global-concurrency.jsonc";
10
11
  export { cullStaleSlots, ZOMBIE_FORCE_GRACE_MS, ZOMBIE_LIVE_IDLE_MS, ZOMBIE_TERMINAL_IDLE_MS, buildProcessTreeKillCommands, drainZombieIntents, drainZombieReports, parseSlotMetadata, readSlotMetadata, slotPathForAgent, writeSlotMetadata, };
11
12
  export function clampCap(raw) {
@@ -31,6 +32,16 @@ export function parseConcurrencyConfig(text) {
31
32
  }
32
33
  return clampCap(raw);
33
34
  }
35
+ export function parseCheckForUpdatesConfig(text) {
36
+ try {
37
+ const raw = JSON.parse(stripJsoncComments(text))
38
+ ?.checkForUpdates;
39
+ return typeof raw === "boolean" ? raw : DEFAULT_CHECK_FOR_UPDATES;
40
+ }
41
+ catch {
42
+ return DEFAULT_CHECK_FOR_UPDATES;
43
+ }
44
+ }
34
45
  export function defaultConfigPath() {
35
46
  return fileURLToPath(new URL("./" + CONFIG_FILENAME, import.meta.url));
36
47
  }
@@ -51,6 +62,15 @@ export function readGlobalCap(path = defaultConfigPath()) {
51
62
  return DEFAULT_CAP;
52
63
  }
53
64
  }
65
+ export function readCheckForUpdates(path = defaultConfigPath()) {
66
+ try {
67
+ ensureConcurrencyConfig(path);
68
+ return parseCheckForUpdatesConfig(readFileSync(path, "utf8"));
69
+ }
70
+ catch {
71
+ return DEFAULT_CHECK_FOR_UPDATES;
72
+ }
73
+ }
54
74
  export function slotDir() {
55
75
  if (process.env.SUBAGENT_SLOT_DIR)
56
76
  return process.env.SUBAGENT_SLOT_DIR;
@@ -79,20 +99,26 @@ export function reserveSlot(agentId, max, dir = slotDir(), cullDeps) {
79
99
  mkdirSync(dir, { recursive: true, mode: 0o1777 });
80
100
  cullStaleSlots(dir, cullDeps);
81
101
  const slotPath = slotPathForAgent(dir, agentId);
102
+ const before = countSlots(dir);
103
+ if (before >= max) {
104
+ return { ok: false, current: before, max };
105
+ }
106
+ // ponytail: count->write->recount narrows the TOCTOU; a cross-process lock would close it fully.
82
107
  writeSlotMetadata(slotPath, { agent_id: agentId });
83
- const n = countSlots(dir);
84
- if (n > max) {
108
+ const after = countSlots(dir);
109
+ if (after > max) {
85
110
  try {
86
111
  unlinkSync(slotPath);
87
112
  }
88
113
  catch { }
89
- return { ok: false, current: n - 1, max };
114
+ return { ok: false, current: countSlots(dir), max };
90
115
  }
91
- return { ok: true, slotPath, current: n, max };
116
+ return { ok: true, slotPath, current: after, max };
92
117
  }
93
118
  catch (e) {
94
- console.error(`[concurrency] reserve failed, failing open: ${e instanceof Error ? e.message : String(e)}`);
95
- return { ok: true, current: 0, max, slotPath: null };
119
+ const error = e instanceof Error ? e.message : String(e);
120
+ console.error(`[concurrency] reserve failed, rejecting launch: ${error}`);
121
+ return { ok: false, current: -1, max, error };
96
122
  }
97
123
  }
98
124
  export function releaseSlot(slotPath) {
@@ -1,2 +1,2 @@
1
1
  // GENERATED by scripts/gen-ruleset-scaffold.mjs from src/global-concurrency.jsonc — DO NOT EDIT.
2
- export const CONCURRENCY_SCAFFOLD = "// subagent-mcp — Global Concurrent Subagent Cap\r\n// ------------------------------------------------------------------\r\n// SOLE source of truth for the machine-wide limit on how many subagents\r\n// may be ALIVE AT ONCE across EVERY session, process, and user on this\r\n// machine. There is NO environment-variable override.\r\n//\r\n// The whole recursive descendant tree counts toward this ONE number: a\r\n// subagent that itself launches subagents adds to the same machine-wide\r\n// total, and OTHER active agentic sessions count too.\r\n//\r\n// RE-READ on every launch_agent call — edits take effect immediately, no\r\n// server restart required.\r\n//\r\n// Value rules (forcibly applied to the number below):\r\n// - missing / unset / non-integer / 0 / negative -> reset to default 20\r\n// - 1 through 9 -> forced UP to minimum 10\r\n// - 10 or greater -> used as-is\r\n//\r\n// Zombie culling is always enabled. There is no config knob. Before cap\n// rejection, launch/tool/hook paths refresh live owned slots, preserve stale\n// slots whose owner server is still alive, and cull stale slots whose owner is\n// gone. Managed stale slots terminate the child process tree, then force-kill\n// after 20s when needed; unmanaged stale slots are only unlinked.\n//\r\n// When the cap is reached after culling, launch_agent is REJECTED (never\r\n// queued). Free a slot with list_agents + kill_agent, then retry.\r\n{\r\n \"globalConcurrentSubagents\": 20\r\n}\r\n";
2
+ export const CONCURRENCY_SCAFFOLD = "// subagent-mcp — Global Concurrent Subagent Cap\n// ------------------------------------------------------------------\n// SOLE source of truth for the machine-wide limit on how many subagents\n// may be ALIVE AT ONCE across EVERY session, process, and user on this\n// machine. There is NO environment-variable override for the cap.\n//\n// The whole recursive descendant tree counts toward this ONE number: a\n// subagent that itself launches subagents adds to the same machine-wide\n// total, and OTHER active agentic sessions count too.\n//\n// RE-READ on every launch_agent call — edits take effect immediately, no\n// server restart required.\n//\n// Value rules (forcibly applied to the number below):\n// - missing / unset / non-integer / 0 / negative -> reset to default 20\n// - 1 through 9 -> forced UP to minimum 10\n// - 10 or greater -> used as-is\n//\n// Zombie culling is always enabled. There is no config knob. Before cap\n// rejection, launch/tool/hook paths refresh live owned slots, preserve stale\n// slots whose owner server is still alive, and cull stale slots whose owner is\n// gone. Managed stale slots terminate the child process tree, then force-kill\n// after 20s when needed; unmanaged stale slots are only unlinked.\n//\n// When the cap is reached after culling, launch_agent is REJECTED (never\n// queued). Free a slot with list_agents + kill_agent, then retry.\n//\n// checkForUpdates controls the silent npmjs update check started when the MCP\n// server connects. Default true. Set to false to skip the registry fetch and\n// suppress hook notices. SUBAGENT_UPDATE_CHECK=0 or false also disables it.\n{\n \"globalConcurrentSubagents\": 20,\n \"checkForUpdates\": true\n}\n";