Skip to main content

Executor Agent

The Executor Agent takes structured plans from the Planner and executes them against real APIs, managing parallel execution, dependency resolution, template parameter substitution, and error recovery.

What It Does

The Executor Agent is the second stage of the agent pipeline:

Structured Plan → EXECUTOR → Tool Results

Input: Plan with steps, parameters, and dependencies
Output: Array of ToolResult objects with execution data

Execution Model

Sequential vs Parallel

The Executor intelligently decides execution strategy based on dependencies:

// Independent steps → Parallel execution
{
steps: [
{ tool: "shipments_list", params: {} },
{ tool: "facilities_list", params: {} }, ├─ Execute simultaneously
{ tool: "inspections_list", params: {} }
]
}

// Dependent steps → Sequential execution
{
steps: [
{ tool: "facilities_list", params: {} }, // Step 1: Execute first
{
tool: "shipments_list", // Step 2: Execute after 1
params: { facility_id: "${step[0].data.*.id}" },
depends_on: [0]
}
]
}

Execution Graph

The Executor builds a dependency graph to determine execution order:

Step 0: facilities_list ─────┐

Step 1: shipments_list ───────┤
├──→ Step 3: analytics
Step 2: contaminants_list ────┘ (depends on 0,1,2)

Execution order:
1. Steps 0, 1, 2 run in parallel
2. Step 3 waits for all dependencies
3. Step 3 executes after all complete

Dependency Resolution Algorithm

Topological Sorting

function buildExecutionGraph(steps: PlanStep[]): Map<number, number[]> {
const graph = new Map<number, number[]>();

for (let i = 0; i < steps.length; i++) {
const deps = steps[i].depends_on || [];
graph.set(i, deps);
}

// Validate: no circular dependencies
detectCircularDependencies(graph);

return graph;
}

function findReadySteps(
steps: PlanStep[],
completed: Set<number>,
graph: Map<number, number[]>
): number[] {
const ready = [];

for (let i = 0; i < steps.length; i++) {
if (completed.has(i)) continue;

const deps = graph.get(i) || [];
const allDepsComplete = deps.every(dep => completed.has(dep));

if (allDepsComplete) {
ready.push(i);
}
}

return ready;
}

Circular Dependency Detection

// Algorithm detects cycles using DFS
function detectCircularDependencies(graph: Map<number, number[]>): void {
const visited = new Set<number>();
const recursionStack = new Set<number>();

function hasCycle(node: number): boolean {
visited.add(node);
recursionStack.add(node);

for (const dep of graph.get(node) || []) {
if (!visited.has(dep)) {
if (hasCycle(dep)) return true;
} else if (recursionStack.has(dep)) {
return true; // Cycle detected!
}
}

recursionStack.delete(node);
return false;
}

for (const node of graph.keys()) {
if (!visited.has(node) && hasCycle(node)) {
throw new Error('Circular dependency detected in plan');
}
}
}

Template Parameter System

Template Syntax

The Executor supports powerful template syntax for referencing previous results:

// Basic field access
"${step[N].data.field}"

// Array element access
"${step[N].data[0].field}"

// Nested object access
"${step[N].data[0].object.nested.field}"

// Array mapping (wildcard)
"${step[N].data.*.field}"

Template Resolution Examples

Example 1: Single Field Reference

// Previous result
results[0].data = [
{ id: "F1", name: "Berlin Plant" },
{ id: "F2", name: "Munich Center" }
];

// Template
template = "${step[0].data[0].id}";

// Resolved value
"F1"

Example 2: Array Mapping

// Previous result
results[0].data = [
{ id: "S1", facility_id: "F1" },
{ id: "S2", facility_id: "F2" },
{ id: "S3", facility_id: "F1" }
];

// Template
template = "${step[0].data.*.id}";

// Resolved value
["S1", "S2", "S3"]

// Joined for API parameter
"S1,S2,S3"

Example 3: Nested Field Access

// Previous result
results[0].data = [
{
id: "S1",
facility: {
id: "F1",
location: { city: "Berlin" }
}
}
];

// Template
template = "${step[0].data[0].facility.location.city}";

// Resolved value
"Berlin"

Example 4: Array of Nested Fields

// Previous result
results[0].data = [
{ id: "S1", facility: { id: "F1", name: "Plant A" } },
{ id: "S2", facility: { id: "F2", name: "Plant B" } },
{ id: "S3", facility: { id: "F1", name: "Plant A" } }
];

// Template
template = "${step[0].data.*.facility.id}";

// Resolved value
["F1", "F2", "F1"]

Configuration Options

interface ExecutorConfig {
maxParallelExecutions: number; // Max concurrent operations
toolTimeout: number; // Timeout per tool (ms)
maxRetries: number; // Max retry attempts
retryDelay: number; // Delay between retries (ms)
failFast: boolean; // Stop on first error
}

// Create with custom config
const executor = new ExecutorAgent(mcpServer, {
maxParallelExecutions: 10, // Higher concurrency
toolTimeout: 60000, // 60s timeout
maxRetries: 5, // More retries
retryDelay: 2000, // 2s between retries
failFast: false // Continue on errors
});

Configuration Trade-offs

SettingLow ValueHigh Value
maxParallelExecutionsSafer, less loadFaster, more load
toolTimeoutFail fastWait longer
maxRetriesFail quicklyMore resilient
retryDelayFast failureAPI-friendly
failFastPartial resultsAll-or-nothing

Real API Execution Examples

Example 1: Simple Execution

const plan = {
steps: [{
tool: "shipments_list",
params: { limit: 5 }
}]
};

const results = await executor.execute(plan);

// Result:
[
{
success: true,
tool: "shipments_list",
data: [
{ id: "S1", status: "delivered", ... },
{ id: "S2", status: "rejected", ... },
{ id: "S3", status: "in_transit", ... }
],
metadata: {
executionTime: 45, // ms
timestamp: "2025-10-12T06:00:00.000Z"
}
}
]

Example 2: Parallel Execution

const plan = {
steps: [
{ tool: "shipments_list", params: { limit: 5 }, parallel: true },
{ tool: "facilities_list", params: {}, parallel: true },
{ tool: "inspections_list", params: { limit: 5 }, parallel: true }
]
};

const startTime = Date.now();
const results = await executor.execute(plan);
const duration = Date.now() - startTime;

console.log('Executed 3 queries in', duration, 'ms');
// Typically 200-400ms (parallel)
// vs 600-1200ms (if sequential)

// All 3 results available simultaneously
console.log(results[0].tool); // "shipments_list"
console.log(results[1].tool); // "facilities_list"
console.log(results[2].tool); // "inspections_list"

Example 3: Sequential with Dependencies

const plan = {
steps: [
{
tool: "facilities_list",
params: { location: "Berlin" }
},
{
tool: "shipments_list",
params: {
facility_id: "${step[0].data.*.id}"
},
depends_on: [0]
}
]
};

const results = await executor.execute(plan);

// Step 1 executes first
console.log(results[0].data);
// → [{ id: "F1", ... }, { id: "F2", ... }]

// Step 2 receives resolved parameter
console.log(results[1].data);
// → [{ id: "S1", facility_id: "F1", ... }, ...]

Example 4: 3-Level Dependency Chain

const plan = {
steps: [
{
tool: "facilities_list",
params: { location: "Berlin", limit: 1 }
},
{
tool: "shipments_list",
params: {
facility_id: "${step[0].data[0].id}",
limit: 10
},
depends_on: [0]
},
{
tool: "contaminants_list",
params: {
shipment_ids: "${step[1].data.*.id}"
},
depends_on: [1]
}
]
};

const results = await executor.execute(plan);

// Execution order:
// 1. Get facilities in Berlin → F1
// 2. Get shipments from F1 → S1, S2, S3
// 3. Get contaminants for S1,S2,S3 → C1, C2

console.log('Executed 3-level chain:', results.length, 'steps');

Example 5: Mixed Parallel and Sequential

const plan = {
steps: [
{ tool: "facilities_list", params: {}, parallel: true }, // ┐
{ tool: "shipments_list", params: {}, parallel: true }, // ├─ Parallel
{
tool: "contaminants_list", // Sequential
params: { facility_id: "${step[0].data[0].id}" },
depends_on: [0]
}
]
};

// Execution timeline:
// t=0ms: Steps 0 and 1 start in parallel
// t=150ms: Step 0 completes
// t=150ms: Step 2 starts (dependency met)
// t=200ms: Step 1 completes
// t=300ms: Step 2 completes

const results = await executor.execute(plan);

Error Handling Strategies

Retry Logic

// Executor retries failed operations automatically

async executeWithRetry(operation, toolName) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await Promise.race([
operation(),
timeout(toolTimeout)
]);
return result;
} catch (error) {
if (attempt < maxRetries) {
const delay = retryDelay * attempt; // Exponential backoff
await sleep(delay);
}
}
}
throw error;
}

Timeout Protection

// Each tool execution has a timeout

const executor = new ExecutorAgent(mcpServer, {
toolTimeout: 30000 // 30 seconds
});

// If tool takes longer than 30s:
// → Throws timeout error
// → Retries if retries remaining
// → Returns error result if all retries exhausted

Partial Failure Handling

// By default, Executor continues on failures (failFast: false)

const plan = {
steps: [
{ tool: "shipments_list", params: {} }, // ✓ Succeeds
{ tool: "invalid_tool", params: {} }, // ✗ Fails
{ tool: "facilities_list", params: {} } // ✓ Succeeds
]
};

const results = await executor.execute(plan);

console.log(results[0].success); // true
console.log(results[1].success); // false
console.log(results[2].success); // true

// All steps attempted, 2/3 successful

Fail Fast Mode

// With failFast: true, execution stops on first error

const executor = new ExecutorAgent(mcpServer, {
failFast: true
});

const results = await executor.execute(plan);
// Throws error if any step fails

Performance Benchmarks

Parallel vs Sequential

Test scenario: 3 independent API calls

// Parallel execution
const parallelPlan = {
steps: [
{ tool: "shipments_list", params: {}, parallel: true },
{ tool: "facilities_list", params: {}, parallel: true },
{ tool: "inspections_list", params: {}, parallel: true }
]
};

// Results:
// Parallel: ~300ms total
// Sequential: ~900ms total
// Speedup: 3x faster

Performance Data (from integration tests):

ScenarioParallel TimeSequential TimeSpeedup
3 queries300ms900ms3.0x
5 queries450ms2250ms5.0x
10 queries800ms8000ms10.0x

Dependency Chain Performance

// 3-level chain
Step 1: 150ms ───→ Step 2: 180ms ───→ Step 3: 200ms

// Total time: 530ms (sequential is required)
// vs if all parallel: ~200ms (but incorrect results)

API Reference

ExecutorAgent Class

class ExecutorAgent {
constructor(
mcpServer: MCPServer,
config?: Partial<ExecutorConfig>
);

async execute(plan: Plan): Promise<ToolResult[]>;
}

Methods

execute(plan)

Executes a plan and returns tool results.

Parameters:

  • plan (Plan): Structured execution plan from Planner

Returns: Promise<ToolResult[]>
Array of results, one per step (in plan order)

Throws:

  • Error: If circular dependency detected
  • Error: If failFast=true and any step fails

Example:

const plan = {
steps: [{ tool: "shipments_list", params: { limit: 10 } }]
};

const results = await executor.execute(plan);

console.log(results[0].success); // true
console.log(results[0].data); // Array of shipments
console.log(results[0].metadata.executionTime); // 45

Configuration

interface ExecutorConfig {
maxParallelExecutions: number; // Default: 5
toolTimeout: number; // Default: 30000 (30s)
maxRetries: number; // Default: 3
retryDelay: number; // Default: 1000 (1s)
failFast: boolean; // Default: false
}

Code Examples

Basic Usage

import { ExecutorAgent } from './agents/executor.js';
import { MCPServer } from './mcp/server.js';
import { registerAllTools } from './tools/index.js';

// Initialize MCP server with tools
const mcpServer = new MCPServer('my-app', '1.0.0');
registerAllTools(mcpServer, 'http://localhost:4000/api');

// Create executor
const executor = new ExecutorAgent(mcpServer);

// Execute plan
const plan = {
steps: [{
tool: "shipments_list",
params: { limit: 5 }
}]
};

const results = await executor.execute(plan);

console.log('Execution complete:');
console.log('Success:', results[0].success);
console.log('Data:', results[0].data);
console.log('Time:', results[0].metadata.executionTime, 'ms');

Complex Dependencies

// Multi-step plan with dependencies
const plan = {
steps: [
{
tool: "facilities_list",
params: { location: "Berlin" }
},
{
tool: "shipments_list",
params: {
facility_id: "${step[0].data.*.id}",
has_contaminants: true
},
depends_on: [0]
},
{
tool: "contaminants_list",
params: {
shipment_ids: "${step[1].data.*.id}"
},
depends_on: [1]
}
]
};

const results = await executor.execute(plan);

// Results array contains all 3 steps
console.log('Facilities found:', results[0].data.length);
console.log('Contaminated shipments:', results[1].data.length);
console.log('Contaminants detected:', results[2].data.length);

Error Recovery

const executor = new ExecutorAgent(mcpServer, {
maxRetries: 5,
retryDelay: 2000,
failFast: false
});

const plan = {
steps: [
{ tool: "shipments_list", params: {} }, // Might fail
{ tool: "facilities_list", params: {} } // Should succeed
]
};

try {
const results = await executor.execute(plan);

// Check each result individually
results.forEach((result, index) => {
if (result.success) {
console.log(`Step ${index} succeeded:`, result.data.length, 'items');
} else {
console.error(`Step ${index} failed:`, result.error?.message);
}
});

} catch (error) {
console.error('Execution failed:', error);
}

Custom Timeout Configuration

// Short timeout for fast-failing
const fastExecutor = new ExecutorAgent(mcpServer, {
toolTimeout: 5000, // 5 seconds
maxRetries: 1
});

// Long timeout for slow operations
const patientExecutor = new ExecutorAgent(mcpServer, {
toolTimeout: 120000, // 2 minutes
maxRetries: 3
});

Advanced Features

Conditional Execution

// Execute steps based on previous results
const plan = {
steps: [
{ tool: "shipments_list", params: { has_contaminants: true } },
{
tool: "contaminants_list",
params: {
shipment_ids: "${step[0].data.*.id}"
},
depends_on: [0]
}
]
};

// If step 0 returns empty array:
// → step 1 receives empty shipment_ids
// → step 1 returns empty results (not an error)

Result Aggregation

// Collect results from multiple steps
const results = await executor.execute(multiStepPlan);

const allData = results
.filter(r => r.success)
.flatMap(r => r.data);

console.log('Total items retrieved:', allData.length);

Metadata Tracking

const results = await executor.execute(plan);

// Track performance
const totalTime = results.reduce(
(sum, r) => sum + r.metadata.executionTime,
0
);

console.log('Total execution time:', totalTime, 'ms');

// Track failures
const failures = results.filter(r => !r.success);
console.log('Failed steps:', failures.length);

// Track retries
const retriedSteps = results.filter(
r => r.metadata.retries && r.metadata.retries > 0
);
console.log('Steps that needed retries:', retriedSteps.length);

Testing

Unit Tests

describe('ExecutorAgent', () => {
it('should execute simple plan', async () => {
const mockTool = {
execute: jest.fn().mockResolvedValue({
success: true,
data: [{ id: 'S1' }]
})
};

const mockMCP = {
getTool: jest.fn().mockReturnValue(mockTool)
};

const executor = new ExecutorAgent(mockMCP as any);

const plan = {
steps: [{ tool: 'test_tool', params: {} }]
};

const results = await executor.execute(plan);

expect(results).toHaveLength(1);
expect(results[0].success).toBe(true);
});
});

Integration Tests

See actual test outputs in Testing Guide.

Test Coverage:

  • ✅ Simple execution
  • ✅ Parallel execution
  • ✅ Sequential dependencies
  • ✅ 3-level dependency chains
  • ✅ Template resolution
  • ✅ Error recovery
  • ✅ Timeout handling
  • ✅ Partial failures
  • ✅ Metadata tracking
  • ✅ Performance benchmarks

Troubleshooting

Tool Not Found

Issue: Tool not found: shipments_list

Solutions:

  1. Verify tool is registered: mcpServer.getTool('shipments_list')
  2. Check tool name matches exactly (case-sensitive)
  3. Ensure registerAllTools() was called
  4. Verify MCP server initialization

Template Resolution Errors

Issue: Step 0 did not produce valid data

Solutions:

  1. Check previous step succeeded: results[0].success === true
  2. Verify data structure: console.log(results[0].data)
  3. Check template syntax: ${step[N].data.field}
  4. Ensure array access is valid: [0] exists

Circular Dependency

Issue: Circular dependency detected in plan

Solutions:

  1. Review depends_on arrays for cycles
  2. Step N cannot depend on Step M if M depends on N
  3. Regenerate plan with Planner
  4. Manually inspect dependency graph

Timeout Errors

Issue: Timeout after 30000ms

Solutions:

  1. Increase toolTimeout configuration
  2. Check API server responsiveness
  3. Reduce data volume (add limit parameter)
  4. Check network connectivity

Performance Optimization

1. Maximize Parallelism

// ❌ Suboptimal: All sequential
{
steps: [
{ tool: "shipments_list", params: {}, depends_on: [] },
{ tool: "facilities_list", params: {}, depends_on: [0] }, // Unnecessary dep
{ tool: "inspections_list", params: {}, depends_on: [1] } // Unnecessary dep
]
}

// ✅ Optimal: All parallel
{
steps: [
{ tool: "shipments_list", params: {}, parallel: true },
{ tool: "facilities_list", params: {}, parallel: true },
{ tool: "inspections_list", params: {}, parallel: true }
]
}

2. Minimize Dependency Depth

// ❌ Suboptimal: Deep chain
Step 1Step 2Step 3Step 4Step 5

// ✅ Optimal: Wide parallel
Step 1
Step 2 ├─→ Step 5
Step 3
Step 4

3. Use Efficient Parameters

// ❌ Inefficient: Get all, filter later
{ tool: "shipments_list", params: {} } // Returns 1000s

// ✅ Efficient: Filter at source
{ tool: "shipments_list", params: {
limit: 50,
has_contaminants: true,
date_from: "2025-10-01"
}}