Service Approval — Orchestrator v3
Coordinate the service-approval agents to produce a complete set of AWS security controls and a compliant resource template for any AWS service.
Pipeline: Intake → Assess → Research (4 sub-skills) → Validate → Map (4 sub-skills) → Generate (3 sub-skills) → Test → Summarize → Evidence
Deployment intent (full-deploy / dry-run / skip / manual-cli) is captured upfront in
the Intake phase via testing.mode in intake-manifest.json. There is no separate
human-in-the-loop gate between Generate and Test — whatever Intake recorded is what
Phase 5 does. Phase 7 Evidence runs after Summarize when testing.mode == full-deploy
and Phase 5 succeeded.
Flags
| Flag | Description | Default |
|---|---|---|
./input-folder |
Path to folder with input files (.md, .json, .xlsx, .pdf, .docx) | None (MCP-only mode) |
--service=<name> |
AWS service ID (e.g., bedrock-agentcore) |
Required if no folder |
--env=<profile> |
AWS CLI profile for Tester | None (skips Tester) |
--skip-test |
Skip Tester agent | false |
--dry-run |
Lint artifacts, do not deploy | false |
--timeout=<minutes> |
Config evaluation timeout | 30 |
--keep |
Keep deployed resources alive after Phase 7 Evidence completes (default: Phase 7 tears down). Phase 5 always defers teardown to Phase 7 when Phase 7 is in scope. | false |
--include-unverified |
Allow unverified items in generation | false |
--questions=<file> |
Path to SITCO Bar Questions file (.docx or .txt) — passed to Phase 0 | None |
--clean |
Delete existing .service-approval/ state before run | false |
--framework=<type> |
Control framework type (ccmv4, nist, cis, iso) — passed to Map phase | auto-detect |
--framework-file=<path> |
Explicit path to framework file — passed to Map phase | auto-detect from input folder |
--from-intake=<file> |
Path to intake YAML — Intake reads this instead of prompting | None |
--interactive |
Force Intake's interactive form even when other flags would satisfy it | false |
Agnosticism Guardrails (apply to every sub-skill)
This pipeline is explicitly AWS-service-agnostic and control-framework-agnostic. Every skill, sub-skill, validator, and generated artifact MUST honor these rules. They are binding on all agents executing any phase of the pipeline.
Service agnosticism
No hardcoded service names in generic skills. The service name (
Amazon ECS,AWS Lambda,Amazon S3, etc.) is ALWAYS a runtime argument passed via--service=<name>. It never appears as a literal in skill files, validator code, or rule descriptions — only in examples explicitly labeled as illustrative (e.g., "e.g.ecs:Create*"). Service prefixes in rule bodies must come fromvalidated.json/sar-facts.json, not assumption.Service-specific data belongs in data files, not code. Lookup tables (
sar-slugs.json, consumer specs, CFN prefixes) MUST live as JSON/YAML data files that any service can extend. Code that iterates these files must emit a WARNING on an unknown key — never silently skip.Sub-skills must read service identifiers from state.
validated.jsonhasserviceandcapabilities.iam.service_prefix. Sub-skills must read both from there, not infer them. The SAR page is the ground truth for any service-specific fact.
Framework agnosticism
No hardcoded framework names or objective IDs in generic skills. Framework domain names (
CEK-03,IAM-04,AC-2,CIS 1.1) appear ONLY inmap-parse-frameworkandmap-framework-mappingskills, which dispatch on the--framework=<type>argument. Never ingenerate-*skills, never in validators.Customer framework is supplied at runtime. Every pipeline run takes
--framework-file=<path>pointing at a customer-provided file (xlsx/json/md). No framework ships bundled except as a schema/parser reference.
No service-specific helper scripts
Agents executing any phase MUST NOT create service-specific helper scripts in scripts/,
.service-approval/<slug>/, or service-approval-plugin/. If a one-off computation is
needed:
- Keep it inline in the agent's task (Python
python3 -c "..."or Bash) - Or write it under
/tmp/for the duration of the run - Never commit or persist a file named
build-<service>-*.py,fix-<service>-*.sh, or similar. Durable tooling belongs in the plugin's owntools/tree and must be generic across services.
Enforcement
- Every generic skill in this pipeline MUST state its agnosticism in its own header if it
adds any consumer/service logic. If it cannot be generic (e.g.,
map-parse-frameworkfor CCMv4), it MUST be a dedicated sub-skill that dispatches on argument. - If an agent notices a rule, table, or validator check that would need rewriting to run on a different AWS service or a different framework, flag it as a CONTRADICTS-AGNOSTICISM finding and stop until resolved.
Pipeline Audit Log
Every phase logs to a single consolidated file:
.service-approval/<slug>/pipeline.log
This is the primary debugging artifact. Hooks (PostToolUse, Stop,
check_phase_complete), MCP calls, skill lifecycle (start/end/retry/halt),
and subprocess invocations all append to the same file in chronological
order. When something breaks, tail -50 .service-approval/<slug>/pipeline.log
shows the failure plus the last few causally-relevant events.
Line format:
<UTC-iso8601> [<phase>] [<source>:<verdict>] <message> [k=v ...]
Sources:
hook:post-tool-use/hook:stop/hook:check-phase-complete— validation hooksmcp:<server>— MCP calls (verdictcallorresponse)skill:<skill-name>— skill lifecycle (verdictstart/end/retry/halt)script:<script-name>— subprocess invocations (smoke-deploy-test.sh, etc.)
How skills emit events:
Skills SHOULD log a start event at the top and an end (or halt) event
at the bottom — this is what makes pipeline.log useful for debugging
("which phase started, when did it finish, did it halt?"). Coverage is
being added to skills incrementally; today the canonical example is
skills/map-assemble/SKILL.md. Until every skill emits these events,
gaps in pipeline.log mean "skill ran without lifecycle logging," not
"phase didn't run." Use the bash CLI:
# At skill start:
python3 -m tools.validate.log --slug <slug> --phase <NN-phase> \
--source skill:<skill-name> --verdict start \
--message "<one-line summary of what this run will do>"
# At skill end:
python3 -m tools.validate.log --slug <slug> --phase <NN-phase> \
--source skill:<skill-name> --verdict end \
--message "<one-line summary of outcome>"
# When retrying (e.g. after a hook auto-fix):
python3 -m tools.validate.log --slug <slug> --phase <NN-phase> \
--source skill:<skill-name> --verdict retry \
--message "<reason>" --extra attempt=2
Backward compatibility: the old mcp-calls.log is still written as
a shadow file (whenever an mcp:* event lands in pipeline.log, it's
mirrored to mcp-calls.log too). Tools that grep mcp-calls.log
continue to work; new tooling should read pipeline.log.
Prerequisites
MCP Pre-flight Check
Before doing anything else, verify all required MCP servers are available. Run this check automatically — do not ask the user. Install any that are missing.
Required MCPs (all from https://github.com/awslabs/mcp):
| Name | Transport | Endpoint / Command |
|---|---|---|
awsknowledge |
HTTP | https://knowledge-mcp.global.api.aws |
aws-documentation |
HTTP | https://mcp.awsdocs.amazon.com |
awsiac |
HTTP | https://iac-mcp.global.api.aws |
aws-cdk-mcp-server |
stdio | npx -y @aws/cdk-mcp-server@latest |
Step 1 — Detect environment (reuse the same logic as Environment Detection below):
Agenttool in tool list → Claude CodeKIRO_AGENTenv var set → KiroGITHUB_COPILOTenv var set → GitHub Copilot (VS Code)- Otherwise → Generic (Aider, Continue, other)
Step 2 — Check and install missing MCPs:
Claude Code:
INSTALLED=$(claude mcp list 2>/dev/null)
for MCP in awsknowledge aws-documentation awsiac aws-cdk-mcp-server; do
echo "$INSTALLED" | grep -q "$MCP" && echo "OK: $MCP" || echo "MISSING: $MCP"
done
For each MISSING entry, run the corresponding add command:
claude mcp add --transport http awsknowledge https://knowledge-mcp.global.api.aws
claude mcp add --transport http aws-documentation https://mcp.awsdocs.amazon.com
claude mcp add --transport http awsiac https://iac-mcp.global.api.aws
claude mcp add aws-cdk-mcp-server -- npx -y @aws/cdk-mcp-server@latest
After adding any MCP, inform the user: "Added missing MCP servers: . They are now active."
Step 3 — Handle MCPs that are registered but failing to connect: If any MCP is listed but shows "Failed to connect" or is unreachable, warn the user:
MCP degraded coverage:
aws-documentation UNREACHABLE — Research and Validate will fall back to web search.
awsiac UNREACHABLE — Generate will use best-effort generation.
Do NOT block execution — continue with degraded coverage and note it in the final report.
Kiro:
Update (2026-05-18): the Kiro CLI 2.0 (
kiro-cli) install path was removed frominstall.sh. Path 1 below describes the legacy symlink install that is no longer wired automatically; Path 2 (manual IDE install via the Powers panel) is the supported flow today. Kiro CLI 2.0 may remain as a manually-installed runtime — the runtime decision is being re-evaluated.
Kiro has two install paths (see install.sh); MCP-config handling differs between them.
Path 1 — Power install (CLI 2.0, recommended). If the user installed via
kiro-cli power install --local powers/service-approval, the Power bundles its own
MCP config at powers/service-approval/mcp.json and registers it with Kiro
automatically. Do NOT hand-edit ~/.kiro/settings/mcp.json — that would shadow or
duplicate the Power-bundled entries. Detect the Power install with:
kiro-cli power list 2>/dev/null | grep -q "service-approval" && echo "Power installed" || echo "Power NOT installed"
If the Power is installed:
- The MCPs are already wired via
powers/service-approval/mcp.json(generated frommanifest.yamlbytools/sync-ide-configs.py). No action needed at runtime. - If MCPs appear unreachable in Kiro, instruct the user to run
kiro-cli power install --local <repo>/powers/service-approvalagain to re-register the Power, then restart Kiro.
Path 2 — Manual IDE install. If the user installed by copying
powers/service-approval/{steering,ide/*} → .kiro/ (the path install.sh falls back
to when kiro-cli is not available), Kiro reads ~/.kiro/settings/mcp.json for MCP
config. Detect this path by:
test -d .kiro && ! kiro-cli power list 2>/dev/null | grep -q "service-approval" && echo "Manual install"
For each missing MCP under Path 2, add it to ~/.kiro/settings/mcp.json:
{
"mcpServers": {
"awsknowledge": { "type": "http", "url": "https://knowledge-mcp.global.api.aws" },
"aws-documentation": { "type": "http", "url": "https://mcp.awsdocs.amazon.com" },
"awsiac": { "type": "http", "url": "https://iac-mcp.global.api.aws" },
"aws-cdk-mcp-server": { "command": "npx", "args": ["-y", "@aws/cdk-mcp-server@latest"] }
}
}
Merge (do not overwrite existing entries). Write back the updated file.
After writing, inform the user: "Added missing MCP servers to
~/.kiro/settings/mcp.json: . Restart Kiro if this is the first time adding them."
If neither path is detected: the Power is not installed. Instruct the user to run
./install.sh from the repo root, then restart Kiro.
GitHub Copilot (VS Code):
Copilot in VS Code reads workspace-scoped .vscode/mcp.json automatically.
The repo ships this file generated from manifest.yaml via
tools/sync-ide-configs.py, so no install action is needed at runtime —
only verification.
test -f .vscode/mcp.json && echo "OK: .vscode/mcp.json present" || echo "MISSING: .vscode/mcp.json"
If present:
- Inform the user: "Copilot will auto-discover MCP servers from
.vscode/mcp.jsonwhen this repo is opened in VS Code. If Copilot is already running, reload the window (Cmd/Ctrl+Shift+P → 'Developer: Reload Window') to pick up changes." - Note: Copilot's MCP schema uses
{"servers": {...}}(NOT{"mcpServers": {...}}like other clients). The sync tool already emits the correct schema; do not hand-edit.
If absent:
- Tell the user: "Run
python3 tools/sync-ide-configs.pyto regenerate.vscode/mcp.jsonfrommanifest.yaml, then reload the VS Code window."
Generic (Aider, Continue, other): Print instructions for each missing MCP and continue — do not block execution:
Missing MCP servers detected. Add these to your IDE's MCP configuration:
awsknowledge: { "type": "http", "url": "https://knowledge-mcp.global.api.aws" }
aws-documentation: { "type": "http", "url": "https://mcp.awsdocs.amazon.com" }
awsiac: { "type": "http", "url": "https://iac-mcp.global.api.aws" }
aws-cdk-mcp-server: { "command": "npx", "args": ["-y", "@aws/cdk-mcp-server@latest"] }
See: https://github.com/awslabs/mcp
Continuing with degraded MCP coverage — some steps may fall back to web search.
If
--cleanwas passed:# Clean all service-specific state trees rm -rf .service-approval/*/ # Preserve plans/ directory if it exists mkdir -p .service-approval/plansCheck for stale state — if any
.service-approval/<slug>/directories exist AND--cleanwas NOT passed: Tell the user: "Existing service state found. Use --clean to start fresh, or I'll run all phases again and overwrite. Proceeding with fresh run." Then continue — NEVER silently read or report from old state files.Phase 0 Intake + Assess will create the service-specific directories automatically. No pre-creation needed. The canonical slug is derived after SAR facts are loaded.
Validate required input: If no
./input-folderAND no--serviceAND no--from-intake, the Intake phase below will prompt the user interactively. If Intake is running in non-interactive mode (e.g.,--from-intake=<file>) and required fields are missing, Intake halts with an error. Do NOT try to resolve the service name here — defer to Intake.
Environment Detection
Detect execution environment to choose parallelism strategy:
env | grep -E 'KIRO_AGENT|GITHUB_COPILOT' 2>/dev/null
Additionally: check whether the Agent tool appears in your available tool list.
If it does, treat this as Claude Code regardless of env var results.
Decision logic:
Agenttool available in tool list → Claude Code — use Parallel Mode (see below)KIRO_AGENTenv var set → Kiro — Sequential ModeGITHUB_COPILOTenv var set → GitHub Copilot — Sequential Mode- No signals detected → Sequential Mode (safe default)
Sequential Mode (default for all non-Claude-Code clients)
Run each phase in order. Pass all relevant flags through.
Intake (Precursor — Mandatory)
Follow ALL instructions in skills/intake/SKILL.md exactly.
Pass: --service, --from-intake if provided, --interactive if provided,
and any of --env, --framework, --framework-file, --skip-test, --dry-run,
--keep, --timeout the user supplied at the orchestrator level (Intake uses
them to pre-fill its form / intake manifest).
When done: verify .service-approval/_staging/<ts>/00-intake/intake-manifest.json
exists before proceeding (it gets promoted to <slug>/00-intake/ by Phase 0
Step 0b after slug derivation). From that manifest, resolve the effective --service, --framework,
--framework-file, input folder, and Phase 5 intent (CLI flags still override;
see Phase 5 resolution below).
Intake always runs. The intake skill itself decides whether to prompt interactively or short-circuit:
- If
./service-approval-intake.yamlexists in the user's CWD and--interactivewas not passed, the skill renders a summary and prompts[U]se as-is / [E]dit. "Use as-is" is the short-circuit path. - If
--from-intake=<path>is provided, the skill reads that file (no prompts). - Otherwise the skill runs the full interactive form and offers to save the
answers to
./service-approval-intake.yaml.
Plugin-repo guard: Files inside the installed plugin
(skills/intake/intake-template.yaml, skills/intake/defaults.yaml) are never
auto-loaded as user intake — they are templates/defaults shared across users.
--clean deletes the entire .service-approval/ tree (all per-slug service
dirs and any _staging/ snapshots) but does not delete the user's
./service-approval-intake.yaml.
Phase 0: Assess (Mandatory)
Follow ALL instructions in skills/assess/SKILL.md exactly.
Pass: input folder path (from intake), --service flag, --questions flag if provided.
When done: verify these files exist before proceeding:
.service-approval/<slug>/01-assess/sar-facts.json.service-approval/<slug>/01-assess/checkpoint-results.json.service-approval/<slug>/01-assess/assessment-summary.md.service-approval/<slug>/01-assess/iac-support.json
Skip condition: Phase 0 is skipped ONLY if all four assessment artifacts already
exist (from a previous /assess run). There is no flag to skip assessment.
Phase 1: Research (3+1 sub-skills, sequential in this mode)
Step 1a — Mitigations:
Follow ALL instructions in skills/research-mitigations/SKILL.md.
Pass: ./input-folder, --service.
Wait for .service-approval/<slug>/02-research/research-mitigations.json.
Step 1b — Capabilities:
Follow ALL instructions in skills/research-capabilities/SKILL.md.
Pass: --service.
Wait for .service-approval/<slug>/02-research/research-capabilities.json.
Step 1c — API Surface:
Follow ALL instructions in skills/research-api-surface/SKILL.md.
Pass: --service.
Wait for .service-approval/<slug>/02-research/research-api-surface.json.
Step 1d — Threat Attack Surface (optional but recommended):
Follow ALL instructions in skills/research-attack-surface/SKILL.md.
Pass: --service.
Wait for .service-approval/<slug>/02-research/research-attack-surface.json.
Auto-trigger rule: Run this step if ANY file in the input folder matches threat model
patterns: *MITRE*, *ATT&CK*, *attack*, *STRIDE*, *threat*model*, *threat*catalog*.
Skip ONLY if no threat model file is present in the input folder AND no customer threat
model input was provided.
Step 1e — Merge:
Run the merge step from skills/research/SKILL.md → "Merge Step" section.
Verify .service-approval/<slug>/02-research/research.json exists and has schema_version: "3.0".
Phase 2: Validate
Follow ALL instructions in skills/validate/SKILL.md exactly.
When done: verify .service-approval/<slug>/03-validate/validated.json exists before proceeding.
Phase 3: Map (4 sub-skills, sequential in this mode)
Step 3a — Parse Framework:
Follow ALL instructions in skills/map-parse-framework/SKILL.md.
Pass: --framework and --framework-file flags if provided.
Wait for .service-approval/<slug>/04-map/map-framework-parsed.json.
Step 3b — Generate Controls:
Follow ALL instructions in skills/map-generate-controls/SKILL.md.
Pass: --service.
Wait for .service-approval/<slug>/04-map/map-controls-generated.json.
Step 3c — Framework Mapping:
Follow ALL instructions in skills/map-framework-mapping/SKILL.md.
Wait for .service-approval/<slug>/04-map/map-framework-mapped.json.
Step 3d — Assemble:
Follow ALL instructions in skills/map-assemble/SKILL.md.
Verify these files exist before proceeding:
.service-approval/<slug>/04-map/mapping-results.json.service-approval/<slug>/04-map/controls-catalog.md.service-approval/<slug>/04-map/framework-mapping.md
Phase 4: Generate (3 sub-skills, sequential in this mode)
Step 4a — Preventive/Proactive:
Follow ALL instructions in skills/generate-preventive/SKILL.md.
Pass: --service, --include-unverified if set.
Wait for <slug>/05-generate/preventive/ and <slug>/05-generate/proactive/ to be populated.
Step 4b — Detective/Responsive:
Follow ALL instructions in skills/generate-detective/SKILL.md.
Pass: --service, --include-unverified if set.
Wait for <slug>/05-generate/detective/ and <slug>/05-generate/responsive/ to be populated.
Step 4c — IaC Templates:
Follow ALL instructions in skills/generate-iac/SKILL.md.
Pass: --service, --include-unverified if set.
Wait for <slug>/05-generate/iac/ to have all 4 formats.
Step 4d — Final Validation:
Run the final validation from skills/generate/SKILL.md → "Final Validation" section.
Phase 5: Test
Resolve Phase 5 intent in this order (CLI flags > intake > default):
- If
--skip-testwas passed on the command line → skip. - Else if
--dry-runwas passed → runskills/test/SKILL.mdwith--dry-run(use--envif provided, else Tier 1 only). - Else if
--env=<profile>was passed → runskills/test/SKILL.mdin full mode with that profile. - Else read
.service-approval/<slug>/00-intake/intake-manifest.json→testing.mode(or the staging path if Phase 0 hasn't yet promoted):skipormanual-cli→ skip Phase 5. (Summarize handles the manual-cli cheat-sheet render.)dry-run→ runskills/test/SKILL.mdwith--dry-run; pass--enviftesting.aws_profileis set.full-deploy→ runskills/test/SKILL.mdin full mode with--env=<testing.aws_profile>and--timeout=<testing.timeout_minutes>. Do NOT pass--keepto Phase 5. Phase 5 always leaves the stack alive when Phase 7 will run; the user'stesting.keeppreference is consumed by Phase 7 (which tears down at the end of evidence collection unlesskeep == true). If Phase 7 is being skipped (e.g. orchestrator was invoked with--no-evidenceor--envis missing for Phase 7), pass--no-evidenceto Phase 5 so it reverts to the legacy deploy-and-teardown behavior.
- If
intake-manifest.jsonis missing (orchestrator invoked without Intake) and no CLI flags were provided → skip Phase 5 with a warning.
Phase 6: Summarize
Follow ALL instructions in skills/summarize/SKILL.md exactly.
Report the final verdict to the user.
Phase 7: Evidence (Optional — requires AWS credentials)
NEW: If --env was provided AND Phase 5 Test deployed resources successfully (not --dry-run):
Run the Evidence skill to perform AWS CLI attestation against deployed resources.
Follow ALL instructions in skills/evidence/SKILL.md exactly.
Pass: --env, --service=<slug> flags.
If --skip-test or --dry-run or Phase 5 was skipped: skip this phase entirely
AND write a skip artifact so the audit trail isn't a hole:
import json
from datetime import datetime, timezone
from tools.paths import phase_dir
evidence_dir = phase_dir(slug, "evidence")
evidence_dir.mkdir(parents=True, exist_ok=True)
skip_reason = (
"Phase 5 Test was skipped or did not deploy resources. "
"Phase 7 requires a live deployed stack (06-test/deployed-resources.json) "
"to probe via AWS CLI. Re-run with --env=<profile> in full-deploy mode "
"to enable Phase 7."
)
skip_artifact = {
"schema_version": "1.0",
"phase": "evidence",
"skipped": True,
"skipped_at": datetime.now(timezone.utc).isoformat(),
"skip_reason": skip_reason,
"preconditions_failed": [
"phase_5_test_run",
"deployed_resources_present",
],
}
(evidence_dir / "phase-skipped.json").write_text(json.dumps(skip_artifact, indent=2))
Stack lifecycle: Phase 5 left the stack alive (teardown_status: "deferred_to_phase_7" in 06-test/test-results.json). Phase 7 reads
06-test/deployed-resources.json, probes the stack via AWS CLI, then tears
it down at the end of Step 11 — UNLESS intake-manifest.json.testing.keep == true, in which case Phase 7 leaves the stack alive for manual debugging and
records teardown_status: "skipped_keep_true". See skills/evidence/SKILL.md
Step 11 for the exact teardown procedure.
Evidence produces:
<slug>/08-evidence/attestation-report.md— control x CLI x verdict matrix<slug>/08-evidence/attestation-results.json— machine-readable verdicts (includesteardown_status)<slug>/08-evidence/summary.md— top-level triage digest<slug>/attestation.md— reviewer's audit guide (service tree manifest)
Parallel Mode (Claude Code only — Agent tool available)
Leverage the Agent tool to parallelize independent sub-skills within each phase.
Intake (Precursor — Mandatory)
Always dispatch Intake — the intake skill itself decides whether to prompt or
short-circuit (see "Discovery Rules" in skills/intake/SKILL.md):
- If
./service-approval-intake.yamlexists in CWD and--interactivewas not passed, the skill renders a summary and prompts[U]se as-is / [E]dit. - If
--from-intake=<path>is set, the skill reads that path with no prompts. - Otherwise the skill runs the full interactive form and offers to save.
Dispatch Intake as a foreground subagent with the full text of
skills/intake/SKILL.md, plus --service, --from-intake, --interactive, and
any pre-Intake CLI flags the user passed (--env, --framework, --framework-file,
--skip-test, --dry-run, --keep, --timeout).
Wait for .service-approval/_staging/<ts>/00-intake/intake-manifest.json (Intake's pre-promotion landing zone).
Plugin-repo files (skills/intake/intake-template.yaml,
skills/intake/defaults.yaml) are never auto-loaded as user intake. --clean
clears the entire .service-approval/ tree (all per-slug service dirs and
any _staging/ snapshots) but does not touch the user's
./service-approval-intake.yaml.
Phase 0: Assess (Mandatory)
If all 4 assessment artifacts (sar-facts.json, checkpoint-results.json,
assessment-summary.md, iac-support.json) already exist, skip (previous
/assess run).
Otherwise: dispatch Assessor as foreground subagent.
Wait for all 4 assessment artifacts.
Phase 1: Research (3 required sub-skills in parallel, +1 conditional)
Dispatch the 3 required research sub-skills simultaneously as background subagents:
Agent 1: research-mitigations (background)
Prompt: Full text of skills/research-mitigations/SKILL.md + input folder + --service flag
Output: research-mitigations.json
Agent 2: research-capabilities (background)
Prompt: Full text of skills/research-capabilities/SKILL.md + --service flag
Output: research-capabilities.json
Agent 3: research-api-surface (background)
Prompt: Full text of skills/research-api-surface/SKILL.md + --service flag
Output: research-api-surface.json
Conditionally dispatch a 4th agent — research-attack-surface — ONLY when a
threat model is present. Apply the same auto-trigger rule as Sequential Mode
(lines 408-411): scan ./input/ for files matching any of these patterns:
*MITRE*, *ATT&CK*, *attack*, *STRIDE*, *threat*model*, *threat*catalog*.
If at least one file matches OR the user supplied an explicit threat model
input, add this 4th agent to the dispatch:
Agent 4 (conditional): research-attack-surface (background)
Prompt: Full text of skills/research-attack-surface/SKILL.md + --service flag
Output: research-attack-surface.json
Skip Agent 4 entirely when no threat model file is present and no customer
threat model input was provided. Skipping it saves one full subagent
cold-start (~30-60s + Bedrock latency) per run, which adds up across the fleet.
The research-merge step already treats research-attack-surface.json as
optional and won't fail when it's absent.
IMPORTANT: Dispatch all required (and conditional, if triggered) sub-skills
in a single message with parallel Agent tool calls. Do NOT set model
parameter — let subagents inherit the caller's model.
When the dispatched agents complete, run the merge step from
skills/research/SKILL.md:
- Combine the 3 required partial JSONs into
research.json - If
research-attack-surface.jsonexists, merge its data intoattack_surface(when Agent 4 was skipped, this is a no-op) - Run the validation script
- If CRITICAL cross-reference errors exist (actions missing from both operations[] and permission_only_actions[]), re-dispatch the api-surface agent with the missing action list
Phase 2: Validate
Dispatch Validator as foreground subagent.
Provide full text of skills/validate/SKILL.md.
Wait for validated.json.
Phase 3: Map (4 sub-skills, 2 parallel + 2 sequential)
Steps 3a+3b: Parallel dispatch
Dispatch both independent sub-skills simultaneously as background subagents:
Agent 3a: map-parse-framework (background)
Prompt: Full text of skills/map-parse-framework/SKILL.md + framework flags
Output: map-framework-parsed.json
Agent 3b: map-generate-controls (background)
Prompt: Full text of skills/map-generate-controls/SKILL.md + --service flag
Output: map-controls-generated.json
IMPORTANT: Dispatch both in a single message with 2 Agent tool calls.
Step 3c: Framework mapping (foreground, after 3a+3b complete)
When both complete, dispatch as foreground subagent:
Agent 3c: map-framework-mapping (foreground)
Prompt: Full text of skills/map-framework-mapping/SKILL.md
Input: map-framework-parsed.json + map-controls-generated.json
Output: map-framework-mapped.json
This is the most reasoning-heavy step — MUST run in foreground to ensure quality gates pass.
Step 3d: Assembly (foreground, after 3c completes)
Dispatch as foreground subagent:
Agent 3d: map-assemble (foreground)
Prompt: Full text of skills/map-assemble/SKILL.md
Input: All 3 intermediate files + validated.json
Output: mapping-results.json + controls-catalog.md + framework-mapping.md
Wait for mapping-results.json, controls-catalog.md, framework-mapping.md.
Phase 4: Generate (3 sub-skills in parallel)
Dispatch all 3 generate sub-skills simultaneously as background subagents:
Agent 4: generate-preventive (background)
Prompt: Full text of skills/generate-preventive/SKILL.md + --service + --include-unverified
Output: <slug>/05-generate/preventive/ + <slug>/05-generate/proactive/
Agent 5: generate-detective (background)
Prompt: Full text of skills/generate-detective/SKILL.md + --service + --include-unverified
Output: <slug>/05-generate/detective/ + <slug>/05-generate/responsive/
Agent 6: generate-iac (background)
Prompt: Full text of skills/generate-iac/SKILL.md + --service + --include-unverified
Output: <slug>/05-generate/iac/
IMPORTANT: Dispatch all 3 in a single message with 3 Agent tool calls.
When all 3 complete, run the final validation from skills/generate/SKILL.md.
If any IaC templates are missing, re-dispatch the generate-iac agent.
Phase 5: Test
Apply the same resolution as Sequential Mode (CLI flags > intake.testing.mode >
default skip). If the resolution produces a run, dispatch Tester as a foreground
subagent with the resolved --env / --dry-run / --keep / --timeout flags.
Phase 6: Summarize
Dispatch Summarizer as foreground subagent.
Wait for APPROVAL-REPORT.md.
Parallelism Summary
Intake (1 agent, foreground — precursor)
Phase 0: Assess (1 agent, foreground)
Phase 1: Research (3 required agents in parallel, +1 conditional, then merge)
1a: Mitigations (background) ─┐
1b: Capabilities (background) ─┤
1c: API Surface (background) ─┤
1d: ATT&CK Surface (conditional) ─┤ ← only when threat-model file in input
└─ 1e: Merge (foreground)
Phase 2: Validate (1 agent, foreground)
Phase 3: Map (2 agents in parallel, then 2 sequential)
3a: Parse Framework (background) ─┐
3b: Generate Controls (background) ─┤
├─ 3c: Framework Mapping (foreground)
└─ 3d: Assemble (foreground)
Phase 4: Generate (3 agents in parallel, then validate)
Phase 5: Test (1 agent, foreground — gated by intake.testing.mode)
Phase 6: Summarize (1 agent, foreground)
Total agents: up to 14 (vs 8 in monolithic mode), but with 3 parallelization windows that significantly reduce wall-clock time.
If any agent exceeds --timeout minutes, proceed to Summarizer with partial results.
Summarizer marks the timed-out agent as INCOMPLETE in the report.
Final Output
Service Approval complete.
Report: .service-approval/APPROVAL-REPORT.md
Controls: .service-approval/<slug>/05-generate/
Verdict: <APPROVED|APPROVED WITH EXCEPTIONS|REQUIRES REMEDIATION>