@heretyc/subagent-mcp 2.12.13 → 2.12.14
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 +170 -170
- package/dist/advanced-ruleset.py +67 -67
- package/dist/concurrency.js +117 -16
- package/dist/config-scaffold.js +1 -1
- package/dist/drivers.js +13 -6
- package/dist/effort.js +5 -3
- package/dist/global-concurrency.jsonc +43 -43
- package/dist/global-subagent-mcp-config.jsonc +43 -43
- package/dist/index.js +6 -6
- package/dist/pending-permissions.js +13 -3
- package/dist/routing-table.json +2133 -2133
- package/dist/ruleset-scaffold.js +1 -1
- package/package.json +70 -70
- package/scripts/postinstall.mjs +102 -102
package/README.md
CHANGED
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
# subagent-mcp
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
6
|
-
[](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
|
|
7
|
-
|
|
8
|
-
## Core Premise
|
|
9
|
-
|
|
10
|
-
subagent-mcp is an MCP stdio server that turns an AI coding assistant (Claude
|
|
11
|
-
Code, Codex, Gemini CLI) into a manager of local Claude and Codex sub-agents on
|
|
12
|
-
macOS, Linux, and Windows. It drives the locally authenticated `claude` and
|
|
13
|
-
`codex` CLIs you already signed into. It does not make direct HTTP API calls and
|
|
14
|
-
does not require API keys.
|
|
15
|
-
|
|
16
|
-
The orchestrator monitors but does not read or write project files itself. Work
|
|
17
|
-
is delegated to fresh sub-agents, so the orchestrator keeps summaries instead
|
|
18
|
-
of raw file context. The main invariants are:
|
|
19
|
-
|
|
20
|
-
- one machine-global, provider-agnostic concurrency cap (default 20, minimum 10)
|
|
21
|
-
- fail-safe orchestration ON on hookless hosts
|
|
22
|
-
- state authority only from harness-verified `<subagent-mcp state="...">` tags
|
|
23
|
-
- sub-agents gated by default with permission ceiling `auto`
|
|
24
|
-
- automatic model, provider, and effort routing per task category
|
|
25
|
-
|
|
26
|
-
## Install
|
|
27
|
-
|
|
28
|
-
### What You Need First
|
|
29
|
-
|
|
30
|
-
- Node.js 18 or newer (`node --version`)
|
|
31
|
-
- `claude` CLI, installed and signed in (`claude --version`)
|
|
32
|
-
- `codex` CLI, installed and signed in (`codex --version`; optional if you only
|
|
33
|
-
use Claude)
|
|
34
|
-
|
|
35
|
-
Building from source needs extra developer tools. See
|
|
36
|
-
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
37
|
-
|
|
38
|
-
### Install The Package
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npm install -g @heretyc/subagent-mcp
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
This is the standard install. Organizations pinning the package through GitHub
|
|
45
|
-
Packages should see [docs/registration.md](docs/registration.md).
|
|
46
|
-
|
|
47
|
-
### Wire It Into Your Assistant
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
subagent-mcp setup
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Installing the package only ships the program. It does not connect anything on
|
|
54
|
-
its own. `subagent-mcp setup` finds your Claude Code or Codex install and
|
|
55
|
-
registers both the server and the per-turn orchestration hooks. Preview first
|
|
56
|
-
with `subagent-mcp setup --dry-run`.
|
|
57
|
-
|
|
58
|
-
### Restart, Then Turn On The Invariant
|
|
59
|
-
|
|
60
|
-
Restart your Claude Code or Codex session so it picks up the new tools. On
|
|
61
|
-
Codex, run `/hooks` and trust the new hook. Then, recommended:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
subagent-mcp init --global
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
This writes a managed "always delegate" rule block into your global assistant
|
|
68
|
-
config once. For one project only, use
|
|
69
|
-
`subagent-mcp init --root /path/to/project`. Full per-platform wiring (Gemini
|
|
70
|
-
CLI, Claude Desktop, manual setup) is in
|
|
71
|
-
[docs/registration.md](docs/registration.md).
|
|
72
|
-
|
|
73
|
-
## How To Operate It
|
|
74
|
-
|
|
75
|
-
### Orchestration Mode
|
|
76
|
-
|
|
77
|
-
- **ON**: your assistant acts as a pure manager. It delegates every step to
|
|
78
|
-
sub-agents. Best for big, long-running jobs.
|
|
79
|
-
- **OFF**: your assistant works normally, with no delegation rules.
|
|
80
|
-
|
|
81
|
-
Flip it with the `orchestration-mode` tool. Desktop apps can toggle the mode but
|
|
82
|
-
do not receive per-turn hook reminders.
|
|
83
|
-
|
|
84
|
-
### Tools
|
|
85
|
-
|
|
86
|
-
The server exposes `launch_agent`, `poll_agent`, `kill_agent`, `send_message`,
|
|
87
|
-
`list_agents`, `wait`, `respond_permission`, `orchestration-mode`, and
|
|
88
|
-
`model-selection-mode`. See [docs/tools.md](docs/tools.md) for the full
|
|
89
|
-
parameter and return reference.
|
|
90
|
-
|
|
91
|
-
You do not have to choose a model. Give `launch_agent` a prompt and a task
|
|
92
|
-
category such as `coding`, `debugging`, or `security_review`; the server picks
|
|
93
|
-
the provider, model, and effort.
|
|
94
|
-
|
|
95
|
-
### Concurrency
|
|
96
|
-
|
|
97
|
-
There is one machine-wide limit on concurrent sub-agents. The default is 20.
|
|
98
|
-
When the limit is reached, `launch_agent` is rejected immediately and does not
|
|
99
|
-
queue. Change the value in `global-subagent-mcp-config.jsonc` in the install
|
|
100
|
-
folder. The file is re-read on every launch.
|
|
101
|
-
|
|
102
|
-
The config file was renamed from `global-concurrency.jsonc` in 2.12.5. The old
|
|
103
|
-
name is still read, with a one-time deprecation notice, when the new file is
|
|
104
|
-
absent.
|
|
105
|
-
|
|
106
|
-
The same settings file includes `checkForUpdates` (default `true`). Disable it
|
|
107
|
-
with `checkForUpdates: false` or `SUBAGENT_UPDATE_CHECK=0`.
|
|
108
|
-
|
|
109
|
-
## Configuration
|
|
110
|
-
|
|
111
|
-
Machine-wide defaults live in `global-subagent-mcp-config.jsonc`, installed
|
|
112
|
-
beside the compiled server and re-read on every `launch_agent`. It controls the
|
|
113
|
-
global concurrency cap, update checks, permission ceiling, escalation behavior,
|
|
114
|
-
strict read-parity logging, and Codex sandbox networking.
|
|
115
|
-
|
|
116
|
-
User and repo permission files can only tighten or add scoped permissions on top
|
|
117
|
-
of the global ceiling. See [README/configuration.md](README/configuration.md)
|
|
118
|
-
for the full key table, precedence rules, and mode summary.
|
|
119
|
-
|
|
120
|
-
## Permissions
|
|
121
|
-
|
|
122
|
-
Launched sub-agents run gated by default. Set `permissionsCeiling` in
|
|
123
|
-
`global-subagent-mcp-config.jsonc`:
|
|
124
|
-
|
|
125
|
-
| Mode | What a sub-agent can do |
|
|
126
|
-
|---|---|
|
|
127
|
-
| `auto` | Default. Safe reads auto-allow, dangerous actions auto-deny, everything else parks for your decision. |
|
|
128
|
-
| `manual` | Same, but every non-denied action parks for a decision. |
|
|
129
|
-
| `yolo` | No gating at all. |
|
|
130
|
-
|
|
131
|
-
When a sub-agent's action parks, its status becomes `permission_requested` and
|
|
132
|
-
it appears in `poll_agent`, `list_agents`, and `wait`. Answer it with:
|
|
133
|
-
|
|
134
|
-
```text
|
|
135
|
-
respond_permission(agent_id="...", decision="allow" | "deny", reason="...")
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
One-time only. Omit `request_id` to answer the oldest pending request.
|
|
139
|
-
Unanswered requests auto-deny after 5 minutes. Full spec:
|
|
140
|
-
[docs/spec/permissions.md](docs/spec/permissions.md).
|
|
141
|
-
|
|
142
|
-
## Basic Debugging
|
|
143
|
-
|
|
144
|
-
- **An agent looks stuck.** A quiet agent is usually still alive. After about 10
|
|
145
|
-
minutes with no output an agent is marked `stalled`. Prefer `wait` or another
|
|
146
|
-
`poll_agent` over killing it.
|
|
147
|
-
- **Cap reached.** Use `list_agents` to see what is running and `kill_agent` on
|
|
148
|
-
work you no longer need. Raising `globalConcurrentSubagents` also works.
|
|
149
|
-
- **Logs.** Agent output is available through `poll_agent`. Server diagnostics
|
|
150
|
-
go to the host MCP server log on stderr.
|
|
151
|
-
|
|
152
|
-
## Documentation
|
|
153
|
-
|
|
154
|
-
| Document | Contents |
|
|
155
|
-
|---|---|
|
|
156
|
-
| [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md) | Design rationale |
|
|
157
|
-
| [docs/registration.md](docs/registration.md) | Per-platform setup |
|
|
158
|
-
| [docs/tools.md](docs/tools.md) | Tool reference |
|
|
159
|
-
| [docs/usage.md](docs/usage.md) | Model and effort matrix |
|
|
160
|
-
| [docs/SPEC.md](docs/SPEC.md) | Technical specification |
|
|
161
|
-
| [README/configuration.md](README/configuration.md) | Configuration keys and precedence |
|
|
162
|
-
| [docs/spec/permissions.md](docs/spec/permissions.md) | Permission system |
|
|
163
|
-
| [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md) | Agent status meanings |
|
|
164
|
-
| [CONTRIBUTING.md](CONTRIBUTING.md) | Developer guide |
|
|
165
|
-
|
|
166
|
-
## License
|
|
167
|
-
|
|
168
|
-
Apache-2.0. Copyright 2026 Lexi Blackburn.
|
|
169
|
-
|
|
170
|
-
See [LICENSE](LICENSE).
|
|
1
|
+
# subagent-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
6
|
+
[](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
|
|
7
|
+
|
|
8
|
+
## Core Premise
|
|
9
|
+
|
|
10
|
+
subagent-mcp is an MCP stdio server that turns an AI coding assistant (Claude
|
|
11
|
+
Code, Codex, Gemini CLI) into a manager of local Claude and Codex sub-agents on
|
|
12
|
+
macOS, Linux, and Windows. It drives the locally authenticated `claude` and
|
|
13
|
+
`codex` CLIs you already signed into. It does not make direct HTTP API calls and
|
|
14
|
+
does not require API keys.
|
|
15
|
+
|
|
16
|
+
The orchestrator monitors but does not read or write project files itself. Work
|
|
17
|
+
is delegated to fresh sub-agents, so the orchestrator keeps summaries instead
|
|
18
|
+
of raw file context. The main invariants are:
|
|
19
|
+
|
|
20
|
+
- one machine-global, provider-agnostic concurrency cap (default 20, minimum 10)
|
|
21
|
+
- fail-safe orchestration ON on hookless hosts
|
|
22
|
+
- state authority only from harness-verified `<subagent-mcp state="...">` tags
|
|
23
|
+
- sub-agents gated by default with permission ceiling `auto`
|
|
24
|
+
- automatic model, provider, and effort routing per task category
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
### What You Need First
|
|
29
|
+
|
|
30
|
+
- Node.js 18 or newer (`node --version`)
|
|
31
|
+
- `claude` CLI, installed and signed in (`claude --version`)
|
|
32
|
+
- `codex` CLI, installed and signed in (`codex --version`; optional if you only
|
|
33
|
+
use Claude)
|
|
34
|
+
|
|
35
|
+
Building from source needs extra developer tools. See
|
|
36
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
37
|
+
|
|
38
|
+
### Install The Package
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @heretyc/subagent-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This is the standard install. Organizations pinning the package through GitHub
|
|
45
|
+
Packages should see [docs/registration.md](docs/registration.md).
|
|
46
|
+
|
|
47
|
+
### Wire It Into Your Assistant
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
subagent-mcp setup
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Installing the package only ships the program. It does not connect anything on
|
|
54
|
+
its own. `subagent-mcp setup` finds your Claude Code or Codex install and
|
|
55
|
+
registers both the server and the per-turn orchestration hooks. Preview first
|
|
56
|
+
with `subagent-mcp setup --dry-run`.
|
|
57
|
+
|
|
58
|
+
### Restart, Then Turn On The Invariant
|
|
59
|
+
|
|
60
|
+
Restart your Claude Code or Codex session so it picks up the new tools. On
|
|
61
|
+
Codex, run `/hooks` and trust the new hook. Then, recommended:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
subagent-mcp init --global
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This writes a managed "always delegate" rule block into your global assistant
|
|
68
|
+
config once. For one project only, use
|
|
69
|
+
`subagent-mcp init --root /path/to/project`. Full per-platform wiring (Gemini
|
|
70
|
+
CLI, Claude Desktop, manual setup) is in
|
|
71
|
+
[docs/registration.md](docs/registration.md).
|
|
72
|
+
|
|
73
|
+
## How To Operate It
|
|
74
|
+
|
|
75
|
+
### Orchestration Mode
|
|
76
|
+
|
|
77
|
+
- **ON**: your assistant acts as a pure manager. It delegates every step to
|
|
78
|
+
sub-agents. Best for big, long-running jobs.
|
|
79
|
+
- **OFF**: your assistant works normally, with no delegation rules.
|
|
80
|
+
|
|
81
|
+
Flip it with the `orchestration-mode` tool. Desktop apps can toggle the mode but
|
|
82
|
+
do not receive per-turn hook reminders.
|
|
83
|
+
|
|
84
|
+
### Tools
|
|
85
|
+
|
|
86
|
+
The server exposes `launch_agent`, `poll_agent`, `kill_agent`, `send_message`,
|
|
87
|
+
`list_agents`, `wait`, `respond_permission`, `orchestration-mode`, and
|
|
88
|
+
`model-selection-mode`. See [docs/tools.md](docs/tools.md) for the full
|
|
89
|
+
parameter and return reference.
|
|
90
|
+
|
|
91
|
+
You do not have to choose a model. Give `launch_agent` a prompt and a task
|
|
92
|
+
category such as `coding`, `debugging`, or `security_review`; the server picks
|
|
93
|
+
the provider, model, and effort.
|
|
94
|
+
|
|
95
|
+
### Concurrency
|
|
96
|
+
|
|
97
|
+
There is one machine-wide limit on concurrent sub-agents. The default is 20.
|
|
98
|
+
When the limit is reached, `launch_agent` is rejected immediately and does not
|
|
99
|
+
queue. Change the value in `global-subagent-mcp-config.jsonc` in the install
|
|
100
|
+
folder. The file is re-read on every launch.
|
|
101
|
+
|
|
102
|
+
The config file was renamed from `global-concurrency.jsonc` in 2.12.5. The old
|
|
103
|
+
name is still read, with a one-time deprecation notice, when the new file is
|
|
104
|
+
absent.
|
|
105
|
+
|
|
106
|
+
The same settings file includes `checkForUpdates` (default `true`). Disable it
|
|
107
|
+
with `checkForUpdates: false` or `SUBAGENT_UPDATE_CHECK=0`.
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
Machine-wide defaults live in `global-subagent-mcp-config.jsonc`, installed
|
|
112
|
+
beside the compiled server and re-read on every `launch_agent`. It controls the
|
|
113
|
+
global concurrency cap, update checks, permission ceiling, escalation behavior,
|
|
114
|
+
strict read-parity logging, and Codex sandbox networking.
|
|
115
|
+
|
|
116
|
+
User and repo permission files can only tighten or add scoped permissions on top
|
|
117
|
+
of the global ceiling. See [README/configuration.md](README/configuration.md)
|
|
118
|
+
for the full key table, precedence rules, and mode summary.
|
|
119
|
+
|
|
120
|
+
## Permissions
|
|
121
|
+
|
|
122
|
+
Launched sub-agents run gated by default. Set `permissionsCeiling` in
|
|
123
|
+
`global-subagent-mcp-config.jsonc`:
|
|
124
|
+
|
|
125
|
+
| Mode | What a sub-agent can do |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `auto` | Default. Safe reads auto-allow, dangerous actions auto-deny, everything else parks for your decision. |
|
|
128
|
+
| `manual` | Same, but every non-denied action parks for a decision. |
|
|
129
|
+
| `yolo` | No gating at all. |
|
|
130
|
+
|
|
131
|
+
When a sub-agent's action parks, its status becomes `permission_requested` and
|
|
132
|
+
it appears in `poll_agent`, `list_agents`, and `wait`. Answer it with:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
respond_permission(agent_id="...", decision="allow" | "deny", reason="...")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
One-time only. Omit `request_id` to answer the oldest pending request.
|
|
139
|
+
Unanswered requests auto-deny after 5 minutes. Full spec:
|
|
140
|
+
[docs/spec/permissions.md](docs/spec/permissions.md).
|
|
141
|
+
|
|
142
|
+
## Basic Debugging
|
|
143
|
+
|
|
144
|
+
- **An agent looks stuck.** A quiet agent is usually still alive. After about 10
|
|
145
|
+
minutes with no output an agent is marked `stalled`. Prefer `wait` or another
|
|
146
|
+
`poll_agent` over killing it.
|
|
147
|
+
- **Cap reached.** Use `list_agents` to see what is running and `kill_agent` on
|
|
148
|
+
work you no longer need. Raising `globalConcurrentSubagents` also works.
|
|
149
|
+
- **Logs.** Agent output is available through `poll_agent`. Server diagnostics
|
|
150
|
+
go to the host MCP server log on stderr.
|
|
151
|
+
|
|
152
|
+
## Documentation
|
|
153
|
+
|
|
154
|
+
| Document | Contents |
|
|
155
|
+
|---|---|
|
|
156
|
+
| [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md) | Design rationale |
|
|
157
|
+
| [docs/registration.md](docs/registration.md) | Per-platform setup |
|
|
158
|
+
| [docs/tools.md](docs/tools.md) | Tool reference |
|
|
159
|
+
| [docs/usage.md](docs/usage.md) | Model and effort matrix |
|
|
160
|
+
| [docs/SPEC.md](docs/SPEC.md) | Technical specification |
|
|
161
|
+
| [README/configuration.md](README/configuration.md) | Configuration keys and precedence |
|
|
162
|
+
| [docs/spec/permissions.md](docs/spec/permissions.md) | Permission system |
|
|
163
|
+
| [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md) | Agent status meanings |
|
|
164
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Developer guide |
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
Apache-2.0. Copyright 2026 Lexi Blackburn.
|
|
169
|
+
|
|
170
|
+
See [LICENSE](LICENSE).
|
package/dist/advanced-ruleset.py
CHANGED
|
@@ -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, 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()
|
|
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()
|
package/dist/concurrency.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, platform, userInfo } from "node:os";
|
|
4
4
|
import { dirname, join, parse as parsePath, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -26,7 +26,50 @@ export function clampCap(raw) {
|
|
|
26
26
|
return v;
|
|
27
27
|
}
|
|
28
28
|
export function stripJsoncComments(text) {
|
|
29
|
-
|
|
29
|
+
let out = "";
|
|
30
|
+
let inString = false;
|
|
31
|
+
let quote = null;
|
|
32
|
+
let escaped = false;
|
|
33
|
+
for (let i = text.startsWith("\uFEFF") ? 1 : 0; i < text.length; i++) {
|
|
34
|
+
const ch = text[i];
|
|
35
|
+
const next = text[i + 1];
|
|
36
|
+
if (inString) {
|
|
37
|
+
out += ch;
|
|
38
|
+
if (escaped) {
|
|
39
|
+
escaped = false;
|
|
40
|
+
}
|
|
41
|
+
else if (ch === "\\") {
|
|
42
|
+
escaped = true;
|
|
43
|
+
}
|
|
44
|
+
else if (ch === quote) {
|
|
45
|
+
inString = false;
|
|
46
|
+
quote = null;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (ch === "\"" || ch === "'") {
|
|
51
|
+
inString = true;
|
|
52
|
+
quote = ch;
|
|
53
|
+
out += ch;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === "/" && next === "/") {
|
|
57
|
+
while (i < text.length && text[i] !== "\n" && text[i] !== "\r")
|
|
58
|
+
i++;
|
|
59
|
+
i--;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "/" && next === "*") {
|
|
63
|
+
i += 2;
|
|
64
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
65
|
+
i++;
|
|
66
|
+
if (i < text.length)
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out += ch;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
30
73
|
}
|
|
31
74
|
function parseJsonObject(text) {
|
|
32
75
|
const parsed = JSON.parse(stripJsoncComments(text));
|
|
@@ -591,6 +634,74 @@ export function countSlots(dir = slotDir()) {
|
|
|
591
634
|
return 0;
|
|
592
635
|
}
|
|
593
636
|
}
|
|
637
|
+
function slotPathForIndex(dir, index) {
|
|
638
|
+
return join(dir, `slot-${index}.json`);
|
|
639
|
+
}
|
|
640
|
+
function slotMetadataJson(agentId) {
|
|
641
|
+
const now = Date.now();
|
|
642
|
+
return JSON.stringify({
|
|
643
|
+
schema_version: 1,
|
|
644
|
+
agent_id: agentId,
|
|
645
|
+
server_pid: process.pid,
|
|
646
|
+
child_pid: null,
|
|
647
|
+
cwd: process.cwd(),
|
|
648
|
+
started_at: new Date(now).toISOString(),
|
|
649
|
+
started_at_ms: now,
|
|
650
|
+
last_activity_ms: now,
|
|
651
|
+
status: null,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function claimSlotPath(dir, agentId, max) {
|
|
655
|
+
let existing;
|
|
656
|
+
try {
|
|
657
|
+
existing = readdirSync(dir).filter((f) => f.startsWith("slot-"));
|
|
658
|
+
}
|
|
659
|
+
catch {
|
|
660
|
+
existing = [];
|
|
661
|
+
}
|
|
662
|
+
if (existing.length >= max)
|
|
663
|
+
return null;
|
|
664
|
+
const occupied = new Set(existing);
|
|
665
|
+
const candidateCount = max - existing.length;
|
|
666
|
+
const candidates = [];
|
|
667
|
+
for (let i = 0; i < max && candidates.length < candidateCount; i++) {
|
|
668
|
+
const name = `slot-${i}.json`;
|
|
669
|
+
if (!occupied.has(name))
|
|
670
|
+
candidates.push(slotPathForIndex(dir, i));
|
|
671
|
+
}
|
|
672
|
+
for (const path of candidates) {
|
|
673
|
+
let fd = null;
|
|
674
|
+
try {
|
|
675
|
+
fd = openSync(path, "wx", 0o600);
|
|
676
|
+
writeFileSync(fd, slotMetadataJson(agentId));
|
|
677
|
+
return path;
|
|
678
|
+
}
|
|
679
|
+
catch (e) {
|
|
680
|
+
if (fd !== null) {
|
|
681
|
+
try {
|
|
682
|
+
closeSync(fd);
|
|
683
|
+
}
|
|
684
|
+
catch { }
|
|
685
|
+
fd = null;
|
|
686
|
+
try {
|
|
687
|
+
unlinkSync(path);
|
|
688
|
+
}
|
|
689
|
+
catch { }
|
|
690
|
+
}
|
|
691
|
+
if (e.code !== "EEXIST")
|
|
692
|
+
throw e;
|
|
693
|
+
}
|
|
694
|
+
finally {
|
|
695
|
+
if (fd !== null) {
|
|
696
|
+
try {
|
|
697
|
+
closeSync(fd);
|
|
698
|
+
}
|
|
699
|
+
catch { }
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
594
705
|
export function scheduleForceKill(ms, kill) {
|
|
595
706
|
const timer = setTimeout(kill, ms);
|
|
596
707
|
timer.unref();
|
|
@@ -608,21 +719,11 @@ export function reserveSlot(agentId, max, dir = slotDir(), cullDeps) {
|
|
|
608
719
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
609
720
|
}
|
|
610
721
|
cullStaleSlots(dir, cullDeps);
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
if (
|
|
614
|
-
return { ok: false, current: before, max };
|
|
615
|
-
}
|
|
616
|
-
// ponytail: count->write->recount narrows the TOCTOU; a cross-process lock would close it fully.
|
|
617
|
-
writeSlotMetadata(slotPath, { agent_id: agentId });
|
|
618
|
-
const after = countSlots(dir);
|
|
619
|
-
if (after > max) {
|
|
620
|
-
try {
|
|
621
|
-
unlinkSync(slotPath);
|
|
622
|
-
}
|
|
623
|
-
catch { }
|
|
722
|
+
// ponytail: numbered O_EXCL slot claims make admission atomic without queues or a process-wide lock.
|
|
723
|
+
const slotPath = claimSlotPath(dir, agentId, max);
|
|
724
|
+
if (!slotPath)
|
|
624
725
|
return { ok: false, current: countSlots(dir), max };
|
|
625
|
-
|
|
726
|
+
const after = countSlots(dir);
|
|
626
727
|
return { ok: true, slotPath, current: after, max };
|
|
627
728
|
}
|
|
628
729
|
catch (e) {
|
package/dist/config-scaffold.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERATED by scripts/gen-ruleset-scaffold.mjs from src/global-subagent-mcp-config.jsonc — DO NOT EDIT.
|
|
2
|
-
export const CONCURRENCY_SCAFFOLD = "// subagent-mcp - Global Subagent MCP Config\
|
|
2
|
+
export const CONCURRENCY_SCAFFOLD = "// subagent-mcp - Global Subagent MCP Config\n// ------------------------------------------------------------------\n// SOLE source of truth for machine-wide subagent-mcp defaults that must be\n// available beside the compiled server.\n//\n// globalConcurrentSubagents controls how many subagents may be ALIVE AT ONCE\n// across EVERY session, process, and user on this machine. There is NO\n// environment-variable override for the cap.\n//\n// RE-READ on every launch_agent call - edits take effect immediately, no\n// server restart required.\n//\n// Cap value rules:\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// 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// permissionsCeiling controls launch-time permissions posture:\n// - auto (default): shared engine gates unsafe/residue actions\n// - manual: human approval for residue while still auto-denying DANGER\n// - yolo: preserve the historical bypass/danger-full-access path, except\n// for non-bypassable config-file self-protection\n//\n// escalation applies only in auto mode. irreversible-only routes irreversible\n// NEUTRAL residue to a human; off leaves residue to orchestrator judgment.\n//\n// strictReadParity controls logging only. Unparseable Codex approval payloads\n// always fail closed to ask; warn logs the construct, off silences that log.\n//\n// sandboxNetwork controls Codex workspace-write network access. Default false.\n// Set true only when sub-agents need network after permission approval.\n{\n \"globalConcurrentSubagents\": 20,\n \"checkForUpdates\": true,\n \"permissionsCeiling\": \"auto\",\n \"escalation\": \"irreversible-only\",\n \"strictReadParity\": \"warn\",\n \"sandboxNetwork\": false\n}\n";
|