#23506: feat(spawn): per-spawn tool permissions for sessions_spawn
app: web-ui
gateway
agents
size: M
Cluster:
Tool and Plugin Enhancements
## Summary
Closes #15032.
- Adds optional `tools: { allow?: string[], deny?: string[] }` param to `sessions_spawn`
- Stores per-spawn tool policy on the child session via `sessions.patch` (`spawnToolPolicy`, immutable once set, subagent-only)
- Merges per-spawn tools with global `tools.subagents.tools` config in `resolveSubagentToolPolicy`: per-spawn allow overrides global allow, per-spawn deny appends to global deny, deny always beats allow
- Existing callers are unaffected (param is optional, no behavior change without it)
## Files changed
| Layer | Files |
|-------|-------|
| Tool schema | `sessions-spawn-tool.ts` |
| Spawn params | `subagent-spawn.ts` |
| Data model | `sessions/types.ts`, `protocol/schema/sessions.ts`, `sessions-patch.ts` |
| Policy merge | `pi-tools.policy.ts` |
| Wiring | `pi-tools.ts`, `subagent-depth.ts` |
| Tests | new e2e test (6 tests), policy merge unit tests (7 tests) |
## Test plan
- [x] All 4 sessions-spawn e2e test suites pass (22 tests)
- [x] sessions-patch unit tests pass (16 tests)
- [x] pi-tools.policy e2e tests pass (30 tests, including 7 new per-spawn merge tests)
- [x] TypeScript compiles cleanly (no new errors)
- [x] Existing allowlist/model/thinking e2e tests unaffected
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added per-spawn tool permissions to `sessions_spawn`, allowing fine-grained control over which tools each spawned subagent can use. The implementation stores the per-spawn policy in the session state via `sessions.patch` (immutable once set), then merges it with global subagent tool configuration when building the tool list. Per-spawn `allow` overrides global `allow`, per-spawn `deny` appends to global `deny`, and deny always beats allow.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is well-designed with comprehensive test coverage (13 new tests covering all merge scenarios), proper immutability guarantees (spawnToolPolicy cannot be changed once set), and clean integration with existing systems. The feature is opt-in with no behavior changes for existing callers.
- No files require special attention
<sub>Last reviewed commit: eeda68d</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#13331: feat(sessions_spawn): add sessionKey param to reuse sub-agent sessions
by Be1Human · 2026-02-10
80.1%
#14734: test(agents): guard against stale allowAgents in existing sessions
by davidahmann · 2026-02-12
77.7%
#10748: feat: Add sessions.spawn gateway method for direct subagent spawning
by fox-openclaw · 2026-02-06
77.2%
#16247: feat(agents): declarative agent definitions for sessions_spawn
by zerone0x · 2026-02-14
76.4%
#17921: feat: add spawnableBy bidirectional sub-agent spawn authorization
by jacobot01 · 2026-02-16
75.9%
#20072: feat(sessions_spawn): add sessionKey param to reuse sub-agent sessions
by Be1Human · 2026-02-18
75.9%
#21556: fix(agents): graceful fallback when spawned model is not in allowlist
by irchelper · 2026-02-20
75.6%
#16558: feat(plugins): add sessions.spawn and rateLimit to plugin runtime
by Zephyr-Blessed · 2026-02-14
74.0%
#19833: feat: contextScripts pre-spawn hook for sub-agent sessions (rebased...
by geilt · 2026-02-18
73.8%
#13303: feat(subagents): replace silent boolean with announce enum ('user'|...
by ivalsaraj · 2026-02-10
73.2%