Execute approved tasks and nothing else. ExecutorAgent picks the first allowed_tools entry, builds arguments, calls ToolRouter.route; ToolRouter is the mandatory chokepoint that enforces scope → risk → human-approval → execution → evidence capture → audit. The only way AgentLoop or cli run-task reaches the instrumented tool_executor is through this pair.
Hold static max retries (unused after planner.plan_retry_with_modifications took over) (executor.py:58ExecutorAgent.__init__ with max_retries_per_task=2).
Implement execute(task) (executor.py:72): validate task_id/target/phase, pick allowed_tools[0], build args via _build_args (executor.py:162), construct ExecutionPlan with expected_observation = objective[:200], call ToolRouter.route(...), measure execution_time, and synthesize ExecutionResult (success = allowed && output non-empty && "error" not in output[:200]; expected_observation_matched by keyword overlap).
Stateless execution; only bookkeeping is RiskController._commands_executed increment after success (tool_router.py:199) and the audit_logs/evidence DB rows.
evidence.type="raw_output" files under evidence/raw_output/<E-...>.txt written by EvidenceStore.save before summarization.
agent_loop.run — ExecutorAgent.execute(task) for the non-swarm path; swarm path still routes via SwarmOrchestrator which in turn uses its own ToolRouter-like gating.
cli.cmd_run_task — constructs ToolRouter + ExecutorAgent with a stub that returns "[tool] {name}...".
This is the enforcement surface for ScopeGate + RiskController (see docs/components/flow-b/scope-risk.md). Do not add a second direct tool_executor call site.
human_approval_fn must be supplied for any high risk when risk_profile != "high_authorized_testing" else those tasks stall in needs_approval.