File layout
Data flow
Key components
ChainDefinition and ChainStep (models.py)
ChainDefinition holds the chain metadata (id, name, category, description) and an ordered list of ChainStep objects. Each step specifies a module (audit or inject), a technique, optional trust boundary, routing (on_success/on_failure), and whether it’s terminal.
ChainCategory enum defines four architecture patterns: rag_pipeline, agent_delegation, mcp_ecosystem, hybrid.
Loader (loader.py)
load_chain(path) parses a single YAML file into a ChainDefinition:
- YAML safe-load with error handling for encoding and parse failures
- Validates required top-level fields (
id,name,category,description,steps) - Validates
categoryagainst theChainCategoryenum - Parses each step dict, enforcing required fields (
id,name,module,technique) - Ensures step ID uniqueness within the file
load_all_chains() discovers all YAML files in the templates directory and enforces chain ID uniqueness across files.
Validator (validator.py)
validate_chain(chain) performs six ordered checks, returning a list of ValidationError objects:
| Check | What it validates |
|---|---|
| 1. Module refs | step.module must be audit or inject |
| 2. Technique refs | Technique must exist in the audit scanner registry or inject technique enum |
| 3. Graph refs | on_success/on_failure targets must be valid step IDs or abort |
| 4. Cycle detection | DFS with three-color marking to detect circular step references |
| 5. Reachability | BFS from the first step to ensure all steps are reachable |
| 6. Terminal steps | At least one step must be marked terminal or be the implicit last step |
counteragent.audit.scanner.registry and counteragent.inject.models to cross-check module references without creating circular dependencies.
Graph algorithms
Cycle detection uses iterative DFS with a three-color state machine (0=unvisited, 1=in-stack, 2=done). When a back-edge is found (visiting a node already in-stack), the cycle path is reported. Reachability uses BFS from the first step over the adjacency graph. The adjacency graph includes expliciton_success/on_failure edges plus implicit next-in-order edges for steps without explicit routing.
Dry-run tracer (tracer.py)
trace_chain(chain) walks the success path without making network calls or sending payloads:
- Starts at the first step in list order
- For each step: records the step ID, module, technique, and trust boundary
- Follows
on_successrouting (or implicit next-in-order) - Stops at terminal steps or end of list
- Returns a
TraceResultwith the ordered step path and unique trust boundaries crossed