feat(subagent): wire per-task tool-allowlist into SpawnContext inheritance#6530
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 19, 2026 23:54
bug-ops
force-pushed
the
fix/6504-subagent-tool-allowlist
branch
from
July 19, 2026 23:57
e997843 to
7b65438
Compare
…tance PR #6503 wired SpawnContext::max_trust_level from the parent session's own effective trust level, closing the trust-cap half of #6493, but left inherited_tool_allowlist unset since no per-task allowlist concept existed to source it from. Add TaskNode::tool_allowlist (Option<Vec<String>>) in zeph-orchestration and populate SpawnContext::inherited_tool_allowlist from it in handle_scheduler_spawn_action, mirroring the existing network_denied wiring. The consumer, apply_constraint_propagation, already intersected this field correctly and is unchanged. This closes the orchestration-policy plumbing only: nothing yet populates TaskNode::tool_allowlist from a planner, config, or the parent session's own PermissionRule deny lists, so production sub-agent spawns are unaffected until a follow-up populates the field (tracked in #6526 and #6527). Closes #6504
The tool-allowlist wiring added in the previous commit pushed this tightly-coupled dispatch function from 100 to 102 lines, tripping clippy::too_many_lines under -D warnings. Matches the established codebase convention for this function shape (see zeph-llm/src/router/chat.rs, provider_impl.rs).
bug-ops
force-pushed
the
fix/6504-subagent-tool-allowlist
branch
from
July 20, 2026 00:08
7b65438 to
22664bb
Compare
bug-ops
added a commit
that referenced
this pull request
Jul 20, 2026
… test literal git's line-based rebase auto-merge silently kept both this branch's and the already-landed PR #6530's insertion of tool_allowlist into the same test-only TaskNode struct literal, since they landed at different line offsets with no overlapping context. Only cargo clippy --all-targets surfaces the resulting E0062 duplicate-field error.
bug-ops
added a commit
that referenced
this pull request
Jul 20, 2026
… test literal git's line-based rebase auto-merge silently kept both this branch's and the already-landed PR #6530's insertion of tool_allowlist into the same test-only TaskNode struct literal, since they landed at different line offsets with no overlapping context. Only cargo clippy --all-targets surfaces the resulting E0062 duplicate-field error.
bug-ops
added a commit
that referenced
this pull request
Jul 20, 2026
… test literal git's line-based rebase auto-merge silently kept both this branch's and the already-landed PR #6530's insertion of tool_allowlist into the same test-only TaskNode struct literal, since they landed at different line offsets with no overlapping context. Only cargo clippy --all-targets surfaces the resulting E0062 duplicate-field error.
bug-ops
added a commit
that referenced
this pull request
Jul 20, 2026
…ns and planner (#6538) * feat(subagent): derive tool-allowlist producers from parent permissions and planner Adds two producers for SpawnContext::inherited_tool_allowlist, composed by intersection via a new intersect_allowlists helper (narrow-only, never widens): - PermissionPolicy::effective_tool_allowlist derives a defense-in-depth floor from the parent session's own wholesale-deny PermissionRule entries, wired into build_spawn_context so it covers every sub-agent spawn path. Every child tool call is already re-checked against the parent's TrustGate at runtime; this narrows what the child's LLM even sees. - TaskNode.tool_allowlist is now populated by the orchestration planner (PlannedTask.tool_allowlist), intersected in handle_scheduler_spawn_action. Unrecognized planner-emitted tool names are dropped with a warning rather than collapsing the allowlist to zero tools. Closes #6526 Closes #6527 * fix(orchestration): remove duplicate tool_allowlist field from router test literal git's line-based rebase auto-merge silently kept both this branch's and the already-landed PR #6530's insertion of tool_allowlist into the same test-only TaskNode struct literal, since they landed at different line offsets with no overlapping context. Only cargo clippy --all-targets surfaces the resulting E0062 duplicate-field error.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SpawnContext::max_trust_levelfrom the parent session's own effective trust level, closing the trust-cap half of High: sub-agent constraint-propagation trust cap and tool-allowlist inheritance are never wired up in production #6493, but leftSpawnContext::inherited_tool_allowlistunset since no per-task allowlist concept existed to source it from.TaskNode::tool_allowlist: Option<Vec<String>>(crates/zeph-orchestration/src/graph.rs) and populatesSpawnContext::inherited_tool_allowlistfrom it inhandle_scheduler_spawn_action(crates/zeph-core/src/agent/scheduler_loop.rs), mirroring the existingnetwork_deniedwiring.apply_constraint_propagation(crates/zeph-subagent/src/manager/spawn.rs:221-292), already intersected this field correctly and is unchanged.None= no per-task narrowing,Some(vec![])narrows the sub-agent to zero tools — verified at both the intersection layer and thefilter.rsenforcement layer.Scope note (important)
This PR wires the orchestration-policy plumbing only. Nothing currently populates
TaskNode::tool_allowlistin production — no planner, config, or parent-sessionPermissionRule-derivation path exists yet. Production sub-agent spawns are therefore unaffected by this PR alone, and the original #6504 reproduction (a parent restricted by its ownPermissionRuledeny lists still spawning unnarrowed sub-agents) is not yet closed by production behavior change. Two follow-up issues track the deferred population sources:TaskNode.tool_allowlistPermissionRuledeny lists (harder problem: requires enumerating the parent's complete effective tool universe)Test plan
TaskNode.tool_allowlistserde default/roundtrip/empty-vs-None-distinctness tests (graph.rs)task_tool_allowlist()helper:Nonetask, task withNonefield, populated task (scheduler_loop.rs)apply_constraint_propagationempty-set fail-closed acrossInheritAll/AllowList/DenyListstarting policies (manager/tests.rs)filter.rsenforcement-layer test confirming an emptyAllowListblocks every tool via the real dispatch pathcargo +nightly fmt --check,cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings, full-workspacecargo nextest, rustdoc gate — all cleantoo_many_linesregression → approved)Closes #6504