Skip to content

feat(subagent): wire per-task tool-allowlist into SpawnContext inheritance#6530

Merged
bug-ops merged 2 commits into
mainfrom
fix/6504-subagent-tool-allowlist
Jul 20, 2026
Merged

feat(subagent): wire per-task tool-allowlist into SpawnContext inheritance#6530
bug-ops merged 2 commits into
mainfrom
fix/6504-subagent-tool-allowlist

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • PR fix(subagent): close secret-leak, trust-cap, and finalize-skip gaps #6503 wired SpawnContext::max_trust_level from 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 left SpawnContext::inherited_tool_allowlist unset since no per-task allowlist concept existed to source it from.
  • Adds TaskNode::tool_allowlist: Option<Vec<String>> (crates/zeph-orchestration/src/graph.rs) and populates SpawnContext::inherited_tool_allowlist from it in handle_scheduler_spawn_action (crates/zeph-core/src/agent/scheduler_loop.rs), mirroring the existing network_denied wiring.
  • The consumer, apply_constraint_propagation (crates/zeph-subagent/src/manager/spawn.rs:221-292), already intersected this field correctly and is unchanged.
  • Empty-allowlist semantics are fail-closed by design: None = no per-task narrowing, Some(vec![]) narrows the sub-agent to zero tools — verified at both the intersection layer and the filter.rs enforcement layer.

Scope note (important)

This PR wires the orchestration-policy plumbing only. Nothing currently populates TaskNode::tool_allowlist in production — no planner, config, or parent-session PermissionRule-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 own PermissionRule deny lists still spawning unnarrowed sub-agents) is not yet closed by production behavior change. Two follow-up issues track the deferred population sources:

Test plan

  • TaskNode.tool_allowlist serde default/roundtrip/empty-vs-None-distinctness tests (graph.rs)
  • task_tool_allowlist() helper: None task, task with None field, populated task (scheduler_loop.rs)
  • apply_constraint_propagation empty-set fail-closed across InheritAll/AllowList/DenyList starting policies (manager/tests.rs)
  • filter.rs enforcement-layer test confirming an empty AllowList blocks every tool via the real dispatch path
  • cargo +nightly fmt --check, cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings, full-workspace cargo nextest, rustdoc gate — all clean
  • Full team-develop pass: architect → critic (design, minor) → developer → tester/perf/security/impl-critic (parallel, all PASS/approved) → reviewer (changes_requested → fixed clippy too_many_lines regression → approved)

Closes #6504

@github-actions github-actions Bot added enhancement New feature or request size/M Medium PR (51-200 lines) documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate and removed size/M Medium PR (51-200 lines) labels Jul 19, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 19, 2026 23:54
@bug-ops
bug-ops force-pushed the fix/6504-subagent-tool-allowlist branch from e997843 to 7b65438 Compare July 19, 2026 23:57
@github-actions github-actions Bot added the size/M Medium PR (51-200 lines) label Jul 19, 2026
bug-ops added 2 commits July 20, 2026 02:07
…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
bug-ops force-pushed the fix/6504-subagent-tool-allowlist branch from 7b65438 to 22664bb Compare July 20, 2026 00:08
@bug-ops
bug-ops merged commit fa7b949 into main Jul 20, 2026
83 of 87 checks passed
@bug-ops
bug-ops deleted the fix/6504-subagent-tool-allowlist branch July 20, 2026 00:18
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core zeph-core crate documentation Improvements or additions to documentation enhancement New feature or request rust Rust code changes size/M Medium PR (51-200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sub-agent per-task tool-allowlist inheritance not yet wired (deferred half of #6493)

1 participant