When SAW receives a triage request, it doesn’t execute a fixed script. Instead, theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/samkit511/SAW---Security-Analyst-Workspace/llms.txt
Use this file to discover all available pages before exploring further.
CoordinatorAgent calls plan_workflow_tool() at runtime to produce an ordered step list for the specific request_type, then iterates over it and dispatches each step to the appropriate sub-agent. All agents share a single ExecutionContext instance — one agent writes to a field, and the next agent reads it. This section walks through every agent’s responsibilities in a log_triage request, which is the most complete flow.
Step plan structure
plan_workflow_tool() returns a plan object with a steps array. Each entry names the agent, the step label, and a human-readable purpose:
incident_followup requests, the plan omits DetectionAgent and runs only Coordinator → Risk → Mitigation → Audit. For task_command requests, it runs Coordinator → Mitigation → Audit.
Pipeline walkthrough
CoordinatorAgent: plan and route
CoordinatorAgent.handle() creates the ExecutionContext, calls self.plan() to get the step list, records the plan step, and then iterates the remaining steps — skipping itself and deferring AuditAgent to the very end.CoordinatorAgent.route() resolves the agent name to the correct instance and calls await agent.run(context). If any agent raises an exception, the loop records a failure entry in context.trace["agent_orchestration"]["failures"] and breaks — but AuditAgent still runs.DetectionAgent: normalize, classify, score
DetectionAgent.run() calls three tools in sequence and populates context.analysis and context.classification:analyze_logs()extractsip,path,method, andpayloadfrom the raw log using key-value parsing with a regex fallback.detect_threat()first triesheuristic_detect()for a fast deterministic match; if nothing matches, it calls the Gemini API (in HYBRID mode).validate_schema()normalizes the output, enforces allowedtypeandseverityvalues, and runscalibrate_confidence()to map raw confidence to a discrete bucket.compute_risk()multiplies calibrated confidence by severity weight (LOW=1, MEDIUM=2, HIGH=3).
create_event() and stores the event ID in context.artifacts["event_id"].RiskAgent: escalation check and decision
RiskAgent.run() reads context.classification and drives two sub-processes:Event memory and escalationupdate_events() appends the current event to the in-memory attack_memory store keyed by source IP and prunes events older than 60 seconds. evaluate_escalation() then checks two thresholds:- Burst: 3 or more events from the same IP in the last 15 seconds →
profile = "burst" - Sustained: 5 or more events in 60 seconds with a weighted severity score ≥ 9 →
profile = "sustained"
context.classification["effective_risk_score"] is overridden to 3.0 (the maximum) and behavior is set to "Aggressive Attacker".Decision enginedecision_engine() compares the effective risk score against two configurable thresholds:| Risk score | Decision | Meaning |
|---|---|---|
| ≥ 2.5 (or escalated) | EXECUTE | Apply mitigations immediately |
| ≥ 1.5 and < 2.5 | OBSERVE | Create analyst task, log for review |
| < 1.5 | IGNORE | Low-risk or false positive, no action |
confidence_bucket is LOW or MEDIUM, or detection_mode is "fallback", RiskAgent calls asa_agent.run() to request a Gemini-backed review:EXECUTE, OBSERVE, or IGNORE. High-confidence deterministic results are never subject to override.MitigationAgent: act on the decision
MitigationAgent.run() reads context.decision["decision"] and takes one of three paths:EXECUTE — calls mitigate() with the threat type and context, persists each action with create_action(), then creates a HIGH-priority follow-up task:mitigate() tracks applied action keys per IP so duplicate actions are never re-applied in the same session.OBSERVE — creates a MEDIUM-priority investigation task without applying any control-plane actions.IGNORE — records the step as completed with no actions or tasks.All results are stored in context.actions and context.tasks.AuditAgent: persist the incident workspace
AuditAgent.run() always runs last, regardless of earlier failures. It calls update_incident() to write the final status, decision, threat type, source IP, and full trace_snapshot to the SQLite incident record:context.artifacts["workspace"]. This workspace is returned in the final API response under both workspace and trace.incident_workspace.ExecutionContext field reference
| Field | Written by | Read by |
|---|---|---|
analysis | DetectionAgent | RiskAgent, MitigationAgent, AuditAgent |
classification | DetectionAgent | RiskAgent, MitigationAgent, AuditAgent |
decision | RiskAgent | MitigationAgent, AuditAgent |
actions | MitigationAgent | AuditAgent, aggregator |
tasks | MitigationAgent, RiskAgent (ADK) | AuditAgent, aggregator |
trace | All agents | AuditAgent (persists as trace_snapshot) |
artifacts | DetectionAgent, MitigationAgent, AuditAgent | aggregator |
plan | CoordinatorAgent | CoordinatorAgent (loop), aggregator |
agent_messages | All agents via _record() | aggregator |
agent_results | All agents via _record() | aggregator |
All agents inherit
_record() from BaseRoleAgent. Every call to _record() writes an agent run row to SQLite via create_agent_run() and appends an entry to context.agent_messages. This gives you a complete, ordered trace of what each agent did, even when a step fails.