Skip to content

feat(synth): deny subagent spawning for specialist agents - #124

Draft
knihit wants to merge 30 commits into
feat/specialists-return-routingfrom
feat/deny-specialist-spawn
Draft

knihit wants to merge 30 commits into
feat/specialists-return-routingfrom
feat/deny-specialist-spawn

Conversation

@knihit

@knihit knihit commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

Stack: 4 of 12 in the orchestration stack. Base: feat/specialists-return-routing (#123). Depends on #114, the memory PR and #123. #123 tells specialists not to spawn; this PR removes the tools that let them. The Kiro CLI v2 half lands at the top of the stack (feat/strip-spawn-v2-specialists).

On GPT models under Kiro CLI v3, specialists re-delegate to orchestrators and cascade to the depth limit. A prompt rule alone does not hold for every model, so specialist configs now carry no agent-spawning tool. Orchestrator output is unchanged.

Agents:

  • k-architect, k-developer, k-media-analyzer, k-product-manager, k-quality-assurance, k-researcher, k-tpm: dropped the subagent tool, toolsSettings.subagent and the Claude Code Agent(...) grants. k-architect also drops dependencies.subagents.

CLI:

  • synth/model.rs: is_specialist(name), true for k-* names.
  • synth/kiro_cli_v3.rs: for specialists, drop subagent tags and subagent permission rules, and emit excludedTools: ["invoke_sub_agent", "run_workflow"]. A subagent deny rule alone does not block: @builtin also carries run_workflow, which accepts agent://<name> and starts any agent, and no permission rule gates it. excludedTools held under --trust-all-tools. The field is skipped when empty, so orchestrator files serialize as before.
  • synth/claude.rs: for specialists, filter Agent, Task and Workflow (bare or scoped) out of tools. A specialist with no tools list gets them as disallowedTools.
  • synth/parse_canonical.rs: synth warns for each k-* spec that grants a spawn tool (Kiro subagent tag or alias, toolsSettings.subagent, Claude Agent/Task/Workflow), so a spec and its output cannot disagree silently.

SOPs:

  • agent-sops/k-code-review-workflow.sop.md step 6: returns the adversarial pass as a handoff for k-architect instead of skipping it. The handoff carries the review's parameters, the PR URL, the resolved base branch, the Step 1 diff, the Step 2 file counts and the Step 5 list; a run resumed at step 6 merges k-architect's findings and continues to Step 7.

Docs / Other:

  • docs/user-guide/agents.md: delegation section and table rewritten. Only orchestrators spawn, and the section says how each runtime enforces it. Matching spawn and step 6 facts in concepts.md, faq.md, glossary.md, sop-workflows/code-review.md, use-cases/plan-and-verify-work.md and use-cases/review-code-and-tests.md; a standalone adversarial pass is routed through konductor.
  • CHANGELOG.md: one [Unreleased] bullet. No VERSION change.

Type of Change

  • New or updated agent spec
  • New or updated agent SOP
  • CLI (cli/) change: Rust
  • Documentation

Testing

  • npm test: not run on the final head (no JS changed). On an earlier head, 119 of 136 passed; the same 17 fail on unmodified main (missing generated scripts/brand-config/lib/constants.js).
  • cd cli && make test passes
  • Gate on this branch's head, and on the top of the stack (feat/strip-spawn-v2-specialists, which contains it): make build && make synth, cd cli && make test (1,873 Rust tests on the top, 0 failed), make test-skills-sh, uvx --with pyyaml pytest -q tests/ (281 passed, 1 skipped on the top), make claude-plugin-check. All pass.
  • Smoke tested: installed into a scratch project with konductor install --from <tree> and ran headless spawn attempts (see Runtime check)
  • npm run benchmark: not run. A 72-run A/B was run instead; see Benchmark.
  • Changed code files carry the required SPDX header (no new code files)

New tests: render_agent_file_strips_spawn_grants_from_specialist and render_agent_file_keeps_spawn_grants_for_orchestrator (kiro_cli_v3.rs); render_agent_md_strips_spawn_tools_from_specialist, render_agent_md_denies_spawn_tools_for_specialist_without_tools_list and render_agent_md_keeps_spawn_tools_for_orchestrator (claude.rs); is_specialist_matches_k_prefix_only (model.rs); installed_specialists_cannot_spawn_but_orchestrator_can (tests/synth_install_e2e.rs, hermetic, --from a local tree, --no-telemetry); test_specialists_hold_no_spawn_grant, which rejects every spawn alias (subagent, use_subagent, agent_crew, *, bare Agent, Task, Workflow and Agent(...)) in kiroCli.tools, kiroCli.allowedTools, toolsSettings and claudeCli.tools.

Orchestrator check: dist/ output for the three orchestrators on Kiro CLI v3, Kiro CLI v2 and Claude Code is byte-identical to a synth of the parent commit built with its own binary.

Runtime check

Each variant was installed into a scratch project and asked to spawn an agent that writes a file. Blocked means no file and no successful spawn call.

Runtime Config Blocked
Kiro CLI v3 as on main (scoped subagent allow) no, under --trust-all-tools a specialist spawned konductor, which spawned k-developer
Kiro CLI v3 subagent tag dropped, or a subagent deny rule no, the model used run_workflow with agent://k-developer
Kiro CLI v3 adds a workflow deny rule no, run_workflow is not gated by it
Kiro CLI v3 excludedTools: [invoke_sub_agent, run_workflow] (this PR) yes, also under --trust-all-tools
Claude Code as on main (Agent(k-developer, k-quality-assurance)) no for listed names, yes for others
Claude Code Agent(...) removed from tools (this PR) yes, also under --dangerously-skip-permissions and when nested under konductor

Benchmark

Setup: main vs this branch (with #123), three cells (a GPT model on Kiro CLI v3, a Claude Sonnet model on Kiro CLI v3, a Claude Sonnet model on Claude Code), 4 cases, 3 reps each, fresh isolated project and HOME per run. 72 runs.

cell n spawns/run spec to spec depth-limit rejections cost/run wall s/run success
GPT, Kiro CLI v3 main 12 7.9 30 6 59.2 credits 478 9/12
GPT, Kiro CLI v3 branch 12 3.2 0 0 30.5 credits 253 9/12
Claude Sonnet, Kiro CLI v3 main 12 1.9 0 0 5.2 credits 122 10/12
Claude Sonnet, Kiro CLI v3 branch 12 2.0 0 0 5.0 credits 120 8/12
Claude Sonnet, Claude Code main 12 1.8 0 0 $0.87 105 11/12
Claude Sonnet, Claude Code branch 12 1.9 0 0 $0.93 114 11/12

On the GPT cell, spawns, credits and wall time roughly halved with task success unchanged; most of the gap is the design plus threat model case (18.7 vs 3.3 spawns per run, 128.6 vs 37.6 credits). The Claude cells did not change. The Claude on Kiro CLI v3 drop on the implement, document and review case was re-run 5 more reps per side (main 2/5, branch 5/5) and reads as variance. n=3 per case. The step 6 handoff and the synth warning came after the bench.

Checklist

  • My code follows the style guidelines of this project (see AGENTS.md's Code Style & Conventions)
  • I have performed a self-review of my own changes
  • I have commented my code where necessary
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings: build is clean, and cd cli && make lint passes
  • I have added or updated tests that prove my change works (see Testing above)

Agents Affected

Orchestrator output is unchanged; only specialist configs change.

  • k-developer
  • k-architect
  • k-researcher
  • k-product-manager
  • k-quality-assurance
  • k-tpm
  • k-browser (synth only; its spec had no spawn grant, but Kiro CLI v3 output now excludes run_workflow)
  • k-media-analyzer

Notes for Reviewers

  • Review: adversarial review in rounds until 0 CRITICAL and 0 IMPORTANT findings remained (5 rounds). The IMPORTANT findings were both about code review step 6: the docs said it runs under konductor while the SOP told k-developer to skip it, and then the handoff did not say how the resumed run finishes. Both are fixed in the SOP. Every finding has a fix commit or a written reason.
  • Kiro CLI v2 gets the spec change here; its synth strip is feat/strip-spawn-v2-specialists at the top of this stack.
  • Merging main (fix(claude): write Claude Code model IDs and ship NOTICE.txt in the plugin #105) into this stack conflicted in claude.rs; both sides were kept (SPAWN_TOOLS and is_spawn_tool next to the model ID helpers, and model next to tools in the render call).
  • Not covered: a specialist shelling out to kiro-cli chat or claude -p through its shell tool.

Specialist (k-*) agent configs no longer carry any agent-spawning tool.
Orchestrator output is unchanged.

- Specs: drop the subagent tool, toolsSettings.subagent and Agent(...)
  grants from every k-* spec, and dependencies.subagents from k-architect.
- Kiro CLI v3 synth: for k-* agents, drop subagent tags and subagent
  permission rules, and emit excludedTools [invoke_sub_agent,
  run_workflow]. A subagent deny rule alone does not block, because
  @Builtin also carries run_workflow, which starts any agent.
- Claude Code synth: for k-* agents, drop Agent, Task and Workflow
  (bare or scoped) from tools; with no tools list, emit them as
  disallowedTools.
- Docs: user guide delegation sections now say only orchestrators spawn.

Kiro CLI v2 gets the spec change but no extra synth deny; that runtime
was not checked.

Verified:
- make build, make synth, make test-skills-sh, make claude-plugin-check:
  pass
- make test-rust: pass (2180 tests, 0 failed)
- make test-py: pass (223), make kiro-power-check: pass
- make guide-check: one failure that also exists on the base branch
  (withdrawn `config` command list), unrelated
- Orchestrator files in dist/ for kiro-v3, kiro-cli-v2 and claude are
  byte-identical to a synth of the parent commit built with its own binary
@knihit
knihit removed this pull request from stack #125 October 29, 2026 04:37
@knihit
knihit force-pushed the feat/deny-specialist-spawn branch from e5fd8c5 to dfbcba4 Compare October 29, 2026 04:37
@knihit
knihit added this pull request to stack #134 October 29, 2026 04:38
Nihit Kasabwala added 22 commits October 30, 2026 01:53
Set every k-* card's "Can delegate to" to nothing, drop subagent from the
k-researcher Kiro tools row, and stop citing subagent target lists as
specialist data (I1).

Say the block covers the runtime spawn tools, not shell (S2), and that the
Kiro CLI v3 excludedTools field was tested with kiro-cli 2.35.0 and later
(S3). Scope the config-enforcement claim to Kiro CLI v3 and Claude Code: a
Kiro CLI v2 probe on kiro-cli 2.36.0 showed @Builtin still carries the
subagent tool (I2, docs half).
The specialist check now rejects subagent, use_subagent, agent_crew and *
in kiroCli.tools and toolsSettings, and bare Agent, Task and Workflow as
well as Agent(...) in claudeCli.tools (I2, test half).
konductor synth now prints a warning for each k-* agent whose spec grants a
spawn tool (Kiro subagent tag or alias, toolsSettings.subagent, Claude
Agent/Task/Workflow), so the spec and the synthed output cannot disagree
silently (S1). The optional role: spec field is declined for now: it is a
schema change outside this PR, and the review marks it as later work.
No content change. Verification, run from the worktree root on a1117bc
(tree identical to this commit):
- make build && make synth: pass (11 agents, 82 skills, 19 SOPs, no synth
  warnings)
- cd cli && make test: pass on rerun (1789 passed, 0 failed). The first run
  had one failure, telemetry_hook_scoping
  global_and_project_hooks_count_each_invocation_once ("failed to package
  dist/ tree: No such file or directory"); it passed alone and in the full
  rerun, and the test does not touch the changed code.
- make test-skills-sh: pass (0 failed)
- uvx --with pyyaml pytest -q tests/: pass (226 passed)
- make claude-plugin-check: pass

Kiro CLI v2 probe (kiro-cli 2.36.0, --agent-engine v2, --trust-all-tools,
specialist spawn rule removed from the scratch copy of k-core-rules.md):
k-architect as synthed here spawned k-developer through the subagent tool.
The v2 output is not covered by this branch, and the docs say so.
The render_frontmatter helper was inserted between the doc comment and the test it
describes.
Kiro CLI v2 output still keeps them on this branch, so the warning now gives the reason
instead: specialists must not spawn.
…ran on

The v3 probe ran on kiro-cli 2.35.0 only; "and later" was an inference.

Verified at this commit from the worktree root:
- make build && make synth: pass
- cd cli && make test: pass (cargo 1789 passed, 0 failed)
- make test-skills-sh: pass (85 passed, 0 failed)
- uvx --with pyyaml pytest -q tests/: 226 passed
- make claude-plugin-check: validation passed
The guide said the adversarial step always no-ops and that routing
through konductor cannot help. Under the core rules k-developer returns
step 6 as a handoff that konductor routes to k-architect. The pages now
quote the SOP's actual skip constraint.
The synth warning checks allowedTools, and a spawn tool there would
pre-approve spawning on Kiro CLI v2. The spec guard now checks it too.
Ran: uvx --with pyyaml pytest -q tests/test_agent_tool_subagent_references.py (6 passed).
…untime

The specs carry no spawn grant on any runtime; the configs block spawn
tools only on Kiro CLI v3 and Claude Code, as the entry goes on to say.
…ist-spawn

# Conflicts:
#	docs/user-guide/concepts.md
…of skipping it

The docs said step 6 runs under konductor, but the SOP told k-developer to
skip it because it cannot load k-adversarial-pull-request-review. Step 6 now
returns a handoff for k-architect, and the four doc pages describe that,
rewrapped at 100 columns.
The handoff now carries the Step 5 consolidated list, and a run resumed at
step 6 with k-architect's findings merges them and continues to Step 7
instead of handing off again.
Four pages stopped at "it comes back to you". Each now says to run the
adversarial review with k-architect, then hand its findings and the handoff
to k-developer to resume k-code-review-workflow at step 6.

Gate (this tree): make build && make synth ok; cd cli && make test 1789
passed; make test-skills-sh 85 passed; uvx --with pyyaml pytest -q tests/
226 passed; make claude-plugin-check ok.
…tep 6 handoff

A resumed k-developer is a fresh agent; without output_file and the Step 2
counts it could write the report to the default path or omit the count
line. Long finding lists go to a handoff file per delegation-protocol.
The diff_input, pr_url and frontend-only constraints concern launching the
review and have nothing to act on in a resumed run.
…ctor

k-architect no longer spawns, so started directly it returns the
adversarial review's passes as a split instead of findings. The four
direct-start paragraphs now send the user to konductor and say what
k-architect does on its own.
Nihit Kasabwala added 7 commits October 31, 2026 14:11
…ist-spawn

Brings PR 1 (project memory is project content) up from #123. No conflicts.

Gate: make build && make synth ok; cli make test 1789 passed, 0 failed;
make test-skills-sh 10 passed; pytest 251 passed, 1 skipped;
make claude-plugin-check ok.
…ist-spawn

Brings PR 1 (project memory is project content) up the stack. No conflicts.

Gate: make build && make synth ok; cli make test 1789 passed, 0 failed;
make test-skills-sh 10 passed, 0 failed; pytest 251 passed, 1 skipped; make claude-plugin-check ok.
… in the step 6 handoff

The handoff is the only input konductor has when it routes step 6, so a
run that reads the parameter list as complete loses cr_url (the review
degrades to diff-only) and the base branch Step 1 resolved.

Doc-only; the gate runs on this branch's head after the round 5 fixes.
The pages said that k-architect started directly returns a split, and in
the next paragraph told users to run the pass against k-architect directly.
Point all four places at konductor, which routes it to k-architect.

Doc-only; the HTML bundles' step 6 text predates this branch and is left
for its own bundle sync. The gate runs on this branch's head.
Whitespace only. Doc-only; the gate runs on this branch's head.
…ist-spawn

# Conflicts:
#	cli/konductor-rs/src/cli/synth/claude.rs
@knihit
knihit removed this pull request from stack #134 October 31, 2026 23:35
knihit pushed a commit that referenced this pull request Oct 31, 2026
…r-checker

Brings PR 1 (project memory is project content) up from #124. Conflicts:
CHANGELOG.md (PR 1's memory bullet replaces #114's, then this branch's review
bullet); docs/index.html and docs/site/user-guide.html (single-line bundles;
this branch's bundles plus PR 1's one FAQ insertion).

Gate: make build && make synth ok; cli make test 1789 passed, 0 failed;
make test-skills-sh 10 passed; pytest 251 passed, 1 skipped;
make claude-plugin-check ok.
@knihit
knihit added this pull request to stack #150 October 31, 2026 23:37

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant