How Shannon Enforces Agent Prerequisites for Deterministic Execution Order in session-manager

Shannon enforces agent prerequisites through a static dependency map in session-manager.ts that defines prerequisite chains, a topologically sorted AGENT_ORDER array, and runtime validation in Temporal workflows that blocks execution until all declared dependencies complete.

Shannon is an open-source security automation framework developed by KeygraphHQ that orchestrates complex vulnerability assessment pipelines. The session-manager.ts module serves as the central authority for agent metadata, defining which reconnaissance and exploitation agents must complete before others can begin, ensuring deterministic execution even when running independent agents in parallel.

Understanding Agent Prerequisites in Shannon

The AgentDefinition Interface

Every agent in Shannon is described by an AgentDefinition object that declares its runtime requirements. Located in src/session-manager.ts at lines 10-15, this interface includes a prerequisites array containing AgentName values that must be satisfied before the agent executes.

interface AgentDefinition {
  name: AgentName;
  description: string;
  prerequisites: AgentName[]; // Agents that must complete first
  // ... additional metadata
}

Static Prerequisite Mapping with AGENTS

The constant AGENTS (lines 24-33 and 79-83) enumerates every agent alongside its prerequisite list, creating a compile-time dependency graph. For example, the recon agent declares ['pre-recon'] as a prerequisite, while the report agent lists all exploitation agents to ensure comprehensive data collection before final generation.

const AGENTS: Record<AgentName, AgentDefinition> = {
  'pre-recon': {
    name: 'pre-recon',
    prerequisites: [],
    // ...
  },
  'recon': {
    name: 'recon',
    prerequisites: ['pre-recon'], // Depends on pre-recon completion
    // ...
  },
  'report': {
    name: 'report',
    prerequisites: ['exploit-1', 'exploit-2', 'exploit-3'], // Depends on all exploits
    // ...
  }
};

Enforcing Execution Order in session-manager

Topological Sorting via AGENT_ORDER

Shannon materializes the dependency graph into a linear execution sequence through the AGENT_ORDER array (lines 86-101). This ordered array represents a valid topological sort of the prerequisite relationships, ensuring that every agent appears after its dependencies in the sequence.

const AGENT_ORDER: AgentName[] = [
  'pre-recon',
  'recon',      // recon comes after pre-recon
  'vuln-scan',
  'exploit-1',  // exploits can be parallelized but come after recon
  'exploit-2',
  'exploit-3',
  'report'      // report comes last, after all exploits
];

Runtime Prerequisite Validation

The actual enforcement occurs in src/temporal/workflows.ts, where the workflow engine iterates over AGENT_ORDER before scheduling agents. Before launching any agent, the engine inspects AGENTS[agent].prerequisites and verifies that all listed dependencies exist in the set of already-completed agents. Only when all prerequisites are satisfied does the workflow dispatch the activity via src/temporal/activities.ts.

// Conceptual implementation from Shannon's workflow logic
async function executeAgentWorkflow() {
  const completed = new Set<AgentName>();
  
  for (const agentName of AGENT_ORDER) {
    const agentDef = AGENTS[agentName];
    
    // Enforce prerequisites
    const unmetPrereqs = agentDef.prerequisites.filter(
      prereq => !completed.has(prereq)
    );
    
    if (unmetPrereqs.length > 0) {
      throw new Error(
        `Cannot execute ${agentName}: missing prerequisites ${unmetPrereqs.join(', ')}`
      );
    }
    
    // Execute via Temporal activity
    await runAgentActivity(agentName);
    completed.add(agentName);
  }
}

Parallel Execution of Independent Agents

Grouping Agents with getParallelGroups

Shannon optimizes pipeline throughput by identifying agents that share no mutual dependencies and can run concurrently. The getParallelGroups() function (implemented in session-manager.ts) analyzes the AGENTS prerequisite map to partition AGENT_ORDER into parallelizable batches.

For example, multiple exploitation agents may depend on the reconnaissance phase completing, but not on each other. The function groups these into a single parallel batch, allowing the Temporal workflow to execute them simultaneously using Promise.all(), while still respecting the prerequisite chain that requires recon to finish first.

import { getParallelGroups, AGENT_ORDER } from './session-manager';

async function runParallelVulnPhase() {
  const groups = getParallelGroups();
  
  // Execute each group sequentially, but agents within a group in parallel
  for (const group of groups) {
    await Promise.all(
      group.map(agentName => runAgentActivity(agentName))
    );
  }
}

Implementation Example

The following example demonstrates a complete prerequisite-aware scheduler that mirrors Shannon's enforcement logic. This pattern ensures that agent prerequisites are validated before execution while supporting both sequential and parallel execution strategies.

import {
  AGENTS,
  AGENT_ORDER,
  getParallelGroups,
  AgentName,
} from './session-manager';

class PrerequisiteEnforcer {
  private completed = new Set<AgentName>();

  async runSequential(): Promise<void> {
    for (const agent of AGENT_ORDER) {
      await this.validateAndRun(agent);
    }
  }

  async runParallelGroups(): Promise<void> {
    const groups = getParallelGroups();
    
    for (const group of groups) {
      // Validate all agents in group before parallel execution
      for (const agent of group) {
        this.checkPrerequisites(agent);
      }
      
      // Execute in parallel
      await Promise.all(group.map(agent => this.runAgent(agent)));
      
      // Mark all as completed
      group.forEach(agent => this.completed.add(agent));
    }
  }

  private async validateAndRun(agent: AgentName): Promise<void> {
    this.checkPrerequisites(agent);
    await this.runAgent(agent);
    this.completed.add(agent);
  }

  private checkPrerequisites(agent: AgentName): void {
    const unmet = AGENTS[agent].prerequisites.filter(
      p => !this.completed.has(p)
    );
    
    if (unmet.length > 0) {
      throw new Error(
        `Prerequisite violation: ${agent} requires ${unmet.join(', ')}`
      );
    }
  }

  private async runAgent(agent: AgentName): Promise<void> {
    // Integration with Temporal activities or direct execution
    console.log(`Executing: ${agent}`);
  }
}

Summary

  • Static dependency mapping: The AGENTS constant in src/session-manager.ts defines every agent's prerequisites at compile time, creating a deterministic dependency graph.
  • Topological ordering: AGENT_ORDER provides a linearized sequence that respects all prerequisite chains, ensuring dependent agents always appear after their requirements.
  • Runtime validation: The Temporal workflow in src/temporal/workflows.ts enforces prerequisites by checking AGENTS[agent].prerequisites against completed agents before dispatching activities.
  • Parallel optimization: getParallelGroups() identifies independent agents that share no mutual dependencies, allowing concurrent execution while maintaining prerequisite integrity.

Frequently Asked Questions

What is the role of the AGENTS constant in session-manager.ts?

The AGENTS constant serves as the central registry for all agent metadata in Shannon. Defined at lines 24-33 and 79-83 of src/session-manager.ts, it maps each AgentName to an AgentDefinition object containing the prerequisites array. This static mapping provides the single source of truth that both the execution order calculator and runtime workflow use to determine dependency relationships.

How does Shannon handle circular dependencies between agents?

Shannon prevents circular dependencies through its AGENT_ORDER array, which represents a valid topological sort of the prerequisite graph. Because AGENT_ORDER is statically defined at lines 86-101 of session-manager.ts and manually ordered, any circular dependency would be immediately apparent during development or code review. The linear sequence ensures that an agent always appears after its prerequisites, making circular references impossible to satisfy in the execution plan.

Can agents run in parallel if they share the same prerequisites?

Yes, agents that share prerequisites but have no mutual dependencies can execute in parallel. The getParallelGroups() function in session-manager.ts analyzes the AGENTS prerequisite map to partition AGENT_ORDER into batches where agents within each batch are independent. For example, multiple exploitation agents may both depend on the reconnaissance phase completing, but not on each other, allowing the Temporal workflow to execute them simultaneously using Promise.all().

Where is the prerequisite check implemented at runtime?

The runtime prerequisite validation occurs in src/temporal/workflows.ts, where the workflow engine orchestrates the agent pipeline. Before dispatching any agent activity via src/temporal/activities.ts, the workflow checks AGENTS[agent].prerequisites against a set of already-completed agents. The engine only proceeds when all listed prerequisites are satisfied, throwing an error if any dependency remains unmet. This enforcement guarantees that the static dependency graph defined in session-manager.ts is respected during actual execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →