From dfbcba48e1a381fe755c00629df15ce1656cde7a Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Tue, 27 Oct 2026 17:48:03 -0400 Subject: [PATCH 01/20] feat(synth): deny subagent spawning for specialist agents 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 --- CHANGELOG.md | 2 + agents/k-architect.agent-spec.json | 14 +-- agents/k-developer.agent-spec.json | 12 +- agents/k-media-analyzer.agent-spec.json | 6 - agents/k-product-manager.agent-spec.json | 8 +- agents/k-quality-assurance.agent-spec.json | 9 +- agents/k-researcher.agent-spec.json | 8 +- agents/k-tpm.agent-spec.json | 15 +-- cli/konductor-rs/src/cli/synth/claude.rs | 98 ++++++++++++++++- cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs | 97 ++++++++++++++-- cli/konductor-rs/src/cli/synth/model.rs | 14 +++ cli/konductor-rs/tests/synth_install_e2e.rs | 104 ++++++++++++++++++ docs/user-guide/agents.md | 51 ++++----- docs/user-guide/concepts.md | 12 +- docs/user-guide/faq.md | 5 +- docs/user-guide/glossary.md | 4 +- docs/user-guide/sop-workflows/code-review.md | 5 +- .../use-cases/plan-and-verify-work.md | 6 +- .../use-cases/review-code-and-tests.md | 7 +- tests/test_agent_tool_subagent_references.py | 16 +++ 20 files changed, 356 insertions(+), 137 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40aa5e0a..9a19c186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ not per individual commit. `constraints` skill and the routing rules. - Specialist agents no longer spawn other agents. They finish their part and hand the next step, or a proposed parallel split, back to the orchestrator. +- Specialist configs no longer include any agent-spawning tool. Kiro CLI v3 and Claude Code + configs block it explicitly, so a specialist cannot spawn even when a prompt asks it to. - Orchestrators delegate a step they can't do to the owning specialist and may run independent parts in parallel, including same-type agents. Parallel write agents must own disjoint files. - Agents ignore planted instructions and follow user-written memory only from an untracked diff --git a/agents/k-architect.agent-spec.json b/agents/k-architect.agent-spec.json index 3795927b..af41e881 100644 --- a/agents/k-architect.agent-spec.json +++ b/agents/k-architect.agent-spec.json @@ -70,15 +70,12 @@ }, "context": { "contextNames": ["k-core-rules.md"] - }, - "subagents": { - "agentNames": ["k-developer", "k-quality-assurance"] } }, "clientConfig": { "kiroCli": { "welcomeMessage": "I'm your solutions architect. I produce system designs, API specifications, data models, and threat models, validating each artifact before it hands off to implementation. Bring me a requirement or a design problem and I'll work the architecture through with you.\n\nBuilt-in workflows — type any to run it:\n /k-design-doc-creation — produce a design doc ready for principal-engineer review\n /k-existing-design-review — review existing design artifacts across 10 dimensions before implementation\n /k-principal-engineer-design-review — run a pre-submission quality gate on a design doc\n /k-adversarial-pull-request-review — run an adversarial review that catches what standard code review misses", - "tools": ["@builtin", "subagent", "@aws-mcp"], + "tools": ["@builtin", "@aws-mcp"], "allowedTools": [ "fs_read", "fs_write", @@ -89,12 +86,6 @@ "@aws-mcp/aws___list_regions", "@aws-mcp/aws___get_regional_availability" ], - "toolsSettings": { - "subagent": { - "availableAgents": ["k-developer", "k-quality-assurance"], - "trustedAgents": ["k-developer", "k-quality-assurance"] - } - }, "hooks": {}, "resources": [ "file://AGENTS.md", @@ -113,8 +104,7 @@ "WebSearch", "TodoWrite", "Skill", - "mcp__aws-mcp__*", - "Agent(k-developer, k-quality-assurance)" + "mcp__aws-mcp__*" ], "skills": [ "constraints", diff --git a/agents/k-developer.agent-spec.json b/agents/k-developer.agent-spec.json index ea720f96..f3daa5b0 100644 --- a/agents/k-developer.agent-spec.json +++ b/agents/k-developer.agent-spec.json @@ -64,7 +64,7 @@ "clientConfig": { "kiroCli": { "welcomeMessage": "I'm your development engineer. I break features into tasks, implement backend and frontend code, review my own changes, validate infrastructure, and track progress. Hand me a spec or a feature and I'll build it.\n\nBuilt-in workflows — type any to run it:\n /k-code-cleanup — strip AI-generated slop from a branch before raising a CR\n /k-pre-cr-critique — run a fast, read-only pre-CR critique of local changes\n /k-codebase-analysis — produce a comprehensive codebase analysis report with diagrams\n /k-code-review-workflow — run a full multi-skill code review consolidated into one report", - "tools": ["@builtin", "subagent", "@aws-mcp"], + "tools": ["@builtin", "@aws-mcp"], "allowedTools": [ "fs_read", "fs_write", @@ -75,16 +75,6 @@ "@aws-mcp/aws___list_regions", "@aws-mcp/aws___get_regional_availability" ], - "toolsSettings": { - "subagent": { - "availableAgents": [ - "k-quality-assurance" - ], - "trustedAgents": [ - "k-quality-assurance" - ] - } - }, "hooks": {}, "resources": [ "file://AGENTS.md", diff --git a/agents/k-media-analyzer.agent-spec.json b/agents/k-media-analyzer.agent-spec.json index c9d3e1cc..e8f1263a 100644 --- a/agents/k-media-analyzer.agent-spec.json +++ b/agents/k-media-analyzer.agent-spec.json @@ -23,12 +23,6 @@ "welcomeMessage": "I interpret media that raw-text tools can't read — PDFs, images, and diagrams. Give me a file and tell me what to extract or describe.", "tools": ["@builtin"], "allowedTools": ["fs_read", "web_fetch"], - "toolsSettings": { - "subagent": { - "trustedAgents": [], - "availableAgents": [] - } - }, "hooks": {}, "mcpServers": {}, "resources": [ diff --git a/agents/k-product-manager.agent-spec.json b/agents/k-product-manager.agent-spec.json index cdc66eee..817e9c3f 100644 --- a/agents/k-product-manager.agent-spec.json +++ b/agents/k-product-manager.agent-spec.json @@ -38,14 +38,8 @@ "clientConfig": { "kiroCli": { "welcomeMessage": "I'm your product manager. I turn ideas into user stories, requirements summaries, and decision research, validating each artifact before handoff. Tell me the problem you're solving and who it's for.", - "tools": ["@builtin", "subagent"], + "tools": ["@builtin"], "allowedTools": ["fs_read", "fs_write"], - "toolsSettings": { - "subagent": { - "trustedAgents": [], - "availableAgents": [] - } - }, "hooks": {}, "mcpServers": {}, "resources": [ diff --git a/agents/k-quality-assurance.agent-spec.json b/agents/k-quality-assurance.agent-spec.json index 4de25062..5625d7d5 100644 --- a/agents/k-quality-assurance.agent-spec.json +++ b/agents/k-quality-assurance.agent-spec.json @@ -35,14 +35,8 @@ "clientConfig": { "kiroCli": { "welcomeMessage": "I'm your QA engineer. I analyze test coverage, plan end-to-end test strategies, generate security tests, and validate UI text. Point me at the code or feature you want tested.\n\nBuilt-in workflows — type any to run it:\n /k-test-coverage-review — review test coverage and plan E2E and security tests", - "tools": ["@builtin", "subagent"], + "tools": ["@builtin"], "allowedTools": ["fs_read", "fs_write", "shell"], - "toolsSettings": { - "subagent": { - "trustedAgents": ["k-developer", "k-browser"], - "availableAgents": ["k-developer", "k-browser"] - } - }, "hooks": {}, "mcpServers": {}, "resources": [ @@ -61,7 +55,6 @@ "WebFetch", "WebSearch", "TodoWrite", - "Agent(k-developer, k-browser)", "Skill" ], "skills": [ diff --git a/agents/k-researcher.agent-spec.json b/agents/k-researcher.agent-spec.json index 28865566..d7f4b08f 100644 --- a/agents/k-researcher.agent-spec.json +++ b/agents/k-researcher.agent-spec.json @@ -30,14 +30,8 @@ "clientConfig": { "kiroCli": { "welcomeMessage": "I gather information — external documentation, web resources, AWS docs, and (when configured) Slack. Ask me a question and I'll research it and report back with sources.", - "tools": ["@builtin", "subagent"], + "tools": ["@builtin"], "allowedTools": ["fs_read"], - "toolsSettings": { - "subagent": { - "availableAgents": [], - "trustedAgents": [] - } - }, "resources": [ "file://AGENTS.md", "skill://.kiro/skills/ws-*/SKILL.md" diff --git a/agents/k-tpm.agent-spec.json b/agents/k-tpm.agent-spec.json index aa4df421..da6031f9 100644 --- a/agents/k-tpm.agent-spec.json +++ b/agents/k-tpm.agent-spec.json @@ -37,25 +37,12 @@ "kiroCli": { "welcomeMessage": "I'm your technical program manager. I coordinate across projects — timelines, status reporting, risk tracking, and program-level decision documents. Tell me the program or the coordination problem.", "tools": [ - "@builtin", - "subagent" + "@builtin" ], "allowedTools": [ "fs_read", "fs_write" ], - "toolsSettings": { - "subagent": { - "trustedAgents": [ - "k-developer", - "k-researcher" - ], - "availableAgents": [ - "k-developer", - "k-researcher" - ] - } - }, "hooks": {}, "mcpServers": {}, "resources": [ diff --git a/cli/konductor-rs/src/cli/synth/claude.rs b/cli/konductor-rs/src/cli/synth/claude.rs index 1efc62d0..f169b75f 100644 --- a/cli/konductor-rs/src/cli/synth/claude.rs +++ b/cli/konductor-rs/src/cli/synth/claude.rs @@ -21,7 +21,7 @@ use super::content_writers::{write_all_context, write_all_skills, write_all_sops use super::kiro_cli_v2::write_sop_scopes_sidecar; #[cfg(test)] use super::kiro_cli_v2::SOP_SCOPES_SIDECAR_FILE; -use super::model::CanonicalModel; +use super::model::{is_specialist, CanonicalModel}; #[cfg(test)] use super::model::{AuxiliaryFile, ContextDef, SkillDef, SopDef}; use super::parser::{ClaudeCliConfig, McpServerDef}; @@ -74,6 +74,8 @@ struct ClaudeAgentFrontmatter<'a> { skills: &'a [String], #[serde(rename = "allowedTools", skip_serializing_if = "is_empty_str_slice")] allowed_tools: &'a [String], + #[serde(rename = "disallowedTools", skip_serializing_if = "<[_]>::is_empty")] + disallowed_tools: &'a [&'static str], #[serde(skip_serializing_if = "IndexMap::is_empty")] hooks: IndexMap, #[serde(rename = "mcpServers", skip_serializing_if = "Vec::is_empty")] @@ -336,6 +338,16 @@ impl HarnessTransformer for ClaudeTransformer { } } +/// Claude Code tools that start another agent. `Task` is the older name +/// of `Agent`. +const SPAWN_TOOLS: [&str; 3] = ["Agent", "Task", "Workflow"]; + +/// Matches a bare spawn tool or its scoped form, e.g. `Agent(k-developer)`. +fn is_spawn_tool(tool: &str) -> bool { + let base = tool.split('(').next().unwrap_or(tool).trim(); + SPAWN_TOOLS.contains(&base) +} + /// Renders one agent's YAML frontmatter + system prompt body. The body /// starts as the verbatim `system_prompt` text with no inserted heading /// (see module docstring), then has each of `context_names`' matching @@ -364,6 +376,9 @@ impl HarnessTransformer for ClaudeTransformer { /// `merge_mcp_servers` (see that function's own doc comment for the /// precedence rule) before rendering, so the frontmatter's `mcpServers:` /// key reflects both sources rather than only the Claude-specific one. +/// +/// For a specialist (`k-*`), spawn tools are dropped from `tools`; with no +/// `tools` list, `disallowedTools` denies them instead. fn render_agent_md( name: &str, config: &super::parser::AgentConfig, @@ -383,13 +398,29 @@ fn render_agent_md( } let merged_mcp_servers = merge_mcp_servers(mcp_registry, &claude.mcp_servers); + let specialist = is_specialist(name); + let tools: Option> = claude.tools.as_ref().map(|tools| { + tools + .iter() + .filter(|tool| !(specialist && is_spawn_tool(tool))) + .cloned() + .collect() + }); + // A specialist with no `tools` list inherits every default tool, + // spawn tools included, so it needs an explicit deny instead. + let disallowed_tools: &[&str] = if specialist && tools.is_none() { + &SPAWN_TOOLS + } else { + &[] + }; let frontmatter = ClaudeAgentFrontmatter { name, description: &config.description, model: &config.model, - tools: claude.tools.as_deref(), + tools: tools.as_deref(), skills: &claude.skills, allowed_tools: &claude.allowed_tools, + disallowed_tools, hooks: hooks_to_yaml(&claude.hooks), mcp_servers: render_mcp_servers(&merged_mcp_servers), }; @@ -842,6 +873,67 @@ mod tests { /// materialization cited in the module docstring -- `mcpServers` in /// particular as a sequence of single-key maps, not a plain map /// keyed by server name. + fn render_frontmatter(name: &str, tools: Option>) -> serde_yaml::Value { + let claude = ClaudeCliConfig { + tools: tools.map(|t| t.into_iter().map(String::from).collect()), + ..Default::default() + }; + let agent = agent_with_claude(name, claude); + let rendered = render_agent_md( + name, + &agent.config, + agent.client_config.claude_cli.as_ref().unwrap(), + &[], + &[], + &HashSet::new(), + &BTreeMap::new(), + ) + .unwrap(); + let frontmatter = rendered.splitn(3, "---\n").nth(1).unwrap(); + serde_yaml::from_str(frontmatter).unwrap() + } + + #[test] + fn render_agent_md_strips_spawn_tools_from_specialist() { + let parsed = render_frontmatter( + "k-example", + Some(vec![ + "Read", + "Agent(k-developer, k-browser)", + "Task", + "Workflow", + "Skill", + ]), + ); + assert_eq!( + parsed["tools"], + serde_yaml::from_str::("[Read, Skill]").unwrap() + ); + assert!(parsed.get("disallowedTools").is_none()); + } + + #[test] + fn render_agent_md_denies_spawn_tools_for_specialist_without_tools_list() { + let parsed = render_frontmatter("k-example", None); + assert!(parsed.get("tools").is_none()); + assert_eq!( + parsed["disallowedTools"], + serde_yaml::from_str::("[Agent, Task, Workflow]").unwrap() + ); + } + + #[test] + fn render_agent_md_keeps_spawn_tools_for_orchestrator() { + let parsed = render_frontmatter("konductor", Some(vec!["Workflow", "Agent(k-developer)"])); + assert_eq!( + parsed["tools"], + serde_yaml::from_str::("[Workflow, Agent(k-developer)]").unwrap() + ); + assert!(render_frontmatter("konductor", None) + .get("disallowedTools") + .is_none()); + } + #[test] fn render_agent_md_renders_allowed_tools_hooks_and_mcp_servers_when_set() { let mut hooks = IndexMap::new(); @@ -933,7 +1025,7 @@ mod tests { ) .unwrap(); - assert!(!rendered.contains("allowedTools:"), "got: {rendered}"); + assert!(!rendered.contains("\nallowedTools:"), "got: {rendered}"); assert!(!rendered.contains("hooks:"), "got: {rendered}"); assert!(!rendered.contains("mcpServers:"), "got: {rendered}"); } diff --git a/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs b/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs index 5fa9495d..4c132385 100644 --- a/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs +++ b/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs @@ -81,7 +81,7 @@ use super::kiro_cli_v2::{ AGENTS_CONTENT_TYPE_DIR, CONTEXT_CONTENT_TYPE_DIR, SKILLS_CONTENT_TYPE_DIR, SOPS_CONTENT_TYPE_DIR, }; -use super::model::CanonicalModel; +use super::model::{is_specialist, CanonicalModel}; use super::parser::KiroCliConfig; use super::path_safety::reject_unsafe_agent_name; use super::staging::stage_content_type; @@ -95,15 +95,16 @@ use super::HarnessTransformer; /// unified permissions block"), replaced by `permissions`. /// /// V3-only fields the agent-config page's New Fields Reference Table -/// documents (`excludedTools`, `includeMcpJson`, `includePowers`) are -/// deliberately NOT emitted here: the source agent-spec schema -/// (`KiroCliConfig`) has no corresponding field to derive them from -/// today, and inventing a default would violate the "implement only -/// what's confirmed" contract this module is built under. A future -/// agent-spec schema change adding one of these would need a -/// corresponding `KiroCliConfig` field and a matching addition here, not -/// a fabricated default. This mirrors the existing project stance already -/// on record for these three fields. +/// documents (`includeMcpJson`, `includePowers`) are deliberately NOT +/// emitted here: the source agent-spec schema (`KiroCliConfig`) has no +/// corresponding field to derive them from today, and inventing a default +/// would violate the "implement only what's confirmed" contract this +/// module is built under. A future agent-spec schema change adding one of +/// these would need a corresponding `KiroCliConfig` field and a matching +/// addition here, not a fabricated default. +/// +/// `excludedTools` is emitted for specialists only, with the fixed +/// `SPECIALIST_EXCLUDED_TOOLS` list (see that constant). /// /// `welcomeMessage` is emitted when the source spec's `clientConfig.kiroCli` /// supplies it (`KiroCliConfig::welcome_message`), and omitted when absent. @@ -119,6 +120,8 @@ struct KiroV3AgentFile<'a> { #[serde(rename = "welcomeMessage", skip_serializing_if = "Option::is_none")] welcome_message: Option<&'a str>, tools: Vec, + #[serde(rename = "excludedTools", skip_serializing_if = "Vec::is_empty")] + excluded_tools: Vec<&'static str>, permissions: PermissionsBlock, #[serde(rename = "mcpServers")] mcp_servers: serde_json::Value, @@ -1159,6 +1162,18 @@ fn convert_hook_entry( Some(serde_json::Value::Object(doc)) } +const SUBAGENT_CAPABILITY: &str = "subagent"; + +/// Dropping the `subagent` tag is not enough: `@builtin` also carries +/// `run_workflow`, which runs any agent via `agent://`, and no +/// permission rule gates it. `excludedTools` removes both and holds under +/// `--trust-all-tools` (checked against kiro-cli 2.35.0, engine v3). +const SPECIALIST_EXCLUDED_TOOLS: [&str; 2] = ["invoke_sub_agent", "run_workflow"]; + +fn is_subagent_tag(tag: &str) -> bool { + tag == SUBAGENT_CAPABILITY || tag.starts_with("subagent/") +} + /// Builds the on-disk JSON shape for one V3 agent from its config and /// `KiroCliConfig`. `tools`/`permissions`/`hooks` are the three fields /// that differ from `kiro_cli_v2.rs::render_agent_file` (see @@ -1218,8 +1233,14 @@ fn render_agent_file<'a>( resources.push(format!("file://{CONTEXT_CONTENT_TYPE_DIR}/{context_name}")); } - let tools = kiro.tools.iter().map(|id| map_tool_tag(id)).collect(); - let rules = derive_permission_rules(&kiro.tools_settings, &kiro.allowed_tools); + let mut tools: Vec = kiro.tools.iter().map(|id| map_tool_tag(id)).collect(); + let mut rules = derive_permission_rules(&kiro.tools_settings, &kiro.allowed_tools); + let mut excluded_tools = Vec::new(); + if is_specialist(name) { + tools.retain(|tag| !is_subagent_tag(tag)); + rules.retain(|rule| rule.capability != SUBAGENT_CAPABILITY); + excluded_tools = SPECIALIST_EXCLUDED_TOOLS.to_vec(); + } Ok(KiroV3AgentFile { name, @@ -1228,6 +1249,7 @@ fn render_agent_file<'a>( model: &config.model, welcome_message: kiro.welcome_message.as_deref(), tools, + excluded_tools, permissions: PermissionsBlock { rules }, mcp_servers, hooks: convert_hooks_for_v3(&kiro.hooks), @@ -1983,6 +2005,57 @@ mod tests { assert_eq!(subagent_rules[0].r#match, vec!["reviewer-agent"]); } + fn kiro_with_spawn_grant() -> KiroCliConfig { + KiroCliConfig { + tools: vec!["@builtin".to_string(), "use_subagent".to_string()], + allowed_tools: vec!["fs_read".to_string(), "subagent".to_string()], + tools_settings: serde_json::json!({ + "subagent": {"trustedAgents": ["k-developer"]} + }), + ..Default::default() + } + } + + #[test] + fn render_agent_file_strips_spawn_grants_from_specialist() { + let agent = agent_with_kiro("k-example", kiro_with_spawn_grant()); + let kiro_cfg = agent.client_config.kiro_cli.as_ref().unwrap(); + let file = + render_agent_file(&agent.name, &agent.config, kiro_cfg, &[], &HashSet::new()).unwrap(); + + assert_eq!(file.tools, vec!["@builtin"]); + assert!( + file.permissions + .rules + .iter() + .all(|r| r.capability != "subagent"), + "specialist kept a subagent rule: {:?}", + file.permissions.rules + ); + let json = serde_json::to_value(&file).unwrap(); + assert_eq!( + json["excludedTools"], + serde_json::json!(["invoke_sub_agent", "run_workflow"]) + ); + } + + #[test] + fn render_agent_file_keeps_spawn_grants_for_orchestrator() { + let agent = agent_with_kiro("konductor", kiro_with_spawn_grant()); + let kiro_cfg = agent.client_config.kiro_cli.as_ref().unwrap(); + let file = + render_agent_file(&agent.name, &agent.config, kiro_cfg, &[], &HashSet::new()).unwrap(); + + assert_eq!(file.tools, vec!["@builtin", "subagent"]); + assert!(file + .permissions + .rules + .iter() + .any(|r| r.capability == "subagent" && r.r#match == vec!["k-developer"])); + let json = serde_json::to_value(&file).unwrap(); + assert!(json.get("excludedTools").is_none()); + } + /// A `toolsSettings` entry that only produces a DENY rule for a /// capability (no `allowedPaths`/`allowedCommands` present) must /// not suppress a separate, legitimate ALLOW grant for that same diff --git a/cli/konductor-rs/src/cli/synth/model.rs b/cli/konductor-rs/src/cli/synth/model.rs index 3989731e..7b0e8d5c 100644 --- a/cli/konductor-rs/src/cli/synth/model.rs +++ b/cli/konductor-rs/src/cli/synth/model.rs @@ -63,6 +63,12 @@ pub struct ContextDef { /// interchangeable at this milestone. pub type AgentSpec = ParsedAgentSpec; +/// Specialists are the `k-*` agents. They never spawn other agents, so each +/// harness transformer strips spawn tools from their configs. +pub(crate) fn is_specialist(agent_name: &str) -> bool { + agent_name.starts_with("k-") +} + /// The full picture of the source tree, held in memory: all the agents, /// skills, SOPs, and context files that `konductor synth` will /// transform into harness-specific output. Built by `parse_canonical`, @@ -134,6 +140,14 @@ mod tests { assert!(executable.executable); } + #[test] + fn is_specialist_matches_k_prefix_only() { + assert!(is_specialist("k-architect")); + assert!(!is_specialist("konductor")); + assert!(!is_specialist("konductor-mux-orchestrator")); + assert!(!is_specialist("konductor-cmux-orchestrator")); + } + #[test] fn canonical_model_default_is_empty() { let model = CanonicalModel::default(); diff --git a/cli/konductor-rs/tests/synth_install_e2e.rs b/cli/konductor-rs/tests/synth_install_e2e.rs index ca141a8b..e5c1681d 100644 --- a/cli/konductor-rs/tests/synth_install_e2e.rs +++ b/cli/konductor-rs/tests/synth_install_e2e.rs @@ -1017,3 +1017,107 @@ fn real_synth_fails_on_dangling_skill_reference() { std::fs::remove_dir_all(&repo_root).ok(); } + +fn seed_spawning_agent_spec(agents_dir: &Path, name: &str) { + let spec = serde_json::json!({ + "schemaVersion": "1", + "name": name, + "config": { + "description": "An example agent.", + "model": "claude-sonnet-5", + "systemPrompt": "You are a helpful agent." + }, + "clientConfig": { + "kiroCli": { + "tools": ["@builtin", "subagent"], + "toolsSettings": {"subagent": {"trustedAgents": ["k-other"]}} + }, + "claudeCli": {"tools": ["Read", "Agent(k-other)"]} + } + }); + std::fs::write( + agents_dir.join(format!("{name}.agent-spec.json")), + serde_json::to_vec_pretty(&spec).unwrap(), + ) + .unwrap(); +} + +fn install_harness(repo_root: &Path, target_dir: &Path, harness: &str) { + let sink = telemetry_test_sink::TelemetrySink::start(); + let result = run_konductor( + target_dir, + &sink, + &[ + CMD_INSTALL, + "--from", + &repo_root.display().to_string(), + "--target", + &target_dir.display().to_string(), + "--harness", + harness, + "--no-telemetry", + ], + ); + assert!( + result.status.success(), + "`install --harness {harness}` must succeed: stderr={}", + String::from_utf8_lossy(&result.stderr) + ); +} + +#[test] +fn installed_specialists_cannot_spawn_but_orchestrator_can() { + let sink = telemetry_test_sink::TelemetrySink::start(); + let repo_root = scratch_dir("repo-specialist-spawn"); + let agents_dir = repo_root.join("agents"); + std::fs::create_dir_all(&agents_dir).unwrap(); + seed_spawning_agent_spec(&agents_dir, "k-example"); + seed_spawning_agent_spec(&agents_dir, "konductor"); + seed_skill_source(&repo_root); + + let synth_result = run_konductor( + &repo_root, + &sink, + &[CMD_SYNTH, "--from", &repo_root.display().to_string()], + ); + assert!( + synth_result.status.success(), + "real `synth --from ` must succeed: stderr={}", + String::from_utf8_lossy(&synth_result.stderr) + ); + + let target_dir = scratch_dir("target-specialist-spawn"); + std::fs::create_dir_all(target_dir.join(".claude")).unwrap(); + install_harness(&repo_root, &target_dir, "kiro-v3"); + install_harness(&repo_root, &target_dir, "claude"); + + let read_kiro = |name: &str| -> serde_json::Value { + let path = target_dir.join(format!(".kiro/agents/{name}.json")); + serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap() + }; + let specialist = read_kiro("k-example"); + assert_eq!(specialist["tools"], serde_json::json!(["@builtin"])); + assert_eq!( + specialist["excludedTools"], + serde_json::json!(["invoke_sub_agent", "run_workflow"]) + ); + assert!(!specialist["permissions"].to_string().contains("subagent")); + + let orchestrator = read_kiro("konductor"); + assert_eq!( + orchestrator["tools"], + serde_json::json!(["@builtin", "subagent"]) + ); + assert!(orchestrator.get("excludedTools").is_none()); + assert!(orchestrator["permissions"].to_string().contains("k-other")); + + let read_claude = |name: &str| -> String { + let path = target_dir.join(format!(".claude/agents/{name}.md")); + std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())) + }; + assert!(!read_claude("k-example").contains("Agent(")); + assert!(read_claude("konductor").contains("Agent(k-other)")); + + std::fs::remove_dir_all(&repo_root).ok(); + std::fs::remove_dir_all(&target_dir).ok(); +} diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 82f319a3..1cd74501 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -139,63 +139,52 @@ pre-approves `fs_read` alone, so anything beyond reading stops to ask you. ## Who can delegate to whom -Delegation is not open. In Kiro CLI it is governed by each agent's -`toolsSettings.subagent.availableAgents`; in Claude Code by whether the agent has an -`Agent(...)` tool at all. +Only the orchestrators delegate. A specialist (any `k-*` agent) never spawns another agent: +it does its own part and returns to its caller, with a handoff note naming the next role or a +proposed parallel split. The orchestrator routes that note. + +This is enforced in config, not just in the prompt: + +- Kiro CLI: no specialist spec grants the `subagent` tool or `toolsSettings.subagent`. For + Kiro CLI v3, synth also writes `excludedTools: ["invoke_sub_agent", "run_workflow"]` into every + specialist, because `@builtin` carries `run_workflow`, which can start any agent. +- Claude Code: synth drops `Agent(...)`, `Task` and `Workflow` from every specialist's `tools` + list. A specialist with no `tools` list gets them as `disallowedTools` instead. ```mermaid %%{init:{'theme':'base','themeVariables':{'fontSize':'14px','primaryColor':'#eef2f7','primaryTextColor':'#0f172a','primaryBorderColor':'#40556e','lineColor':'#7a8899','textColor':'#0f172a','edgeLabelBackground':'#ffffff','clusterBkg':'#fafbfc','clusterBorder':'#c9d2dc'}}}%% flowchart LR - Orch["konductor
any agent"]:::focus + Orch["konductor
the 8 specialists"]:::focus Arch["k-architect"] Dev["k-developer"] QA["k-quality-assurance"] TPM["k-tpm"] Res["k-researcher"] PM["k-product-manager"] - Br["k-browser
no delegation tool"]:::muted - MA["k-media-analyzer
no delegation tool"]:::muted + Br["k-browser"] + MA["k-media-analyzer"] Mux["konductor-mux-orchestrator
konductor-cmux-orchestrator
dispatch via shell, not subagent"]:::muted - None["k-product-manager
k-researcher
subagent tool, zero targets"]:::gate Orch --> Arch & Dev & QA & TPM & Res & PM & Br & MA - Arch --> Dev - Arch --> QA - Dev --> QA - QA --> Dev - QA --> Br - TPM --> Dev - TPM --> Res classDef focus fill:#dbe7f5,stroke:#2a5d8f,stroke-width:2px,color:#0f172a; - classDef gate fill:#fdf3e0,stroke:#a06800,stroke-width:1.5px,color:#0f172a; classDef muted fill:#f4f4f5,stroke:#8a8a8a,stroke-width:1.2px,color:#0f172a,stroke-dasharray:4 3; ``` -*Kiro CLI delegation graph. Claude Code differs — see the table below.* - | Agent | Kiro CLI can spawn | Claude Code can spawn | | --- | --- | --- | | `konductor` | the 8 specialists (an explicit allowlist, not "anything") | the same 8, via `Agent(...)`, plus `SendMessage` | -| `k-architect` | `k-developer`, `k-quality-assurance` | `k-developer`, `k-quality-assurance`, via `Agent(...)` | -| `k-developer` | `k-quality-assurance` | **nothing** | -| `k-quality-assurance` | `k-developer`, `k-browser` | `k-developer`, `k-browser`, via `Agent(...)` | -| `k-tpm` | `k-developer`, `k-researcher` | **nothing** | -| `k-product-manager` | has the tool, **zero targets** | **nothing** | -| `k-researcher` | has the tool, **zero targets** | **nothing** | | `konductor-mux-orchestrator` | no `subagent` tool — dispatches via shell scripts | dispatches via shell | | `konductor-cmux-orchestrator` | no `subagent` tool — dispatches via shell scripts | dispatches via shell | -| `k-browser` | no delegation | **nothing** | -| `k-media-analyzer` | no delegation | **nothing** | +| every `k-*` specialist | **nothing** | **nothing** | ### Why this matters: a real consequence `k-code-review-workflow` step 6 says to *"spawn `k-architect` in adversarial mode"*. That SOP is declared only by `k-developer` — which **cannot spawn `k-architect` in either runtime**: -- In Kiro CLI, `k-developer`'s targets are `["k-quality-assurance"]`. The architect is not on - that list. -- In Claude Code, `k-developer` has no `Agent(...)` tool at all. +- Specialists hold no spawn tool in either runtime (see above). +- The core rules tell a specialist to do such steps itself or return them to its caller. The SOP anticipates this — *"You MUST skip this step if no adversarial CR-review capability is available"* — so it degrades quietly rather than erroring. **In practice step 6 always no-ops, @@ -374,7 +363,7 @@ images, and diagrams — not a skill library. Point it at a file and ask a quest | Difference | Kiro CLI | Claude Code | | --- | --- | --- | | Agent name | `konductor` | `konductor` | -| Cross-agent delegation | 7 agents have `subagent`, 5 with real targets | `konductor`, `k-architect`, and `k-quality-assurance` hold `Agent(...)` | +| Cross-agent delegation | only `konductor` has `subagent`; the mux orchestrators use shell | only `konductor` holds `Agent(...)` | | MCP servers | declared and launched from `dependencies.mcpRegistry` | requested via tool globs (`mcp__aws-mcp__*`); you configure the server yourself | | Tool naming | `fs_read`, `fs_write`, `shell` | `Read`, `Write`, `Edit`, `Bash` | | Permission model | two lists: `tools` grants with a prompt, `allowedTools` pre-approves | one list: `clientConfig.claudeCli.tools` is the ceiling, and your `settings.json` decides what prompts | @@ -383,9 +372,7 @@ images, and diagrams — not a skill library. Point it at a file and ask a quest | Skills resolution | through the bundled `skill-lookup` MCP server. Skills install to `.konductor/skills/`, deliberately outside the `.kiro/skills/` tree Kiro CLI scans, and the agent reaches them through that server | native Claude Code skills: `clientConfig.claudeCli.skills` names the set, and each body installs to `.claude/skills//SKILL.md` | | Extra setup | none | agent-teams env var **and** a permissions allowlist | -The delegation row is the one that changes behaviour most: a workflow that relies on one -specialist handing off to another may work in Kiro CLI and not in Claude Code — `k-developer` -and `k-tpm` have Kiro targets but no `Agent(...)` tool at all. +In both runtimes a specialist hands off by returning to the orchestrator, never by spawning. --- diff --git a/docs/user-guide/concepts.md b/docs/user-guide/concepts.md index 08fc5d70..357d213f 100644 --- a/docs/user-guide/concepts.md +++ b/docs/user-guide/concepts.md @@ -166,14 +166,10 @@ than doing them itself. ## Orchestrator and delegation -**`konductor`**, as the orchestrator, is a hub — though not an exclusive one. In Kiro CLI four -specialists can also spawn each other directly (`k-architect` → developer and QA; -`k-developer` → QA; `k-quality-assurance` → developer and browser; `k-tpm` → developer and -researcher), and in Claude Code `k-architect` and `k-quality-assurance` carry `Agent(...)` tools -of their own — see [Who can delegate to whom](agents.md#who-can-delegate-to-whom). In the common -case, though, every piece of work routes through `konductor`, which decides who owns it, hands it -over, and checks the result. The core rules tell specialists not to use those grants: a -specialist does its own part, then returns a note naming the next role, or a proposed parallel +**`konductor`**, as the orchestrator, is the hub. Specialists hold no spawn tool in either +runtime (see [Who can delegate to whom](agents.md#who-can-delegate-to-whom)), so every piece of +work routes through `konductor`, which decides who owns it, hands it over, and checks the result. +A specialist does its own part, then returns a note naming the next role, or a proposed parallel split with the files each part touches, and `konductor` routes it. A **delegation** is not a casual instruction — it is a fixed 7-section format defined by the diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index e23911f2..33774244 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -210,9 +210,8 @@ transmitted. Delete it whenever you like. ## Why did my code review skip the security pass? Because it always does. `k-code-review-workflow` step 6 tries to spawn `k-architect` for an -adversarial pass, and **the only agent that owns the SOP cannot spawn the architect** — in Kiro CLI -`k-developer`'s `availableAgents` list does not include it, and in Claude Code it has no -`Agent(...)` tool. +adversarial pass, and **the only agent that owns the SOP cannot spawn the architect**: no +specialist holds a spawn tool in either runtime. The SOP is written to skip the step quietly when the capability is missing, so you get standard review with no error. diff --git a/docs/user-guide/glossary.md b/docs/user-guide/glossary.md index d5690591..e2a43772 100644 --- a/docs/user-guide/glossary.md +++ b/docs/user-guide/glossary.md @@ -33,8 +33,8 @@ Another name for a **SOP**. ### `availableAgents` The list in a Kiro CLI agent spec (`clientConfig.kiroCli.toolsSettings.subagent.availableAgents`) -naming exactly which agents that agent may spawn. An empty list means the agent holds the -`subagent` tool but can spawn nothing. Only `konductor` is unrestricted. See +naming exactly which agents that agent may spawn. Only `konductor` declares one; specialists +hold no `subagent` tool at all. See [Who can delegate to whom](agents.md#who-can-delegate-to-whom). ### Canonical model diff --git a/docs/user-guide/sop-workflows/code-review.md b/docs/user-guide/sop-workflows/code-review.md index 65411e09..fa8b606a 100644 --- a/docs/user-guide/sop-workflows/code-review.md +++ b/docs/user-guide/sop-workflows/code-review.md @@ -133,9 +133,8 @@ references, and a verdict of **READY FOR CR** or **NEEDS FIXES**. - **The diff, not just the file list, is the input.** Step 1 captures full hunks; the changed-file list is derived from them. - **The adversarial step never actually fires.** Step 6 tells the agent to spawn - `k-architect`, but its one owning agent cannot: in Kiro CLI `k-developer`'s - `subagent.availableAgents` is `["k-quality-assurance"]`, which does not include the architect, - and in Claude Code it has no `Agent(...)` tool at all. + `k-architect`, but its one owning agent cannot: no specialist holds a spawn tool in either + runtime. The SOP degrades quietly by design (*"You MUST skip this step if no adversarial CR-review capability is available"*), so you get standard review only. Routing through the orchestrator does not help — it does not declare this SOP. Run diff --git a/docs/user-guide/use-cases/plan-and-verify-work.md b/docs/user-guide/use-cases/plan-and-verify-work.md index 5f4049bb..6814f5be 100644 --- a/docs/user-guide/use-cases/plan-and-verify-work.md +++ b/docs/user-guide/use-cases/plan-and-verify-work.md @@ -182,10 +182,8 @@ The chain itself is runtime-agnostic. What differs is dispatch mechanics: Functionally the same three SOPs run either way. -One caveat worth knowing: **specialist-to-specialist handoff is narrower in Claude Code**. There, -only `k-architect` (→ developer, QA) and `k-quality-assurance` (→ developer, browser) carry -`Agent(...)` tools of their own; `k-developer` and `k-tpm` have Kiro CLI targets but no `Agent(...)` -tool at all. The multiplexer variants shell out instead, so they work either way. +Specialists never spawn each other in either runtime. A specialist returns to the orchestrator, +which routes the next step. --- diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index 763930cf..b0e070dd 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -29,11 +29,8 @@ it generates. **`k-developer`, the SOP's only owner, cannot do that spawn**, verified against the agent specs: -- In **Claude Code**, `k-developer` has no `Agent(...)` tool at all. (`k-architect` and - `k-quality-assurance` do hold `Agent(...)` tools there — delegation in Claude Code is not - orchestrator-only — but the agent that owns this SOP is not one of them.) -- In **Kiro CLI**, delegation goes through `toolsSettings.subagent.availableAgents`, and - `k-developer`'s is `["k-quality-assurance"]` — no architect. +- In **Claude Code**, no specialist has an `Agent(...)` tool; only `konductor` does. +- In **Kiro CLI**, no specialist has the `subagent` tool; only `konductor` does. Step 6's own constraints anticipate this — *"You MUST skip this step if no adversarial CR-review capability is available"* — so the workflow degrades gracefully rather than erroring. In practice diff --git a/tests/test_agent_tool_subagent_references.py b/tests/test_agent_tool_subagent_references.py index 05b61f50..9cc351f5 100644 --- a/tests/test_agent_tool_subagent_references.py +++ b/tests/test_agent_tool_subagent_references.py @@ -52,3 +52,19 @@ def test_agent_tool_entries_splits_comma_separated_names(): def test_agent_tool_entries_ignores_specs_without_an_agent_grant(): spec = {"clientConfig": {"claudeCli": {"tools": ["Read", "Write"]}}} assert _agent_tool_entries(spec) == [] + + +def test_specialists_hold_no_spawn_grant(): + failures = [] + for spec_path in sorted(AGENTS_DIR.glob("k-*.agent-spec.json")): + spec = json.loads(spec_path.read_text()) + client = spec.get("clientConfig", {}) + kiro = client.get("kiroCli", {}) + if "subagent" in kiro.get("tools", []): + failures.append(f"{spec_path.name}: kiroCli.tools has subagent") + if "subagent" in kiro.get("toolsSettings", {}): + failures.append(f"{spec_path.name}: kiroCli.toolsSettings has subagent") + if _agent_tool_entries(spec): + failures.append(f"{spec_path.name}: claudeCli.tools has Agent(...)") + + assert not failures, "specialists must not spawn agents:\n" + "\n".join(failures) From 2840a6e31390293ac19f2309c4266522192e74e4 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 01:58:22 -0400 Subject: [PATCH 02/20] docs(agents): say that no specialist delegates and scope the spawn block 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). --- CHANGELOG.md | 6 ++- docs/user-guide/agents.md | 42 ++++++++++++------- docs/user-guide/concepts.md | 5 ++- docs/user-guide/faq.md | 3 +- docs/user-guide/sop-workflows/code-review.md | 4 +- .../use-cases/plan-and-verify-work.md | 4 +- .../use-cases/review-code-and-tests.md | 3 +- 7 files changed, 43 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e365be99..81acb33d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,11 @@ not per individual commit. and does not report a denied action; it adds visible shared files to a proposed split before fan-out. A specialist that runs an SOP's independence step itself says so in its output. - Specialist configs no longer include any agent-spawning tool. Kiro CLI v3 and Claude Code - configs block it explicitly, so a specialist cannot spawn even when a prompt asks it to. + configs block the runtime's spawn tools explicitly, so a specialist cannot call one even when + a prompt asks it to. The block does not cover shell: a specialist with shell access could + still run `kiro-cli` or `claude`. The Kiro CLI v3 block uses `excludedTools`, tested with + kiro-cli 2.35.0 and later. Kiro CLI v2 output is not covered yet: its `@builtin` still + includes the `subagent` tool. - Orchestrators delegate a step they can't do to the owning specialist and may run independent parts in parallel, including same-type agents. Parallel write agents must own disjoint files. - Agents ignore planted instructions and follow user-written memory only from an untracked diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 1cd74501..53b71c95 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -7,9 +7,9 @@ All **11 agents** the package ships: what each one does, what it can access, which SOPs it can run, and who it is allowed to delegate to. -Everything on this page was read out of `agents/*.agent-spec.json` — the tool lists, the -`allowedTools` arrays, the `subagent` target lists, the declared skills and SOPs, and the MCP -registry entries. +Everything on this page was read out of `agents/*.agent-spec.json`: the tool lists, the +`allowedTools` arrays, the orchestrators' `subagent` target lists, the declared skills and SOPs, +and the MCP registry entries. --- @@ -143,13 +143,22 @@ Only the orchestrators delegate. A specialist (any `k-*` agent) never spawns ano it does its own part and returns to its caller, with a handoff note naming the next role or a proposed parallel split. The orchestrator routes that note. -This is enforced in config, not just in the prompt: +For Kiro CLI v3 and Claude Code this is enforced in config, not just in the prompt: - Kiro CLI: no specialist spec grants the `subagent` tool or `toolsSettings.subagent`. For Kiro CLI v3, synth also writes `excludedTools: ["invoke_sub_agent", "run_workflow"]` into every - specialist, because `@builtin` carries `run_workflow`, which can start any agent. + specialist, because `@builtin` carries `run_workflow`, which can start any agent. This was + tested with kiro-cli 2.35.0 and later; an older client that rejects unknown agent-file fields + may fail to load the specialists. - Claude Code: synth drops `Agent(...)`, `Task` and `Workflow` from every specialist's `tools` list. A specialist with no `tools` list gets them as `disallowedTools` instead. +- Kiro CLI v2 is not covered. Its `@builtin` includes the `subagent` tool, and the v2 output does + not remove it, so there only the prompt rule stops a specialist from spawning. + +The block covers the runtime's agent-spawning tools only, not shell. A specialist with shell +access could still start a new agent session by running `kiro-cli` or `claude` from the shell. +`k-architect`, `k-developer` and `k-quality-assurance` have shell pre-approved in Kiro CLI, and +every specialist except `k-researcher` is granted `Bash` in Claude Code. ```mermaid %%{init:{'theme':'base','themeVariables':{'fontSize':'14px','primaryColor':'#eef2f7','primaryTextColor':'#0f172a','primaryBorderColor':'#40556e','lineColor':'#7a8899','textColor':'#0f172a','edgeLabelBackground':'#ffffff','clusterBkg':'#fafbfc','clusterBorder':'#c9d2dc'}}}%% @@ -181,9 +190,10 @@ flowchart LR ### Why this matters: a real consequence `k-code-review-workflow` step 6 says to *"spawn `k-architect` in adversarial mode"*. That SOP -is declared only by `k-developer` — which **cannot spawn `k-architect` in either runtime**: +is declared only by `k-developer`, which **cannot spawn `k-architect`** in Kiro CLI v3 or Claude +Code: -- Specialists hold no spawn tool in either runtime (see above). +- Specialists hold no spawn tool there (see above; Kiro CLI v2 relies on the prompt rule). - The core rules tell a specialist to do such steps itself or return them to its caller. The SOP anticipates this — *"You MUST skip this step if no adversarial CR-review capability is @@ -263,7 +273,7 @@ The most capable agent in the package, on the strongest model. | **Skills** | **38** — the largest share of any agent. See [Architecture and design](skills.md#architecture-and-design), [Design quality gates](skills.md#design-quality-gates), [API and data modelling](skills.md#api-and-data-modelling) | | **MCP** | `aws-mcp`, launched via `uvx mcp-proxy-for-aws-cli==1.7.0`. Kiro CLI's `allowedTools` grants five tools (documentation search, documentation read, skill retrieval, region list, regional availability); Claude Code grants the whole server via `mcp__aws-mcp__*` (8 tools, including the three AWS-API-acting ones: `aws___run_script`, `aws___get_presigned_url`, `aws___get_tasks`) | | **Can mutate?** | Write and shell in both runtimes — pre-approved in Kiro CLI, granted in Claude Code | -| **Can delegate to** | `k-developer`, `k-quality-assurance` — in both runtimes | +| **Can delegate to** | nothing (returns to the orchestrator) | Owns both design SOPs *and* the adversarial pull-request review. Its system prompt applies a maker-checker pass to every artifact and presents findings as CRITICAL → IMPORTANT → SUGGESTION @@ -277,7 +287,7 @@ followed by `Fix these issues? [y/n]`. | **Skills** | 27 — backend and frontend development and review, `infra-validation`, `code-review`, `git-workflow`, `asdlc-code-simplifier`, `design-impact-review`, `find-aws-skills`, `security-remediation`, the three `adversarial-code-review-pass-*` skills, `verification`, and others | | **MCP** | `aws-mcp`, same as the architect | | **Can mutate?** | Write and shell in both runtimes — pre-approved in Kiro CLI, granted in Claude Code | -| **Can delegate to** | `k-quality-assurance` (Kiro CLI only) | +| **Can delegate to** | nothing (returns to the orchestrator) | The workhorse. Everything the orchestrator cannot do itself — file writes, git, shell, builds — lands here. It owns the only source-modifying SOP, `k-code-cleanup`. @@ -289,7 +299,7 @@ lands here. It owns the only source-modifying SOP, `k-code-cleanup`. | **SOPs** | `k-test-coverage-review` | | **Skills** | 12 — `test-coverage-analysis`, `e2e-test-strategy`, `cypress-test-implementation`, `playwright-test-implementation`, `security-test-generation`, `dom-inspection`, `ui-text-validation`, and others | | **Can mutate?** | Write and shell in both runtimes — pre-approved in Kiro CLI, granted in Claude Code | -| **Can delegate to** | `k-developer`, `k-browser` — in both runtimes | +| **Can delegate to** | nothing (returns to the orchestrator) | ### `k-product-manager` @@ -298,7 +308,7 @@ lands here. It owns the only source-modifying SOP, `k-code-cleanup`. | **SOPs** | **none** — skill-driven | | **Skills** | 16 — `user-story-writing`, `requirements-extraction`, `decision-research`, `sprint-planning`, `asana-sprint-planning`, `task-decomposition`, `legacy-to-agentic-estimate`, `humanize-writing`, `deliberation-panel`, `kiro-requirements-generation`, and others | | **Can mutate?** | Write pre-approved in Kiro CLI and granted in Claude Code; shell asks first in Kiro CLI and is granted in Claude Code | -| **Can delegate to** | nothing — has the `subagent` tool with an empty target list | +| **Can delegate to** | nothing (returns to the orchestrator) | Because it declares no SOPs there is no scripted procedure to invoke. Ask for the outcome — user stories, a requirements summary, a sprint plan — and it applies the relevant skill. @@ -310,7 +320,7 @@ stories, a requirements summary, a sprint plan — and it applies the relevant s | **SOPs** | **none** — skill-driven | | **Skills** | 14 — `program-planning`, `status-reporting`, `risk-management`, `program-decision-docs`, `plan-review`, `legacy-to-agentic-estimate`, `sprint-planning`, and others | | **Can mutate?** | Write pre-approved in Kiro CLI and granted in Claude Code; shell asks first in Kiro CLI and is granted in Claude Code | -| **Can delegate to** | `k-developer`, `k-researcher` (Kiro CLI only) | +| **Can delegate to** | nothing (returns to the orchestrator) | Owns `legacy-to-agentic-estimate` and its `convert_estimates.py` script, which is why `k-plan` step 5 delegates the Agentic Projection here. @@ -321,10 +331,10 @@ Owns `legacy-to-agentic-estimate` and its `convert_estimates.py` script, which i | --- | --- | | **SOPs** | **none** — skill-driven | | **Skills** | 8 — `external-research`, `sdlc-navigator`, `document-formats`, `deliberation-panel`, `socratic-elicitation`, `persistent-memory`, `workspace-skills`, `constraints` | -| **Kiro CLI tools** | `@builtin`, `subagent`; `allowedTools` is `fs_read` only | +| **Kiro CLI tools** | `@builtin`; `allowedTools` is `fs_read` only | | **Claude Code tools** | `WebFetch`, `WebSearch`, `TodoWrite`, `Read`, `Skill` — the narrowest tool list of any agent, and the only one with no write or shell tool | | **Can mutate?** | **Not in Claude Code** — no write or shell tool is granted at all. In Kiro CLI it pre-approves `fs_read` only, so anything more asks first | -| **Can delegate to** | nothing — `subagent` tool with an empty target list | +| **Can delegate to** | nothing (returns to the orchestrator) | The only specialist that cannot modify anything in either runtime, which makes it the safest agent to point at unfamiliar code. `k-context-gathering` step 1 spawns one or two of these in parallel. @@ -340,6 +350,7 @@ Optional Slack search is opt-in — see | **Skills** | 4 — `constraints`, `sdlc-navigator`, `app-discovery`, `dom-inspection` | | **MCP** | `playwright-mcp` (`@playwright/mcp@latest`). Kiro CLI `allowedTools` enumerates 13 specific browser tools — snapshot, screenshot, console messages, network requests, tabs, navigate, navigate back, wait for, click, type, select option, press key, close | | **Can mutate?** | Pre-approved for reads only in Kiro CLI, so writes and commands ask first; **write and shell granted in Claude Code** | +| **Can delegate to** | nothing (returns to the orchestrator) | Needs the browser binary: `npx playwright install chromium`. Note the Kiro CLI tool list is an explicit allowlist — it cannot, for example, upload files or evaluate arbitrary JavaScript. @@ -352,6 +363,7 @@ explicit allowlist — it cannot, for example, upload files or evaluate arbitrar | **Skills** | 1, `constraints`. It also declares `humanize-writing`, loaded on demand | | **Kiro CLI tools** | `@builtin`; `allowedTools` is `fs_read`, `web_fetch` | | **Can mutate?** | Pre-approved for reads only in Kiro CLI, so writes and commands ask first; **write and shell granted in Claude Code** | +| **Can delegate to** | nothing (returns to the orchestrator) | The leanest agent in the package. Its capability is the model's ability to interpret PDFs, images, and diagrams — not a skill library. Point it at a file and ask a question about it. @@ -363,7 +375,7 @@ images, and diagrams — not a skill library. Point it at a file and ask a quest | Difference | Kiro CLI | Claude Code | | --- | --- | --- | | Agent name | `konductor` | `konductor` | -| Cross-agent delegation | only `konductor` has `subagent`; the mux orchestrators use shell | only `konductor` holds `Agent(...)` | +| Cross-agent delegation | only `konductor` declares `subagent`; the mux orchestrators use shell. v3 specialists also have the built-in spawn tools excluded; v2 specialists still get `subagent` from `@builtin` | only `konductor` holds `Agent(...)` | | MCP servers | declared and launched from `dependencies.mcpRegistry` | requested via tool globs (`mcp__aws-mcp__*`); you configure the server yourself | | Tool naming | `fs_read`, `fs_write`, `shell` | `Read`, `Write`, `Edit`, `Bash` | | Permission model | two lists: `tools` grants with a prompt, `allowedTools` pre-approves | one list: `clientConfig.claudeCli.tools` is the ceiling, and your `settings.json` decides what prompts | diff --git a/docs/user-guide/concepts.md b/docs/user-guide/concepts.md index 357d213f..e23e332a 100644 --- a/docs/user-guide/concepts.md +++ b/docs/user-guide/concepts.md @@ -166,8 +166,9 @@ than doing them itself. ## Orchestrator and delegation -**`konductor`**, as the orchestrator, is the hub. Specialists hold no spawn tool in either -runtime (see [Who can delegate to whom](agents.md#who-can-delegate-to-whom)), so every piece of +**`konductor`**, as the orchestrator, is the hub. Specialists hold no spawn tool in Kiro CLI +v3 or Claude Code, and the core rules forbid spawning everywhere (see +[Who can delegate to whom](agents.md#who-can-delegate-to-whom)), so every piece of work routes through `konductor`, which decides who owns it, hands it over, and checks the result. A specialist does its own part, then returns a note naming the next role, or a proposed parallel split with the files each part touches, and `konductor` routes it. diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index 33774244..72383dfb 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -211,7 +211,8 @@ transmitted. Delete it whenever you like. Because it always does. `k-code-review-workflow` step 6 tries to spawn `k-architect` for an adversarial pass, and **the only agent that owns the SOP cannot spawn the architect**: no -specialist holds a spawn tool in either runtime. +specialist holds a spawn tool in Kiro CLI v3 or Claude Code, and the core rules forbid it in +Kiro CLI v2. The SOP is written to skip the step quietly when the capability is missing, so you get standard review with no error. diff --git a/docs/user-guide/sop-workflows/code-review.md b/docs/user-guide/sop-workflows/code-review.md index fa8b606a..5f8610bb 100644 --- a/docs/user-guide/sop-workflows/code-review.md +++ b/docs/user-guide/sop-workflows/code-review.md @@ -133,8 +133,8 @@ references, and a verdict of **READY FOR CR** or **NEEDS FIXES**. - **The diff, not just the file list, is the input.** Step 1 captures full hunks; the changed-file list is derived from them. - **The adversarial step never actually fires.** Step 6 tells the agent to spawn - `k-architect`, but its one owning agent cannot: no specialist holds a spawn tool in either - runtime. + `k-architect`, but its one owning agent cannot: no specialist holds a spawn tool in Kiro CLI + v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. The SOP degrades quietly by design (*"You MUST skip this step if no adversarial CR-review capability is available"*), so you get standard review only. Routing through the orchestrator does not help — it does not declare this SOP. Run diff --git a/docs/user-guide/use-cases/plan-and-verify-work.md b/docs/user-guide/use-cases/plan-and-verify-work.md index 6814f5be..dd3295c4 100644 --- a/docs/user-guide/use-cases/plan-and-verify-work.md +++ b/docs/user-guide/use-cases/plan-and-verify-work.md @@ -182,8 +182,8 @@ The chain itself is runtime-agnostic. What differs is dispatch mechanics: Functionally the same three SOPs run either way. -Specialists never spawn each other in either runtime. A specialist returns to the orchestrator, -which routes the next step. +Specialists never spawn each other. Kiro CLI v3 and Claude Code enforce this in config; Kiro CLI +v2 relies on the core rules. A specialist returns to the orchestrator, which routes the next step. --- diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index b0e070dd..dd9f3bab 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -30,7 +30,8 @@ it generates. **`k-developer`, the SOP's only owner, cannot do that spawn**, verified against the agent specs: - In **Claude Code**, no specialist has an `Agent(...)` tool; only `konductor` does. -- In **Kiro CLI**, no specialist has the `subagent` tool; only `konductor` does. +- In **Kiro CLI v3**, no specialist has the `subagent` tool; only `konductor` does. In Kiro CLI + v2, `@builtin` still carries it, and only the core rules stop the spawn. Step 6's own constraints anticipate this — *"You MUST skip this step if no adversarial CR-review capability is available"* — so the workflow degrades gracefully rather than erroring. In practice From 7e146825eefd353c4b1ad80a351c13f656341237 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 01:58:38 -0400 Subject: [PATCH 03/20] test(synth): reject every spawn-tool alias in specialist specs 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). --- tests/test_agent_tool_subagent_references.py | 62 +++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/tests/test_agent_tool_subagent_references.py b/tests/test_agent_tool_subagent_references.py index 9cc351f5..035a085b 100644 --- a/tests/test_agent_tool_subagent_references.py +++ b/tests/test_agent_tool_subagent_references.py @@ -54,17 +54,63 @@ def test_agent_tool_entries_ignores_specs_without_an_agent_grant(): assert _agent_tool_entries(spec) == [] +KIRO_SPAWN_TOOLS = {"subagent", "use_subagent", "agent_crew", "*"} +CLAUDE_SPAWN_TOOLS = {"Agent", "Task", "Workflow"} + + +def _spawn_grants(spec): + client = spec.get("clientConfig", {}) + kiro = client.get("kiroCli", {}) + grants = [] + for tool in kiro.get("tools", []): + if tool in KIRO_SPAWN_TOOLS or tool.split("/")[0] in KIRO_SPAWN_TOOLS: + grants.append(f"kiroCli.tools has {tool}") + for key in kiro.get("toolsSettings", {}): + if key in KIRO_SPAWN_TOOLS: + grants.append(f"kiroCli.toolsSettings has {key}") + for tool in client.get("claudeCli", {}).get("tools") or []: + if tool.split("(")[0].strip() in CLAUDE_SPAWN_TOOLS: + grants.append(f"claudeCli.tools has {tool}") + return grants + + def test_specialists_hold_no_spawn_grant(): failures = [] for spec_path in sorted(AGENTS_DIR.glob("k-*.agent-spec.json")): spec = json.loads(spec_path.read_text()) - client = spec.get("clientConfig", {}) - kiro = client.get("kiroCli", {}) - if "subagent" in kiro.get("tools", []): - failures.append(f"{spec_path.name}: kiroCli.tools has subagent") - if "subagent" in kiro.get("toolsSettings", {}): - failures.append(f"{spec_path.name}: kiroCli.toolsSettings has subagent") - if _agent_tool_entries(spec): - failures.append(f"{spec_path.name}: claudeCli.tools has Agent(...)") + failures.extend(f"{spec_path.name}: {grant}" for grant in _spawn_grants(spec)) assert not failures, "specialists must not spawn agents:\n" + "\n".join(failures) + + +def test_spawn_grants_catches_every_alias(): + spec = { + "clientConfig": { + "kiroCli": { + "tools": ["@builtin", "subagent", "use_subagent", "agent_crew", "*"], + "toolsSettings": {"subagent": {}}, + }, + "claudeCli": {"tools": ["Read", "Agent", "Task", "Workflow", "Agent(k-developer)"]}, + } + } + assert _spawn_grants(spec) == [ + "kiroCli.tools has subagent", + "kiroCli.tools has use_subagent", + "kiroCli.tools has agent_crew", + "kiroCli.tools has *", + "kiroCli.toolsSettings has subagent", + "claudeCli.tools has Agent", + "claudeCli.tools has Task", + "claudeCli.tools has Workflow", + "claudeCli.tools has Agent(k-developer)", + ] + + +def test_spawn_grants_ignores_ordinary_tools(): + spec = { + "clientConfig": { + "kiroCli": {"tools": ["@builtin", "@aws-mcp"], "toolsSettings": {"shell": {}}}, + "claudeCli": {"tools": ["Read", "Bash", "mcp__aws-mcp__*"]}, + } + } + assert _spawn_grants(spec) == [] From a1117bc0d7168be772799b2c98ec28ea7ad17b2f Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 01:59:42 -0400 Subject: [PATCH 04/20] feat(synth): warn when a specialist's spawn grants are stripped 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. --- cli/konductor-rs/src/cli/synth/claude.rs | 2 +- cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs | 6 +- .../src/cli/synth/parse_canonical.rs | 91 ++++++++++++++++++- 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/cli/konductor-rs/src/cli/synth/claude.rs b/cli/konductor-rs/src/cli/synth/claude.rs index f169b75f..5a580ce6 100644 --- a/cli/konductor-rs/src/cli/synth/claude.rs +++ b/cli/konductor-rs/src/cli/synth/claude.rs @@ -343,7 +343,7 @@ impl HarnessTransformer for ClaudeTransformer { const SPAWN_TOOLS: [&str; 3] = ["Agent", "Task", "Workflow"]; /// Matches a bare spawn tool or its scoped form, e.g. `Agent(k-developer)`. -fn is_spawn_tool(tool: &str) -> bool { +pub(crate) fn is_spawn_tool(tool: &str) -> bool { let base = tool.split('(').next().unwrap_or(tool).trim(); SPAWN_TOOLS.contains(&base) } diff --git a/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs b/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs index 4c132385..fcc4c417 100644 --- a/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs +++ b/cli/konductor-rs/src/cli/synth/kiro_cli_v3.rs @@ -412,7 +412,7 @@ impl HarnessTransformer for KiroCliV3Transformer { /// collective markers (`@builtin`, `*`), and any server-scoped /// `@` entry, none of which this module's four source /// pages confirm a rename for. -fn map_tool_tag(id: &str) -> String { +pub(crate) fn map_tool_tag(id: &str) -> String { TOOL_ID_TO_TAG .iter() .find(|(old, _)| *old == id) @@ -1162,7 +1162,7 @@ fn convert_hook_entry( Some(serde_json::Value::Object(doc)) } -const SUBAGENT_CAPABILITY: &str = "subagent"; +pub(crate) const SUBAGENT_CAPABILITY: &str = "subagent"; /// Dropping the `subagent` tag is not enough: `@builtin` also carries /// `run_workflow`, which runs any agent via `agent://`, and no @@ -1170,7 +1170,7 @@ const SUBAGENT_CAPABILITY: &str = "subagent"; /// `--trust-all-tools` (checked against kiro-cli 2.35.0, engine v3). const SPECIALIST_EXCLUDED_TOOLS: [&str; 2] = ["invoke_sub_agent", "run_workflow"]; -fn is_subagent_tag(tag: &str) -> bool { +pub(crate) fn is_subagent_tag(tag: &str) -> bool { tag == SUBAGENT_CAPABILITY || tag.starts_with("subagent/") } diff --git a/cli/konductor-rs/src/cli/synth/parse_canonical.rs b/cli/konductor-rs/src/cli/synth/parse_canonical.rs index 70c2cf62..c8867189 100644 --- a/cli/konductor-rs/src/cli/synth/parse_canonical.rs +++ b/cli/konductor-rs/src/cli/synth/parse_canonical.rs @@ -15,7 +15,11 @@ use std::path::{Path, PathBuf}; use serde_json::Value; -use crate::cli::synth::model::{AuxiliaryFile, CanonicalModel, ContextDef, SkillDef, SopDef}; +use crate::cli::synth::claude::is_spawn_tool; +use crate::cli::synth::kiro_cli_v3::{is_subagent_tag, map_tool_tag, SUBAGENT_CAPABILITY}; +use crate::cli::synth::model::{ + is_specialist, AuxiliaryFile, CanonicalModel, ContextDef, SkillDef, SopDef, +}; use crate::cli::synth::parser::{self, ParseError}; use crate::cli::synth::path_safety::case_insensitive_fold_key; @@ -79,6 +83,9 @@ pub fn parse_canonical(source_dir: &Path) -> Result for warning in check_skill_scope_consistency(&agents) { eprintln!("konductor: warning: {warning}"); } + for warning in check_specialist_spawn_grants(&agents) { + eprintln!("konductor: warning: {warning}"); + } Ok(CanonicalModel { agents, @@ -311,6 +318,41 @@ fn check_skill_scope_consistency(agents: &[parser::ParsedAgentSpec]) -> Vec Vec { + let mut warnings = Vec::new(); + for agent in agents.iter().filter(|agent| is_specialist(&agent.name)) { + let mut grants = Vec::new(); + if let Some(kiro) = &agent.client_config.kiro_cli { + let is_kiro_spawn = |id: &String| is_subagent_tag(&map_tool_tag(id)); + grants.extend(kiro.tools.iter().filter(|id| is_kiro_spawn(id)).cloned()); + grants.extend( + kiro.allowed_tools + .iter() + .filter(|id| is_kiro_spawn(id)) + .cloned(), + ); + if kiro.tools_settings.get(SUBAGENT_CAPABILITY).is_some() { + grants.push(format!("toolsSettings.{SUBAGENT_CAPABILITY}")); + } + } + if let Some(claude) = &agent.client_config.claude_cli { + let tools = claude.tools.iter().flatten(); + grants.extend(tools.filter(|tool| is_spawn_tool(tool)).cloned()); + } + if !grants.is_empty() { + warnings.push(format!( + "agent '{}' is a specialist (k-* name) but its spec grants spawn tools {grants:?}; \ + synth strips them from its output, so remove them from the spec", + agent.name + )); + } + } + warnings +} + /// Lists direct children of `dir` matching `predicate`, sorted for determinism /// before any processing occurs. Returns an empty vec if `dir` doesn't exist. fn list_dir_entries( @@ -1153,6 +1195,53 @@ mod tests { parser::parse_agent_spec_str(&content, "agents/test.agent-spec.json").unwrap() } + fn agent_spec_with_client_config( + name: &str, + client_config_json: &str, + ) -> parser::ParsedAgentSpec { + let content = format!( + r#"{{"schemaVersion":"1","name":"{name}","config":{{"description":"d","systemPrompt":"s","model":"m"}},"clientConfig":{client_config_json}}}"# + ); + parser::parse_agent_spec_str(&content, "agents/test.agent-spec.json").unwrap() + } + + #[test] + fn check_specialist_spawn_grants_warns_on_each_stripped_grant() { + let agent = agent_spec_with_client_config( + "k-example", + r#"{"kiroCli":{"tools":["@builtin","use_subagent"],"allowedTools":["subagent"],"toolsSettings":{"subagent":{}}},"claudeCli":{"tools":["Read","Agent(k-developer)","Task"]}}"#, + ); + let warnings = check_specialist_spawn_grants(&[agent]); + assert_eq!(warnings.len(), 1, "got: {warnings:?}"); + for expected in [ + "k-example", + "use_subagent", + "\"subagent\"", + "toolsSettings.subagent", + "Agent(k-developer)", + "Task", + ] { + assert!( + warnings[0].contains(expected), + "missing {expected}: {}", + warnings[0] + ); + } + } + + #[test] + fn check_specialist_spawn_grants_ignores_orchestrators_and_clean_specialists() { + let orchestrator = agent_spec_with_client_config( + "konductor", + r#"{"kiroCli":{"tools":["@builtin","subagent"]},"claudeCli":{"tools":["Agent(k-developer)"]}}"#, + ); + let specialist = agent_spec_with_client_config( + "k-example", + r#"{"kiroCli":{"tools":["@builtin"]},"claudeCli":{"tools":["Read","Bash"]}}"#, + ); + assert!(check_specialist_spawn_grants(&[orchestrator, specialist]).is_empty()); + } + /// Clean case: every `skill://` resource entry's name already /// appears in `skillNames` -- resources are a subset of skillNames, /// so no warning is produced. From 11fc362ef7da044d967ebb175c70c3991a5b6d1e Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 02:12:10 -0400 Subject: [PATCH 05/20] chore(verify): record test results for the #124 review fixes No content change. Verification, run from the worktree root on a1117bc0 (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. From 2eec7f5660f3bbaaf1559c58841ec85dff234cd0 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 04:58:52 -0400 Subject: [PATCH 06/20] style(synth): move the frontmatter regression comment back onto its test The render_frontmatter helper was inserted between the doc comment and the test it describes. --- cli/konductor-rs/src/cli/synth/claude.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/konductor-rs/src/cli/synth/claude.rs b/cli/konductor-rs/src/cli/synth/claude.rs index 5a580ce6..3c67d9a8 100644 --- a/cli/konductor-rs/src/cli/synth/claude.rs +++ b/cli/konductor-rs/src/cli/synth/claude.rs @@ -867,12 +867,6 @@ mod tests { ); } - /// Regression guard: an agent whose `claudeCli` sets - /// `allowedTools`/`hooks`/`mcpServers` must render all three as - /// their own frontmatter keys, grounded against the real Claude Code - /// materialization cited in the module docstring -- `mcpServers` in - /// particular as a sequence of single-key maps, not a plain map - /// keyed by server name. fn render_frontmatter(name: &str, tools: Option>) -> serde_yaml::Value { let claude = ClaudeCliConfig { tools: tools.map(|t| t.into_iter().map(String::from).collect()), @@ -934,6 +928,12 @@ mod tests { .is_none()); } + /// Regression guard: an agent whose `claudeCli` sets + /// `allowedTools`/`hooks`/`mcpServers` must render all three as + /// their own frontmatter keys, grounded against the real Claude Code + /// materialization cited in the module docstring -- `mcpServers` in + /// particular as a sequence of single-key maps, not a plain map + /// keyed by server name. #[test] fn render_agent_md_renders_allowed_tools_hooks_and_mcp_servers_when_set() { let mut hooks = IndexMap::new(); From 1d32adc2eb2af66985dee9ceb2fbccc94fd6d607 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 04:58:58 -0400 Subject: [PATCH 07/20] fix(synth): stop claiming synth strips specialist spawn grants Kiro CLI v2 output still keeps them on this branch, so the warning now gives the reason instead: specialists must not spawn. --- cli/konductor-rs/src/cli/synth/parse_canonical.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/konductor-rs/src/cli/synth/parse_canonical.rs b/cli/konductor-rs/src/cli/synth/parse_canonical.rs index c8867189..6ce02d0a 100644 --- a/cli/konductor-rs/src/cli/synth/parse_canonical.rs +++ b/cli/konductor-rs/src/cli/synth/parse_canonical.rs @@ -345,7 +345,7 @@ fn check_specialist_spawn_grants(agents: &[parser::ParsedAgentSpec]) -> Vec Date: Fri, 30 Oct 2026 04:59:10 -0400 Subject: [PATCH 08/20] docs(agents): cite only the kiro-cli version the excludedTools probe 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 --- CHANGELOG.md | 2 +- docs/user-guide/agents.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ece1ccc..983dcc30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ not per individual commit. configs block the runtime's spawn tools explicitly, so a specialist cannot call one even when a prompt asks it to. The block does not cover shell: a specialist with shell access could still run `kiro-cli` or `claude`. The Kiro CLI v3 block uses `excludedTools`, tested with - kiro-cli 2.35.0 and later. Kiro CLI v2 output is not covered yet: its `@builtin` still + kiro-cli 2.35.0. Kiro CLI v2 output is not covered yet: its `@builtin` still includes the `subagent` tool. - Orchestrators delegate a step they can't do to the owning specialist and may run independent parts in parallel, including same-type agents. Parallel write agents must own disjoint files. diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 53b71c95..0d86d87c 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -148,7 +148,7 @@ For Kiro CLI v3 and Claude Code this is enforced in config, not just in the prom - Kiro CLI: no specialist spec grants the `subagent` tool or `toolsSettings.subagent`. For Kiro CLI v3, synth also writes `excludedTools: ["invoke_sub_agent", "run_workflow"]` into every specialist, because `@builtin` carries `run_workflow`, which can start any agent. This was - tested with kiro-cli 2.35.0 and later; an older client that rejects unknown agent-file fields + tested with kiro-cli 2.35.0; an older client that rejects unknown agent-file fields may fail to load the specialists. - Claude Code: synth drops `Agent(...)`, `Task` and `Workflow` from every specialist's `tools` list. A specialist with no `tools` list gets them as `disallowedTools` instead. From 8f546fdf751b6e63fd8efc56a4fb2f861f7f880d Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 10:45:12 -0400 Subject: [PATCH 09/20] docs(guide): say step 6 runs through konductor's handoff routing 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. --- docs/user-guide/agents.md | 12 ++++++------ docs/user-guide/faq.md | 16 +++++++--------- docs/user-guide/sop-workflows/code-review.md | 14 +++++++------- .../use-cases/review-code-and-tests.md | 15 +++++++-------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 0d86d87c..c332970b 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -196,13 +196,13 @@ Code: - Specialists hold no spawn tool there (see above; Kiro CLI v2 relies on the prompt rule). - The core rules tell a specialist to do such steps itself or return them to its caller. -The SOP anticipates this — *"You MUST skip this step if no adversarial CR-review capability is -available"* — so it degrades quietly rather than erroring. **In practice step 6 always no-ops, -and you get standard review only.** +Under `konductor`, the core rules have `k-developer` return step 6 as a handoff for +`k-architect`, naming the SOP and the step to resume at. `konductor` routes it to the architect +and sends the findings back to the developer to finish steps 6 and 7. When `k-developer` is +started directly, nothing routes the handoff, and the SOP's own constraint applies: +*"You MUST skip this step if `k-adversarial-pull-request-review` is unavailable"*. You then get standard review only. -Routing the request through `konductor` does **not** fix it: the orchestrator does not -declare `k-code-review-workflow`, so it delegates the whole SOP to the developer, who then hits -the same wall. The reliable workaround is to run +To be sure the adversarial pass runs, run [`k-adversarial-pull-request-review`](sop-workflows/code-review.md#k-adversarial-pull-request-review) yourself against `k-architect`, which owns it. diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index 72383dfb..f81415cd 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -209,17 +209,15 @@ transmitted. Delete it whenever you like. ## Why did my code review skip the security pass? -Because it always does. `k-code-review-workflow` step 6 tries to spawn `k-architect` for an -adversarial pass, and **the only agent that owns the SOP cannot spawn the architect**: no -specialist holds a spawn tool in Kiro CLI v3 or Claude Code, and the core rules forbid it in -Kiro CLI v2. -The SOP is written to skip the step quietly when the capability is missing, so you get standard -review with no error. +`k-code-review-workflow` step 6 asks for an adversarial pass by `k-architect`, and **the only +agent that owns the SOP cannot spawn the architect**: no specialist holds a spawn tool in Kiro CLI +v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. -Routing through `konductor` does not fix it either: the orchestrator does not declare -`k-code-review-workflow`, so it hands the SOP to the developer, which hits the same wall. +Under `konductor`, the developer returns step 6 as a handoff for `k-architect`, and `konductor` +routes it and sends the findings back for the report. If you started `k-developer` directly, +nothing routes the handoff, so the SOP skips the step and you get standard review with no error. -Run the adversarial pass yourself against the agent that owns it: +To run the adversarial pass on its own, ask the agent that owns it: ```text Run k-adversarial-pull-request-review against my current diff. diff --git a/docs/user-guide/sop-workflows/code-review.md b/docs/user-guide/sop-workflows/code-review.md index 5f8610bb..12366581 100644 --- a/docs/user-guide/sop-workflows/code-review.md +++ b/docs/user-guide/sop-workflows/code-review.md @@ -132,14 +132,14 @@ references, and a verdict of **READY FOR CR** or **NEEDS FIXES**. - **The diff, not just the file list, is the input.** Step 1 captures full hunks; the changed-file list is derived from them. -- **The adversarial step never actually fires.** Step 6 tells the agent to spawn - `k-architect`, but its one owning agent cannot: no specialist holds a spawn tool in Kiro CLI - v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. - The SOP degrades quietly by design (*"You MUST skip this step if no adversarial CR-review - capability is available"*), so you get standard review only. Routing through the orchestrator does - not help — it does not declare this SOP. Run +- **The adversarial step runs only through `konductor`.** Step 6 asks for `k-architect`, but its + one owning agent cannot spawn: no specialist holds a spawn tool in Kiro CLI v3 or Claude Code, + and the core rules forbid it in Kiro CLI v2. Under `konductor`, `k-developer` returns step 6 as + a handoff, `konductor` routes it to `k-architect`, and the findings come back for the report. + When `k-developer` is started directly, the SOP skips the step + (*"You MUST skip this step if `k-adversarial-pull-request-review` is unavailable"*), so you get standard review only. Run [`k-adversarial-pull-request-review`](#k-adversarial-pull-request-review) against `k-architect` - separately instead. See + separately if you need it there. See [Agents reference](../agents.md#why-this-matters-a-real-consequence). - **The adversarial step is also skipped for frontend-only reviews** by design, because it targets backend and infra gaps. diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index dd9f3bab..505248f9 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -33,16 +33,15 @@ it generates. - In **Kiro CLI v3**, no specialist has the `subagent` tool; only `konductor` does. In Kiro CLI v2, `@builtin` still carries it, and only the core rules stop the spawn. -Step 6's own constraints anticipate this — *"You MUST skip this step if no adversarial CR-review -capability is available"* — so the workflow degrades gracefully rather than erroring. In practice -**step 6 silently no-ops and you get standard review only.** +**Through `konductor`, step 6 still runs.** The core rules have `k-developer` return step 6 as +a handoff for `k-architect`, naming the SOP and the step to resume at. `konductor` routes it to +the architect and sends the findings back to the developer to finish the report. -**Routing through `konductor` does not fix it.** `k-code-review-workflow` is not among the ten -SOPs the orchestrator declares, so it delegates the whole SOP to the developer, which hits the -same wall. +**Started directly, `k-developer` skips it.** Nothing routes the handoff, and step 6's own +constraint applies: *"You MUST skip this step if `k-adversarial-pull-request-review` is unavailable"*. You get standard review only. -**The workaround that does work:** run `k-adversarial-pull-request-review` yourself against -`k-architect`, which owns it. That is step 4 below. +**To run the adversarial pass on its own:** run `k-adversarial-pull-request-review` yourself +against `k-architect`, which owns it. That is step 4 below. --- From 88584b50a98548476ae2b5950f117030deb667f5 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 10:45:26 -0400 Subject: [PATCH 10/20] test(agents): check kiroCli.allowedTools for spawn grants 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). --- tests/test_agent_tool_subagent_references.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_agent_tool_subagent_references.py b/tests/test_agent_tool_subagent_references.py index 035a085b..a156665b 100644 --- a/tests/test_agent_tool_subagent_references.py +++ b/tests/test_agent_tool_subagent_references.py @@ -54,6 +54,7 @@ def test_agent_tool_entries_ignores_specs_without_an_agent_grant(): assert _agent_tool_entries(spec) == [] +# `*` is rejected here, at spec level, because a wildcard on v2 includes `subagent`. KIRO_SPAWN_TOOLS = {"subagent", "use_subagent", "agent_crew", "*"} CLAUDE_SPAWN_TOOLS = {"Agent", "Task", "Workflow"} @@ -65,6 +66,9 @@ def _spawn_grants(spec): for tool in kiro.get("tools", []): if tool in KIRO_SPAWN_TOOLS or tool.split("/")[0] in KIRO_SPAWN_TOOLS: grants.append(f"kiroCli.tools has {tool}") + for tool in kiro.get("allowedTools", []): + if tool in KIRO_SPAWN_TOOLS or tool.split("/")[0] in KIRO_SPAWN_TOOLS: + grants.append(f"kiroCli.allowedTools has {tool}") for key in kiro.get("toolsSettings", {}): if key in KIRO_SPAWN_TOOLS: grants.append(f"kiroCli.toolsSettings has {key}") @@ -88,6 +92,7 @@ def test_spawn_grants_catches_every_alias(): "clientConfig": { "kiroCli": { "tools": ["@builtin", "subagent", "use_subagent", "agent_crew", "*"], + "allowedTools": ["fs_read", "use_subagent"], "toolsSettings": {"subagent": {}}, }, "claudeCli": {"tools": ["Read", "Agent", "Task", "Workflow", "Agent(k-developer)"]}, @@ -98,6 +103,7 @@ def test_spawn_grants_catches_every_alias(): "kiroCli.tools has use_subagent", "kiroCli.tools has agent_crew", "kiroCli.tools has *", + "kiroCli.allowedTools has use_subagent", "kiroCli.toolsSettings has subagent", "claudeCli.tools has Agent", "claudeCli.tools has Task", From b38c57febb2259c0cae4ed7a86cca380685e7c18 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 10:45:27 -0400 Subject: [PATCH 11/20] docs(changelog): open the spawn-tool entry with what holds on every runtime 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. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1be8f6b..07a680dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ not per individual commit. fan-out. When the note or split came from an SOP, the orchestrator returns the results to that specialist to finish the SOP. A specialist that runs an SOP's independence step itself says so in its output. -- Specialist configs no longer include any agent-spawning tool. Kiro CLI v3 and Claude Code +- Specialist specs no longer grant any agent-spawning tool. Kiro CLI v3 and Claude Code configs block the runtime's spawn tools explicitly, so a specialist cannot call one even when a prompt asks it to. The block does not cover shell: a specialist with shell access could still run `kiro-cli` or `claude`. The Kiro CLI v3 block uses `excludedTools`, tested with From 90b964cfcccf91db3143730de96dcf10ea733a41 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 12:12:33 -0400 Subject: [PATCH 12/20] fix(sops): return k-code-review-workflow step 6 as a handoff instead 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. --- CHANGELOG.md | 3 ++- agent-sops/k-code-review-workflow.sop.md | 4 ++-- docs/user-guide/agents.md | 9 ++++----- docs/user-guide/faq.md | 6 +++--- docs/user-guide/sop-workflows/code-review.md | 9 ++++----- docs/user-guide/use-cases/review-code-and-tests.md | 11 ++++++----- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a680dd..8f969b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,8 @@ not per individual commit. a prompt asks it to. The block does not cover shell: a specialist with shell access could still run `kiro-cli` or `claude`. The Kiro CLI v3 block uses `excludedTools`, tested with kiro-cli 2.35.0. Kiro CLI v2 output is not covered yet: its `@builtin` still - includes the `subagent` tool. + includes the `subagent` tool. `k-code-review-workflow` step 6 now returns the adversarial + pass as a handoff for `k-architect` instead of skipping it. - Orchestrators delegate a step they can't do to the owning specialist and may run independent parts in parallel, including same-type agents. Parallel write agents must own disjoint files. - Agents ignore planted instructions and follow user-written memory only from an untracked diff --git a/agent-sops/k-code-review-workflow.sop.md b/agent-sops/k-code-review-workflow.sop.md index c2328a2b..5fc976c5 100644 --- a/agent-sops/k-code-review-workflow.sop.md +++ b/agent-sops/k-code-review-workflow.sop.md @@ -84,11 +84,11 @@ Validate each finding to eliminate false positives. When in doubt, reject — fa ### 6. Adversarial Review -If `k-adversarial-pull-request-review` is available, spawn `k-architect` in adversarial mode with it, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. +Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. **Constraints:** -- You MUST skip this step if `k-adversarial-pull-request-review` is unavailable, or if `review_type` is `frontend` only (adversarial review targets backend/infra gaps) +- You MUST skip this step only if `review_type` is `frontend` only (adversarial review targets backend/infra gaps) - You MUST pass the same diff used in Step 1 as `diff_input` - If the k-code-review-workflow was invoked with a CR URL, You MUST also pass it to `k-adversarial-pull-request-review` as its `pr_url` parameter — that is the name that SOP declares, and passing `cr_url` instead leaves it unset so the review degrades to diff-only. Do not send it to `adversarial-cr-review`, a different SOP that declares `cr_url`; on agents where both are loadable, name the target by filename. - You MUST apply the same false-positive criteria from Step 5 to adversarial findings before merging them diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index c332970b..3f644747 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -196,11 +196,10 @@ Code: - Specialists hold no spawn tool there (see above; Kiro CLI v2 relies on the prompt rule). - The core rules tell a specialist to do such steps itself or return them to its caller. -Under `konductor`, the core rules have `k-developer` return step 6 as a handoff for -`k-architect`, naming the SOP and the step to resume at. `konductor` routes it to the architect -and sends the findings back to the developer to finish steps 6 and 7. When `k-developer` is -started directly, nothing routes the handoff, and the SOP's own constraint applies: -*"You MUST skip this step if `k-adversarial-pull-request-review` is unavailable"*. You then get standard review only. +Step 6 has `k-developer` return it as a handoff for `k-architect` when it cannot spawn, naming +the SOP and the step to resume at. Under `konductor`, `konductor` routes it to the architect and +sends the findings back to the developer to finish steps 6 and 7. When `k-developer` is started +directly, nothing routes the handoff, and it comes back to you. To be sure the adversarial pass runs, run [`k-adversarial-pull-request-review`](sop-workflows/code-review.md#k-adversarial-pull-request-review) diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index f81415cd..73232b52 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -213,9 +213,9 @@ transmitted. Delete it whenever you like. agent that owns the SOP cannot spawn the architect**: no specialist holds a spawn tool in Kiro CLI v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. -Under `konductor`, the developer returns step 6 as a handoff for `k-architect`, and `konductor` -routes it and sends the findings back for the report. If you started `k-developer` directly, -nothing routes the handoff, so the SOP skips the step and you get standard review with no error. +Step 6 tells the developer to return it as a handoff for `k-architect` when it cannot spawn. +Under `konductor`, `konductor` routes the handoff and sends the findings back for the report. If +you started `k-developer` directly, nothing routes the handoff, and it comes back to you. To run the adversarial pass on its own, ask the agent that owns it: diff --git a/docs/user-guide/sop-workflows/code-review.md b/docs/user-guide/sop-workflows/code-review.md index 12366581..7febea78 100644 --- a/docs/user-guide/sop-workflows/code-review.md +++ b/docs/user-guide/sop-workflows/code-review.md @@ -134,12 +134,11 @@ references, and a verdict of **READY FOR CR** or **NEEDS FIXES**. list is derived from them. - **The adversarial step runs only through `konductor`.** Step 6 asks for `k-architect`, but its one owning agent cannot spawn: no specialist holds a spawn tool in Kiro CLI v3 or Claude Code, - and the core rules forbid it in Kiro CLI v2. Under `konductor`, `k-developer` returns step 6 as - a handoff, `konductor` routes it to `k-architect`, and the findings come back for the report. - When `k-developer` is started directly, the SOP skips the step - (*"You MUST skip this step if `k-adversarial-pull-request-review` is unavailable"*), so you get standard review only. Run + and the core rules forbid it in Kiro CLI v2. Step 6 has `k-developer` return it as a handoff + instead. Under `konductor`, `konductor` routes it to `k-architect`, and the findings come back + for the report. When `k-developer` is started directly, the handoff comes back to you: run [`k-adversarial-pull-request-review`](#k-adversarial-pull-request-review) against `k-architect` - separately if you need it there. See + yourself. See [Agents reference](../agents.md#why-this-matters-a-real-consequence). - **The adversarial step is also skipped for frontend-only reviews** by design, because it targets backend and infra gaps. diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index 505248f9..6386b3ea 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -33,12 +33,13 @@ it generates. - In **Kiro CLI v3**, no specialist has the `subagent` tool; only `konductor` does. In Kiro CLI v2, `@builtin` still carries it, and only the core rules stop the spawn. -**Through `konductor`, step 6 still runs.** The core rules have `k-developer` return step 6 as -a handoff for `k-architect`, naming the SOP and the step to resume at. `konductor` routes it to -the architect and sends the findings back to the developer to finish the report. +**Through `konductor`, step 6 still runs.** Step 6 tells `k-developer` to return it as a +handoff for `k-architect`, naming the SOP and the step to resume at, when it cannot spawn the +architect. `konductor` routes it to the architect and sends the findings back to the developer +to finish the report. -**Started directly, `k-developer` skips it.** Nothing routes the handoff, and step 6's own -constraint applies: *"You MUST skip this step if `k-adversarial-pull-request-review` is unavailable"*. You get standard review only. +**Started directly, the handoff comes back to you.** Nothing routes it, so the report waits on +step 6 until you run the adversarial pass yourself. **To run the adversarial pass on its own:** run `k-adversarial-pull-request-review` yourself against `k-architect`, which owns it. That is step 4 below. From 2b7294d7afc11a99138a72e754a769a8e7fcc480 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 13:54:11 -0400 Subject: [PATCH 13/20] fix(sop): say how a run resumed at code review step 6 finishes 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. --- agent-sops/k-code-review-workflow.sop.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent-sops/k-code-review-workflow.sop.md b/agent-sops/k-code-review-workflow.sop.md index 5fc976c5..cda93fcd 100644 --- a/agent-sops/k-code-review-workflow.sop.md +++ b/agent-sops/k-code-review-workflow.sop.md @@ -84,10 +84,11 @@ Validate each finding to eliminate false positives. When in doubt, reject — fa ### 6. Adversarial Review -Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. +Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. Include the Step 5 consolidated findings list in the handoff so the resumed run can merge into it. **Constraints:** +- If you were resumed at step 6 with `k-architect`'s findings, You MUST NOT spawn or hand off again. Apply the constraints below to those findings and the Step 5 list from the handoff, then continue to Step 7. - You MUST skip this step only if `review_type` is `frontend` only (adversarial review targets backend/infra gaps) - You MUST pass the same diff used in Step 1 as `diff_input` - If the k-code-review-workflow was invoked with a CR URL, You MUST also pass it to `k-adversarial-pull-request-review` as its `pr_url` parameter — that is the name that SOP declares, and passing `cr_url` instead leaves it unset so the review degrades to diff-only. Do not send it to `adversarial-cr-review`, a different SOP that declares `cr_url`; on agents where both are loadable, name the target by filename. From 7c1363e9fad3666b6f6cc0becabca7323e5546e1 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 13:57:41 -0400 Subject: [PATCH 14/20] docs: say how to finish a code review report started without konductor 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. --- docs/user-guide/agents.md | 4 +++- docs/user-guide/faq.md | 4 +++- docs/user-guide/sop-workflows/code-review.md | 3 ++- docs/user-guide/use-cases/review-code-and-tests.md | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 3f644747..6ddf0ba8 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -199,7 +199,9 @@ Code: Step 6 has `k-developer` return it as a handoff for `k-architect` when it cannot spawn, naming the SOP and the step to resume at. Under `konductor`, `konductor` routes it to the architect and sends the findings back to the developer to finish steps 6 and 7. When `k-developer` is started -directly, nothing routes the handoff, and it comes back to you. +directly, nothing routes the handoff, and it comes back to you. To finish the report, run +`k-adversarial-pull-request-review` with `k-architect`, then pass its findings and the handoff to +`k-developer` and ask it to resume `k-code-review-workflow` at step 6. To be sure the adversarial pass runs, run [`k-adversarial-pull-request-review`](sop-workflows/code-review.md#k-adversarial-pull-request-review) diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index 73232b52..9d16acf2 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -215,7 +215,9 @@ v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. Step 6 tells the developer to return it as a handoff for `k-architect` when it cannot spawn. Under `konductor`, `konductor` routes the handoff and sends the findings back for the report. If -you started `k-developer` directly, nothing routes the handoff, and it comes back to you. +you started `k-developer` directly, nothing routes the handoff, and it comes back to you. To +finish the report, run `k-adversarial-pull-request-review` with `k-architect`, then pass its +findings and the handoff to `k-developer` and ask it to resume `k-code-review-workflow` at step 6. To run the adversarial pass on its own, ask the agent that owns it: diff --git a/docs/user-guide/sop-workflows/code-review.md b/docs/user-guide/sop-workflows/code-review.md index 7febea78..f9b6cdbc 100644 --- a/docs/user-guide/sop-workflows/code-review.md +++ b/docs/user-guide/sop-workflows/code-review.md @@ -138,7 +138,8 @@ references, and a verdict of **READY FOR CR** or **NEEDS FIXES**. instead. Under `konductor`, `konductor` routes it to `k-architect`, and the findings come back for the report. When `k-developer` is started directly, the handoff comes back to you: run [`k-adversarial-pull-request-review`](#k-adversarial-pull-request-review) against `k-architect` - yourself. See + yourself, then pass its findings and the handoff to `k-developer` and ask it to resume + `k-code-review-workflow` at step 6 to finish the report. See [Agents reference](../agents.md#why-this-matters-a-real-consequence). - **The adversarial step is also skipped for frontend-only reviews** by design, because it targets backend and infra gaps. diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index 6386b3ea..09377adf 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -39,7 +39,9 @@ architect. `konductor` routes it to the architect and sends the findings back to to finish the report. **Started directly, the handoff comes back to you.** Nothing routes it, so the report waits on -step 6 until you run the adversarial pass yourself. +step 6 until you run the adversarial pass yourself. To finish the report, run +`k-adversarial-pull-request-review` with `k-architect`, then pass its findings and the handoff to +`k-developer` and ask it to resume `k-code-review-workflow` at step 6. **To run the adversarial pass on its own:** run `k-adversarial-pull-request-review` yourself against `k-architect`, which owns it. That is step 4 below. From 7523f79ddc3c92e893f81fb5a4b5065968dc9626 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 21:24:10 -0400 Subject: [PATCH 15/20] fix(sop): carry the code review's parameters and file counts in the step 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. --- agent-sops/k-code-review-workflow.sop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-sops/k-code-review-workflow.sop.md b/agent-sops/k-code-review-workflow.sop.md index cda93fcd..631025a8 100644 --- a/agent-sops/k-code-review-workflow.sop.md +++ b/agent-sops/k-code-review-workflow.sop.md @@ -84,7 +84,7 @@ Validate each finding to eliminate false positives. When in doubt, reject — fa ### 6. Adversarial Review -Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. Include the Step 5 consolidated findings list in the handoff so the resumed run can merge into it. +Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. Include the SOP parameters (`source_dir`, `review_type`, `output_file`, `base_branch`), the Step 2 file counts by category, and the Step 5 consolidated findings list in the handoff so the resumed run can merge into them and write the report. If the list is long, write it to `.konductor/handoff/k-developer-code-review.md` and name that path, per the `delegation-protocol` skill's Handoff Pattern. **Constraints:** From c77bb0d68e138efcf9b6a095317e5636d1ff7564 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 21:24:13 -0400 Subject: [PATCH 16/20] fix(sop): name only the constraints a run resumed at step 6 applies The diff_input, pr_url and frontend-only constraints concern launching the review and have nothing to act on in a resumed run. --- agent-sops/k-code-review-workflow.sop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-sops/k-code-review-workflow.sop.md b/agent-sops/k-code-review-workflow.sop.md index 631025a8..b8a4ff11 100644 --- a/agent-sops/k-code-review-workflow.sop.md +++ b/agent-sops/k-code-review-workflow.sop.md @@ -88,7 +88,7 @@ Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review` **Constraints:** -- If you were resumed at step 6 with `k-architect`'s findings, You MUST NOT spawn or hand off again. Apply the constraints below to those findings and the Step 5 list from the handoff, then continue to Step 7. +- If you were resumed at step 6 with `k-architect`'s findings, You MUST NOT spawn or hand off again. Apply the false-positive, merge and no-re-report constraints below to those findings and the Step 5 list from the handoff, then continue to Step 7. - You MUST skip this step only if `review_type` is `frontend` only (adversarial review targets backend/infra gaps) - You MUST pass the same diff used in Step 1 as `diff_input` - If the k-code-review-workflow was invoked with a CR URL, You MUST also pass it to `k-adversarial-pull-request-review` as its `pr_url` parameter — that is the name that SOP declares, and passing `cr_url` instead leaves it unset so the review degrades to diff-only. Do not send it to `adversarial-cr-review`, a different SOP that declares `cr_url`; on agents where both are loadable, name the target by filename. From cd89b6e67710c38974a88ec1f049d06edf3b8748 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Fri, 30 Oct 2026 21:24:29 -0400 Subject: [PATCH 17/20] docs(user-guide): route a direct-start adversarial pass through konductor 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. --- docs/user-guide/agents.md | 7 ++++--- docs/user-guide/faq.md | 4 +++- docs/user-guide/sop-workflows/code-review.md | 9 +++++---- docs/user-guide/use-cases/review-code-and-tests.md | 7 ++++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 6ddf0ba8..4e463766 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -199,9 +199,10 @@ Code: Step 6 has `k-developer` return it as a handoff for `k-architect` when it cannot spawn, naming the SOP and the step to resume at. Under `konductor`, `konductor` routes it to the architect and sends the findings back to the developer to finish steps 6 and 7. When `k-developer` is started -directly, nothing routes the handoff, and it comes back to you. To finish the report, run -`k-adversarial-pull-request-review` with `k-architect`, then pass its findings and the handoff to -`k-developer` and ask it to resume `k-code-review-workflow` at step 6. +directly, nothing routes the handoff, and it comes back to you. To finish the report, ask `konductor` to run +`k-adversarial-pull-request-review`, then pass its findings and the handoff to `k-developer` and +ask it to resume `k-code-review-workflow` at step 6. Started directly, `k-architect` returns the +review's passes as a split, or runs them itself in one session and says so. To be sure the adversarial pass runs, run [`k-adversarial-pull-request-review`](sop-workflows/code-review.md#k-adversarial-pull-request-review) diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index 9d16acf2..0de7007a 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -216,8 +216,10 @@ v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. Step 6 tells the developer to return it as a handoff for `k-architect` when it cannot spawn. Under `konductor`, `konductor` routes the handoff and sends the findings back for the report. If you started `k-developer` directly, nothing routes the handoff, and it comes back to you. To -finish the report, run `k-adversarial-pull-request-review` with `k-architect`, then pass its +finish the report, ask `konductor` to run `k-adversarial-pull-request-review`, then pass its findings and the handoff to `k-developer` and ask it to resume `k-code-review-workflow` at step 6. +Started directly, `k-architect` returns the review's passes as a split, or runs them itself in one +session and says so. To run the adversarial pass on its own, ask the agent that owns it: diff --git a/docs/user-guide/sop-workflows/code-review.md b/docs/user-guide/sop-workflows/code-review.md index f9b6cdbc..1e46fcd6 100644 --- a/docs/user-guide/sop-workflows/code-review.md +++ b/docs/user-guide/sop-workflows/code-review.md @@ -136,10 +136,11 @@ references, and a verdict of **READY FOR CR** or **NEEDS FIXES**. one owning agent cannot spawn: no specialist holds a spawn tool in Kiro CLI v3 or Claude Code, and the core rules forbid it in Kiro CLI v2. Step 6 has `k-developer` return it as a handoff instead. Under `konductor`, `konductor` routes it to `k-architect`, and the findings come back - for the report. When `k-developer` is started directly, the handoff comes back to you: run - [`k-adversarial-pull-request-review`](#k-adversarial-pull-request-review) against `k-architect` - yourself, then pass its findings and the handoff to `k-developer` and ask it to resume - `k-code-review-workflow` at step 6 to finish the report. See + for the report. When `k-developer` is started directly, the handoff comes back to you: ask + `konductor` to run [`k-adversarial-pull-request-review`](#k-adversarial-pull-request-review), + then pass its findings and the handoff to `k-developer` and ask it to resume + `k-code-review-workflow` at step 6 to finish the report. Started directly, `k-architect` returns + the review's passes as a split, or runs them itself in one session and says so. See [Agents reference](../agents.md#why-this-matters-a-real-consequence). - **The adversarial step is also skipped for frontend-only reviews** by design, because it targets backend and infra gaps. diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index 09377adf..f5d3755e 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -39,9 +39,10 @@ architect. `konductor` routes it to the architect and sends the findings back to to finish the report. **Started directly, the handoff comes back to you.** Nothing routes it, so the report waits on -step 6 until you run the adversarial pass yourself. To finish the report, run -`k-adversarial-pull-request-review` with `k-architect`, then pass its findings and the handoff to -`k-developer` and ask it to resume `k-code-review-workflow` at step 6. +step 6 until the adversarial pass runs. To finish the report, ask `konductor` to run +`k-adversarial-pull-request-review`, then pass its findings and the handoff to `k-developer` and +ask it to resume `k-code-review-workflow` at step 6. Started directly, `k-architect` returns the +review's passes as a split, or runs them itself in one session and says so. **To run the adversarial pass on its own:** run `k-adversarial-pull-request-review` yourself against `k-architect`, which owns it. That is step 4 below. From 0e76c9fa1921554c7b095231acb050e53ed47663 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Sat, 31 Oct 2026 15:04:10 -0400 Subject: [PATCH 18/20] docs(sop): carry cr_url, the resolved base branch and the Step 1 diff 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. --- agent-sops/k-code-review-workflow.sop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-sops/k-code-review-workflow.sop.md b/agent-sops/k-code-review-workflow.sop.md index b8a4ff11..8d5a004e 100644 --- a/agent-sops/k-code-review-workflow.sop.md +++ b/agent-sops/k-code-review-workflow.sop.md @@ -84,7 +84,7 @@ Validate each finding to eliminate false positives. When in doubt, reject — fa ### 6. Adversarial Review -Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. Include the SOP parameters (`source_dir`, `review_type`, `output_file`, `base_branch`), the Step 2 file counts by category, and the Step 5 consolidated findings list in the handoff so the resumed run can merge into them and write the report. If the list is long, write it to `.konductor/handoff/k-developer-code-review.md` and name that path, per the `delegation-protocol` skill's Handoff Pattern. +Spawn `k-architect` in adversarial mode with `k-adversarial-pull-request-review`, passing the same diff used in Step 1 as `diff_input`, to surface gaps missed by standard review. If you cannot spawn `k-architect`, return step 6 as a handoff naming `k-architect`, `k-adversarial-pull-request-review`, this SOP, and step 6 as the step to resume at, per the core rules' specialist handoffs. Include the SOP parameters (`source_dir`, `review_type`, `output_file`, `cr_url` if given, and the base branch Step 1 resolved), the Step 1 diff or the command that reproduces it, the Step 2 file counts by category, and the Step 5 consolidated findings list in the handoff so the resumed run can merge into them and write the report. If the list is long, write it to `.konductor/handoff/k-developer-code-review.md` and name that path, per the `delegation-protocol` skill's Handoff Pattern. **Constraints:** From bcd509f66d2ef66d01e914a1537f3823c17f1701 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Sat, 31 Oct 2026 15:04:37 -0400 Subject: [PATCH 19/20] docs(guide): route the standalone adversarial pass through konductor 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. --- docs/user-guide/agents.md | 4 ++-- docs/user-guide/faq.md | 3 ++- docs/user-guide/use-cases/review-code-and-tests.md | 7 ++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 4e463766..17a517ef 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -204,9 +204,9 @@ directly, nothing routes the handoff, and it comes back to you. To finish the re ask it to resume `k-code-review-workflow` at step 6. Started directly, `k-architect` returns the review's passes as a split, or runs them itself in one session and says so. -To be sure the adversarial pass runs, run +To be sure the adversarial pass runs, ask `konductor` to run [`k-adversarial-pull-request-review`](sop-workflows/code-review.md#k-adversarial-pull-request-review) -yourself against `k-architect`, which owns it. +against your diff; `konductor` routes it to `k-architect`, which owns it. ### The two multiplexer orchestrators do not use subagents at all diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md index 939cea1a..b7585e6c 100644 --- a/docs/user-guide/faq.md +++ b/docs/user-guide/faq.md @@ -222,7 +222,8 @@ findings and the handoff to `k-developer` and ask it to resume `k-code-review-wo Started directly, `k-architect` returns the review's passes as a split, or runs them itself in one session and says so. -To run the adversarial pass on its own, ask the agent that owns it: +To run the adversarial pass on its own, ask `konductor`, which routes it to `k-architect`, the +agent that owns it: ```text Run k-adversarial-pull-request-review against my current diff. diff --git a/docs/user-guide/use-cases/review-code-and-tests.md b/docs/user-guide/use-cases/review-code-and-tests.md index f5d3755e..621083a7 100644 --- a/docs/user-guide/use-cases/review-code-and-tests.md +++ b/docs/user-guide/use-cases/review-code-and-tests.md @@ -44,8 +44,9 @@ step 6 until the adversarial pass runs. To finish the report, ask `konductor` to ask it to resume `k-code-review-workflow` at step 6. Started directly, `k-architect` returns the review's passes as a split, or runs them itself in one session and says so. -**To run the adversarial pass on its own:** run `k-adversarial-pull-request-review` yourself -against `k-architect`, which owns it. That is step 4 below. +**To run the adversarial pass on its own:** ask `konductor` to run +`k-adversarial-pull-request-review` against your diff; `konductor` routes it to `k-architect`, +which owns it. That is step 4 below. --- @@ -169,7 +170,7 @@ afterwards — this is the one SOP here where "review" means "changed your code" ### 4. `k-adversarial-pull-request-review` — the security second opinion -Ask **`k-architect`** directly. This is the workaround for the step 6 gap. +Ask **`konductor`**, which routes it to `k-architect`. This is the workaround for the step 6 gap. ```text Run k-adversarial-pull-request-review against my current diff. From 422a82d23cbed2bfb7b36fcfb8a600ef147b2f36 Mon Sep 17 00:00:00 2001 From: Nihit Kasabwala <> Date: Sat, 31 Oct 2026 15:04:46 -0400 Subject: [PATCH 20/20] docs(agents): rewrap the step 6 paragraph to the page's width Whitespace only. Doc-only; the gate runs on this branch's head. --- docs/user-guide/agents.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/agents.md b/docs/user-guide/agents.md index 17a517ef..224fde18 100644 --- a/docs/user-guide/agents.md +++ b/docs/user-guide/agents.md @@ -199,10 +199,11 @@ Code: Step 6 has `k-developer` return it as a handoff for `k-architect` when it cannot spawn, naming the SOP and the step to resume at. Under `konductor`, `konductor` routes it to the architect and sends the findings back to the developer to finish steps 6 and 7. When `k-developer` is started -directly, nothing routes the handoff, and it comes back to you. To finish the report, ask `konductor` to run -`k-adversarial-pull-request-review`, then pass its findings and the handoff to `k-developer` and -ask it to resume `k-code-review-workflow` at step 6. Started directly, `k-architect` returns the -review's passes as a split, or runs them itself in one session and says so. +directly, nothing routes the handoff, and it comes back to you. To finish the report, ask +`konductor` to run `k-adversarial-pull-request-review`, then pass its findings and the handoff +to `k-developer` and ask it to resume `k-code-review-workflow` at step 6. Started directly, +`k-architect` returns the review's passes as a split, or runs them itself in one session and +says so. To be sure the adversarial pass runs, ask `konductor` to run [`k-adversarial-pull-request-review`](sop-workflows/code-review.md#k-adversarial-pull-request-review)