Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Orchestration Frameworks

ASecurity

Designs and implements orchestration frameworks for multi-agent systems

4 stars
0 votes
0 copies
3 views
Added 9/4/2026
businesspythonreactnodeawstestinggitdatabaseperformancedocumentation

Works with

cli

Security Analysis

A100/100

Scanned 9/4/2026

Install to Claude Code

$npx -y skills add paulpas/agent-skill-router --skill orchestration-frameworks --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Orchestration Frameworks?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Orchestration Frameworks
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/paulpas-orchestration-frameworks/badge)](https://www.skillsdirectory.com/skills/paulpas-orchestration-frameworks)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---




name: orchestration-frameworks
description: Designs and implements orchestration frameworks for multi-agent systems
  including LangGraph, AutoGen, CrewAI, Temporal, and Prefect with workflow patterns,
  state management, and fault tolerance.
license: MIT
compatibility: opencode
metadata:
  version: "1.0.0"
  domain: agent
  triggers: orchestration framework, multi-agent system, agent coordination, workflow engine, langgraph, autogen, crewai, temporal engine
  archetypes:
  - orchestration
  - strategic
  anti_triggers:
  - brainstorming
  - vague ideation
  - single-agent monolith
  response_profile:
    verbosity: medium
    directive_strength: high
    abstraction_level: tactical
  role: orchestration
  scope: orchestration
  output-format: analysis
  content-types:
  - guidance
  - examples
  - do-dont
  - diagrams
  related-skills: task-decomposition-engine, parallel-skill-runner, confidence-based-selector




---




# Orchestration Framework Engineering

Architects and implements orchestration frameworks that coordinate multi-agent systems, manage workflow state, and handle fault tolerance. You design the control plane that determines how agents delegate tasks, share context, and recover from failures.

## TL;DR Checklist

- [ ] Map agent capabilities to available tools before selecting an orchestration pattern
- [ ] Design explicit state machine for multi-step workflows — never rely on implicit ordering
- [ ] Implement circuit breaker pattern for all inter-agent communication paths
- [ ] Define fallback routing for every branching point with at least one alternative path
- [ ] Select framework based on workload type: synchronous (LangGraph) vs asynchronous (Temporal) vs collaborative (CrewAI)
- [ ] Instrument every step with structured logging, trace IDs, and latency metrics
- [ ] Test failure injection before deploying to production

---

## When to Use

Use this skill when:

- Designing a multi-agent system where tasks span 3+ agents with non-trivial dependencies
- Building a workflow engine that requires state persistence, checkpointing, and recovery from crashes
- Coordinating agent collaboration across distributed processes or microservice boundaries
- Implementing human-in-the-loop approval gates within an automated agent pipeline
- Architecting event-driven agent meshes where agents react to shared events on a bus
- Migrating from ad-hoc script-based agent coordination to a structured orchestration layer

---

## When NOT to Use

Avoid this skill for:

- Single-agent tasks or linear scripts — use `code-philosophy` (5 Laws of Elegant Defense) and direct implementation instead. Overhead outweighs benefit for workflows under 3 steps.
- Real-time trading execution where latency is sub-millisecond — orchestration layers add unpredictable overhead. Use synchronous function calls directly.
- Read-heavy analytical queries on static datasets — these don't involve agent coordination or workflow state.

---

## Core Workflow

### 1. Classify Orchestration Pattern

Determine which pattern fits the workload by analyzing the dependency graph of agent tasks:

**Sequential Pipeline:** Tasks execute in strict order with data flowing forward. Example: ingest → transform → validate → report. Use when outputs are deterministic and dependencies are linear.

**Parallel Fan-Out / Fan-In:** One initiator dispatches N independent subtasks, then aggregates results. Example: send 5 research agents different topics simultaneously, collect summaries. Use when subtasks are independent and latency-critical.

**Hierarchical Delegation:** A supervisor agent decomposes tasks, delegates to worker agents, and synthesizes outputs. Example: project manager agent assigns coding, testing, and documentation to specialist agents. Use when tasks have inherent role-based specialization.

**Dynamic Graph:** The next node in the workflow is determined at runtime based on intermediate results. Example: a code-analysis agent routes to either "fix-bugs" or "add-features" based on its findings. Use when branching depends on computed outcomes.

```
                    +-------------------+
                    |   Task Classifier  |
                    +--------+----------+
                             |
            +----------------+----------------+
            |                |                |
     Sequential Pipeline   Fan-Out/Fan-In   Dynamic Graph
            |                |                |
    A -> B -> C          Init -> [X,Y,Z]    Init -> NodeA -> ?
                            |    |    |        (route at runtime)
                         Result collected & merged
```

**Checkpoint:** Verify no circular dependencies exist in the task dependency graph before proceeding. Run a topological sort on your agent DAG — if it fails, restructure the dependency graph.

### 2. Select Orchestration Engine

Choose based on workload characteristics and operational requirements:

| Workload Type | Recommended Engine | Why |
|---|---|---|
| Deterministic sequential flows with shared state | LangGraph (StateGraph) | Typed state, explicit edges, human-in-the-loop built-in |
| Async distributed workflows with retries & recovery | Temporal.io | Durable execution, exactly-once semantics, saga support |
| Multi-agent collaborative research / brainstorming | CrewAI or AutoGen | Role-based agents, conversation loops, tool sharing |
| Event-driven agent mesh (reactive architecture) | Custom event bus + agent registry | Decoupled agents, pub/sub routing, horizontal scaling |
| Scheduled batch pipelines with complex DAGs | Prefect | UI for DAG visualization, retry policies, notifications |

**Decision matrix:**

```python
def select_orchestration_engine(workload: dict) -> str:
    """Select orchestration engine based on workload characteristics.
    
    Args:
        workload: Dict with keys: latency_sensitivity (high/med/low),
                  state_persistence_required (bool), retry_count (int),
                  human_approval_needed (bool), agent_count (int)
    
    Returns:
        Engine name string
    """
    if workload.get("human_approval_needed") and workload["retry_count"] <= 3:
        return "langgraph"
    elif workload.get("state_persistence_required") and workload["retry_count"] > 3:
        return "temporal"
    elif workload["agent_count"] > 5:
        return "crewai" if workload.get("collaborative", False) else "temporal"
    elif workload.get("latency_sensitivity") == "high":
        return "langgraph"
    else:
        return "prefect"
```

**Checkpoint:** Confirm selected framework supports required features: state persistence, retry policies, and human-in-the-loop capabilities. Cross-check against the feature matrix of each engine.

### 3. Define Shared State Schema

Design the shared state structure that all agents read/write. Use typed schemas with Pydantic to enforce contracts:

```python
from pydantic import BaseModel, Field, field_validator
from typing import Optional, Any, Literal
from enum import Enum
import datetime


class AgentStatus(str, Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"


class OrchestrationState(BaseModel):
    """Typed schema for multi-agent orchestration shared state.
    
    Defines the contract between orchestrator and all participating agents.
    Fields marked with docstrings as @input or @output indicate which
    agents must declare them in their capability schema.
    """
    # Workflow metadata (global, set by orchestrator)
    workflow_id: str = Field(description="@input orchestrator-generated UUID")
    status: AgentStatus = AgentStatus.PENDING
    created_at: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
    updated_at: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)

    # Shared context (global, readable by all agents)
    context: dict[str, Any] = Field(default_factory=dict, description="@readable all_agents")
    
    # Per-agent result slots (global, written by assigned agent, read by downstream)
    agent_results: dict[str, Any] = Field(default_factory=dict, description="@writable <assigned_agent>")

    # Routing metadata (local per agent during execution)
    routing_decision: Optional[dict[str, Any]] = Field(default=None, description="@write router")
    fallback_triggered: bool = False
    retry_count: int = 0
    max_retries: int = 3

    @field_validator("agent_results")
    @classmethod
    def validate_result_structure(cls, v: dict) -> dict:
        """Ensure all results contain required fields."""
        for agent_name, result in v.items():
            if not isinstance(result, dict):
                raise ValueError(f"Result for {agent_name} must be a dict")
            if "status" not in result or "output" not in result:
                raise ValueError(
                    f"Result for {agent_name} missing 'status' or 'output' keys"
                )
        return v

    def mark_agent_complete(self, agent_name: str, output: Any, error: Optional[str] = None) -> None:
        """Mark an agent's task as complete with its output."""
        self.agent_results[agent_name] = {
            "status": "completed" if error is None else "failed",
            "output": output,
            "error": error,
            "completed_at": datetime.datetime.utcnow().isoformat(),
        }
        self.updated_at = datetime.datetime.utcnow()
```

**Checkpoint:** Validate state schema covers all data paths between agents; no agent should write to state it doesn't declare in its input schema. Run a static analysis pass that verifies each agent's declared inputs/outputs against the `OrchestrationState` fields.

### 4. Implement Agent Routing and Delegation

Write the routing logic that routes tasks to appropriate agents based on capability matching, confidence scores, and current load:

```python
from dataclasses import dataclass, field
from collections import defaultdict


@dataclass
class AgentCapability:
    """Declares what an agent can do, its reliability profile, and current load."""
    name: str
    tools: list[str] = field(default_factory=list)
    confidence_score: float = Field(default=0.5, ge=0.0, le=1.0)
    max_concurrent_tasks: int = 5
    current_load: int = 0
    fallback_chain: list[str] = field(default_factory=list)


class CapabilityRouter:
    """Routes tasks to agents based on capability matching and load balancing.
    
    Follows the principle of capability-based discovery over hard-coded routing.
    Implements graceful degradation through fallback chains.
    """
    
    def __init__(self) -> None:
        self.agents: dict[str, AgentCapability] = {}
        self.routing_log: list[dict] = []

    def register_agent(self, capability: AgentCapability) -> None:
        self.agents[capability.name] = capability

    def route_task(
        self,
        task_description: str,
        required_tools: list[str],
    ) -> dict[str, Any]:
        """Route a task to the best available agent.
        
        Args:
            task_description: Natural language description of the task.
            required_tools: List of tool names the task requires.
            
        Returns:
            Routing decision with assigned agent, rationale, and fallback chain.
        """
        candidates = self._score_agents(required_tools)
        
        if not candidates:
            decision = self._build_no_candidates_decision(task_description, required_tools)
        else:
            best_agent = candidates[0]
            decision = self._build_routing_decision(best_agent, task_description, required_tools)

        # Log every routing decision for auditability (code-philosophy: Intentional Naming)
        self.routing_log.append(decision)
        return decision

    def _score_agents(self, required_tools: list[str]) -> list[AgentCapability]:
        """Score and sort agents by capability match and available capacity."""
        scored = []
        for agent in self.agents.values():
            tool_overlap = len(set(required_tools) & set(agent.tools))
            score = (tool_overlap / max(len(required_tools), 1)) * agent.confidence_score
            
            # Penalize overloaded agents
            if agent.current_load >= agent.max_concurrent_tasks:
                score *= 0.1  # Still consider as last resort if fallback chain exists
            
            scored.append((score, agent))

        scored.sort(key=lambda x: x[0], reverse=True)
        return [agent for _, agent in scored if agent.current_load < agent.max_concurrent_tasks or agent.fallback_chain]

    def _build_routing_decision(
        self,
        agent: AgentCapability,
        task_description: str,
        required_tools: list[str],
    ) -> dict[str, Any]:
        """Build a structured routing decision with rationale."""
        return {
            "assigned_agent": agent.name,
            "rationale": f"Matched tools: {len(set(required_tools) & set(agent.tools))}/{len(required_tools)}, confidence: {agent.confidence_score:.2f}",
            "fallback_chain": agent.fallback_chain if agent.current_load >= agent.max_concurrent_tasks else [],
            "timestamp": datetime.datetime.utcnow().isoformat(),
            "trace_id": f"route-{datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{agent.name}",
        }

    def _build_no_candidates_decision(
        self, task_description: str, required_tools: list[str]
    ) -> dict[str, Any]:
        """Handle case where no agent can fulfill the task."""
        return {
            "assigned_agent": None,
            "rationale": f"No agent supports tools: {required_tools}",
            "fallback_chain": [],
            "error": "UNROUTABLE_TASK",
            "timestamp": datetime.datetime.utcnow().isoformat(),
        }
```

**Checkpoint:** Every routing decision must have a logged fallback path; no silent failures. Verify the `routing_log` contains at least one entry with a non-empty `fallback_chain` when testing degraded scenarios.

### 5. Add Fault Tolerance Layers

Implement circuit breakers, retry with exponential backoff, and checkpoint-based recovery:

```python
import time
import asyncio
from typing import Callable, TypeVar
from enum import Enum


T = TypeVar("T")


class CircuitState(str, Enum):
    CLOSED = "closed"       # Normal operation
    OPEN = "open"           # Failures exceeded threshold; reject calls
    HALF_OPEN = "half_open" # Testing if service recovered


class CircuitBreaker:
    """Circuit breaker for inter-agent communication.
    
    Protects against cascading failures when an agent becomes unavailable.
    Uses a sliding window failure counter with configurable thresholds.
    """
    
    def __init__(
        self,
        name: str,
        failure_threshold: int = 5,
        recovery_timeout: float = 30.0,
        half_open_max_calls: int = 1,
    ) -> None:
        self.name = name
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.half_open_max_calls = half_open_max_calls
        
        self._failure_count: int = 0
        self._success_count: int = 0
        self._state: CircuitState = CircuitState.CLOSED
        self._last_failure_time: float = 0.0
        self._half_open_calls: int = 0

    @property
    def state(self) -> CircuitState:
        """Transition to half-open if recovery timeout has elapsed."""
        if self._state == CircuitState.OPEN and \
           time.monotonic() - self._last_failure_time >= self.recovery_timeout:
            self._state = CircuitState.HALF_OPEN
            self._half_open_calls = 0
        return self._state

    @property
    def is_available(self) -> bool:
        """Check if the circuit allows calls through."""
        current_state = self.state
        if current_state == CircuitState.CLOSED:
            return True
        if current_state == CircuitState.HALF_OPEN:
            return self._half_open_calls < self.half_open_max_calls
        return False

    def record_success(self) -> None:
        """Record a successful call. Reset circuit on recovery."""
        if self.state == CircuitState.HALF_OPEN:
            self._success_count += 1
            if self._success_count >= self.half_open_max_calls:
                self._reset()
        elif self.state == CircuitState.CLOSED:
            self._failure_count = max(0, self._failure_count - 1)

    def record_failure(self) -> None:
        """Record a failed call. Open circuit if threshold reached."""
        self._failure_count += 1
        self._last_failure_time = time.monotonic()
        
        if self._failure_count >= self.failure_threshold:
            self._state = CircuitState.OPEN

    def _reset(self) -> None:
        """Reset circuit breaker to closed state."""
        self._state = CircuitState.CLOSED
        self._failure_count = 0
        self._success_count = 0

    def __call__(self, func: Callable[..., T]) -> Callable[..., T]:
        """Decorator to wrap a function with circuit breaker protection."""
        async def wrapper(*args: Any, **kwargs: Any) -> T:
            if not self.is_available:
                raise CircuitOpenError(
                    f"Circuit '{self.name}' is OPEN. "
                    f"Failures: {self._failure_count}/{self.failure_threshold}"
                )
            
            try:
                result = await func(*args, **kwargs)
                self.record_success()
                return result
            except Exception as e:
                self.record_failure()
                raise CircuitDownstreamError(
                    f"Circuit '{self.name}' failed: {e}"
                ) from e
        
        return wrapper


class CircuitOpenError(Exception):
    """Raised when circuit breaker is open and call is rejected."""
    pass


class CircuitDownstreamError(Exception):
    """Raised when the downstream agent/service call fails."""
    pass


async def retry_with_backoff(
    func: Callable[..., T],
    max_retries: int = 3,
    base_delay: float = 1.0,
    backoff_multiplier: float = 2.0,
    exceptions_to_retry: tuple[type[Exception], ...] = (CircuitDownstreamError,),
) -> T:
    """Retry a function with exponential backoff and jitter.
    
    Implements the pattern from code-philosophy Law 3 (Atomic Predictability):
    each retry is an isolated, deterministic attempt.
    
    Args:
        func: Async callable to retry.
        max_retries: Maximum number of retry attempts.
        base_delay: Initial delay in seconds before first retry.
        backoff_multiplier: Multiplier for exponential backoff.
        exceptions_to_retry: Tuple of exception types that trigger a retry.
        
    Returns:
        The result of the successful function call.
        
    Raises:
        The last exception if all retries are exhausted.
    """
    last_exception: Exception | None = None
    
    for attempt in range(max_retries + 1):
        try:
            return await func()
        except exceptions_to_retry as e:
            last_exception = e
            if attempt < max_retries:
                delay = base_delay * (backoff_multiplier ** attempt)
                # Add jitter to prevent thundering herd
                import random
                jittered_delay = delay * (0.5 + random.random() * 0.5)
                time.sleep(jittered_delay)
    
    raise last_exception  # type: ignore[misc]


async def select_fallback_agent(
    primary_agent: str,
    fallback_chain: list[str],
    circuit_breakers: dict[str, CircuitBreaker],
) -> str | None:
    """Select the first available fallback agent from the chain.
    
    Args:
        primary_agent: The name of the failed primary agent.
        fallback_chain: Ordered list of fallback agent names.
        circuit_breakers: Registry of circuit breakers per agent.
        
    Returns:
        Name of an available fallback agent, or None if all unavailable.
    """
    # Always try primary first (it may have recovered)
    if primary_agent in circuit_breakers and circuit_breakers[primary_agent].is_available:
        return primary_agent
    
    for fallback in fallback_chain:
        if fallback in circuit_breakers and circuit_breakers[fallback].is_available:
            return fallback
    
    return None
```

**Checkpoint:** Test failure injection — kill one agent mid-workflow and verify the system recovers to last consistent state. Verify that the circuit breaker transitions from CLOSED → OPEN → HALF_OPEN → CLOSED through a full cycle under simulated conditions.

### 6. Add Observability

Instrument every orchestration step with structured logging, trace IDs, and metrics:

```python
import json
import logging
from contextvars import ContextVar
from typing import Any


# Trace ID propagated across agent boundaries via context variables
trace_id_var: ContextVar[str] = ContextVar("trace_id", default="unknown")
span_id_var: ContextVar[str] = ContextVar("span_id", default="unknown")

logger = logging.getLogger("orchestration")


def set_trace_context(trace_id: str, span_id: str) -> None:
    """Set trace context for the current execution flow."""
    trace_id_var.set(trace_id)
    span_id_var.set(span_id)


def log_orchestration_event(
    event_type: str,
    agent_name: str | None,
    step: str,
    details: dict[str, Any],
    level: str = "info",
) -> None:
    """Log structured orchestration events for observability.
    
    Every event includes trace_id for end-to-end correlation across
    agent boundaries. Follows the code-philosophy constraint that all
    observable behavior must be instrumented.
    """
    trace_id = trace_id_var.get()
    span_id = span_id_var.get()
    
    log_entry = {
        "trace_id": trace_id,
        "span_id": span_id,
        "event_type": event_type,  # routing_decision, agent_start, agent_complete, error, circuit_breaker, fallback_triggered
        "agent_name": agent_name,
        "step": step,
        **details,
    }
    
    log_func = getattr(logger, level, logger.info)
    log_func(json.dumps(log_entry, default=str))


async def instrumented_agent_call(
    agent_name: str,
    task: Any,
    orchestrator: "Orchestrator",  # Forward reference to avoid circular import
) -> dict[str, Any]:
    """Wrapper that instruments every agent call with tracing and metrics.
    
    Generates unique span IDs per invocation and propagates trace IDs
    across the full orchestration chain.
    """
    import uuid
    
    span_id = str(uuid.uuid4())[:8]
    trace_id = trace_id_var.get() or str(uuid.uuid4())[:12]
    set_trace_context(trace_id, span_id)
    
    start_time = time.monotonic()
    
    try:
        log_orchestration_event("agent_call_start", agent_name, f"call:{span_id}", {
            "task_preview": str(task)[:200],
        })
        
        result = await orchestrator.execute_agent(agent_name, task)
        
        elapsed = time.monotonic() - start_time
        log_orchestration_event("agent_call_complete", agent_name, f"call:{span_id}", {
            "duration_ms": round(elapsed * 1000, 2),
            "result_summary": str(result)[:200] if result else None,
        })
        
        return result
        
    except Exception as e:
        elapsed = time.monotonic() - start_time
        log_orchestration_event("agent_call_error", agent_name, f"call:{span_id}", {
            "duration_ms": round(elapsed * 1000, 2),
            "error_type": type(e).__name__,
            "error_message": str(e),
        }, level="error")
        raise
```

**Checkpoint:** Verify end-to-end trace ID flows through all agent boundaries in a single workflow execution. Run a test that generates a single `trace_id` and confirm it appears identically in log entries for every step of the pipeline.

---

## Implementation Patterns / Reference Guide

### Pattern 1: LangGraph StateGraph for Deterministic Multi-Agent Pipeline

Use when you need explicit control over state transitions between agents, with built-in human-in-the-loop support and checkpointing:

```python
from langgraph.graph import StateGraph, END, START
from langgraph.checkpoint.memory import MemorySaver
import uuid


class ResearchPipelineState:
    """State schema for a multi-agent research pipeline.
    
    Demonstrates how typed state flows through a LangGraph with
    explicit edges and conditional routing.
    """
    workflow_id: str
    query: str
    sources: list[dict] = []       # Gathered by web_research_agent
    analysis: dict[str, Any] = {}   # Produced by analysis_agent
    final_report: str = ""          # Written by report_agent
    
    def __init__(self, query: str) -> None:
        self.workflow_id = str(uuid.uuid4())[:12]
        self.query = query


def define_research_graph() -> StateGraph:
    """Build a deterministic multi-agent research pipeline.
    
    Flow: START -> web_research_agent -> analysis_router -> 
          [deep_analysis | surface_summary] -> report_agent -> END
    
    Conditional routing based on analysis depth required.
    """
    graph = StateGraph(ResearchPipelineState)

    # --- Define agent nodes with error handlers ---
    
    def web_research_agent(state: ResearchPipelineState) -> ResearchPipelineState:
        """Scrape and gather initial research sources."""
        logger.info(f"[{state.workflow_id}] web_research started")
        state.sources = [
            {"url": f"research-topic-{i}", "content": f"data-{i}", "relevance": 0.9}
            for i in range(5)
        ]
        return state

    def analysis_router(state: ResearchPipelineState) -> str:
        """Route to deep_analysis or surface_summary based on source count.
        
        This is the branching point — both paths have explicit fallbacks.
        """
        if len(state.sources) >= 3:
            logger.info(f"[{state.workflow_id}] Routing to deep_analysis ({len(state.sources)} sources)")
            return "deep_analysis_agent"
        else:
            logger.warning(f"[{state.workflow_id}] Routing to surface_summary (only {len(state.sources)} sources, insufficient for deep analysis)")
            return "surface_summary_agent"

    def deep_analysis_agent(state: ResearchPipelineState) -> ResearchPipelineState:
        """Deep analysis of multiple sources."""
        state.analysis = {"depth": "deep", "findings_count": len(state.sources) * 2}
        return state

    def surface_summary_agent(state: ResearchPipelineState) -> ResearchPipelineState:
        """Surface-level summary when insufficient data for deep analysis."""
        state.analysis = {"depth": "surface", "findings_count": len(state.sources)}
        return state

    def report_agent(state: ResearchPipelineState) -> ResearchPipelineState:
        """Generate final research report from collected analysis."""
        state.final_report = f"Report for '{state.query}': {len(state.analysis.get('findings_count', 0))} findings analyzed."
        return state

    # --- Build graph with explicit edges ---
    
    # Agent nodes (these are the actual agent implementations)
    graph.add_node("web_research", web_research_agent)
    graph.add_node("deep_analysis", deep_analysis_agent)
    graph.add_node("surface_summary", surface_summary_agent)
    graph.add_node("report", report_agent)

    # Explicit edges — NO implicit ordering
    graph.add_edge(START, "web_research")
    graph.add_edge("web_research", "analysis_router")
    
    # Conditional routing from the router node
    graph.add_conditional_edges(
        "analysis_router",
        analysis_router,
        {
            "deep_analysis_agent": "deep_analysis",
            "surface_summary_agent": "surface_summary",
        },
    )
    
    # Both analysis paths converge to report — explicit fan-in edge
    graph.add_edge("deep_analysis", "report")
    graph.add_edge("surface_summary", "report")
    graph.add_edge("report", END)

    # Checkpointing for recovery after crashes (durable execution)
    checkpointer = MemorySaver()
    return graph.compile(checkpointer=checkpointer)


# Execution with checkpoint persistence:
# app = define_research_graph()
# result = app.invoke({"query": "Impact of AI on software engineering"}, config={"configurable": {"thread_id": "unique-thread-123"}})
```

### Pattern 2: Temporal Workflow for Async Distributed Agent Orchestration

Use when you need durable execution with automatic retry, cancellation, and human-in-the-loop approvals across distributed agents:

```python
from temporalio import workflow, activity
from typing import Any
import datetime

with workflow.unchecked_run():
    pass  # Ensure we're in a workflow context for type checking


# Activity definitions (individual agent tasks)
@activity.defn
async def research_activity(topic: str) -> dict[str, Any]:
    """Agent activity that performs web research on a topic."""
    await asyncio.sleep(0.1)  # Simulate network call
    return {
        "topic": topic,
        "sources_found": 12,
        "summary": f"Research results for {topic}",
        "timestamp": datetime.datetime.utcnow().isoformat(),
    }


@activity.defn
async def analysis_activity(research_results: dict[str, Any]) -> dict[str, Any]:
    """Agent activity that analyzes research findings."""
    await asyncio.sleep(0.1)
    return {
        "analysis_type": "deep",
        "key_findings": 5,
        "confidence": 0.87,
        "recommendations": ["investigate further", "validate source quality"],
    }


@activity.defn
async def report_activity(analysis: dict[str, Any]) -> str:
    """Agent activity that generates final report."""
    await asyncio.sleep(0.1)
    return f"Final report with {analysis['key_findings']} key findings."


# Signal type for human-in-the-loop approval
class ApprovalSignal(workflow.Signal):
    approved: bool = True
    comment: str = ""


@workflow.defn
class ResearchWorkflow:
    """Durable multi-agent research workflow with human-in-the-loop.
    
    Demonstrates Temporal's durable execution model where the entire
    workflow state is persisted to the temporal database, enabling
    recovery from any failure without manual intervention.
    """
    
    def __init__(self) -> None:
        self.human_approved = True
    
    @workflow.signal
    async def approval_signal(self, signal: ApprovalSignal) -> None:
        """Human-in-the-loop approval gate.
        
        Workflow pauses until a human approves or rejects via this signal.
        This is a checkpoint boundary — the workflow state before the signal
        is durable and can be resumed from any point.
        """
        self.human_approved = signal.approved

    @workflow.run
    async def run(self, topic: str) -> dict[str, Any]:
        """Main workflow execution with retry policy and structured recovery."""
        
        # Retry policy for the research activity (exponential backoff built into Temporal)
        from temporalio.common import RetryPolicy
        
        retry_policy = RetryPolicy(
            initial_interval=timedelta(seconds=1),
            maximum_interval=timedelta(seconds=30),
            maximum_attempts=3,
            non_retryable_error_types=["ValueError", "PermissionDenied"],
        )
        
        # Step 1: Research with retries
        research_results = await workflow.execute_activity(
            research_activity,
            args=[topic],
            retry=retry_policy,
            schedule_to_close_timeout=timedelta(minutes=5),
        )
        
        # Human-in-the-loop checkpoint
        if not self.human_approved:
            return {
                "status": "rejected",
                "reason": "Human approval not granted",
                "topic": topic,
            }
        
        # Step 2: Analysis (runs only after research succeeds)
        analysis = await workflow.execute_activity(
            analysis_activity,
            args=[research_results],
            retry=retry_policy,
        )
        
        # Step 3: Report generation
        report = await workflow.execute_activity(
            report_activity,
            args=[analysis],
            retry=retry_policy,
        )
        
        return {
            "status": "completed",
            "topic": topic,
            "report": report,
            "workflow_id": workflow.info().workflow_id,
            "started_at": workflow.info().start_time.isoformat(),
        }
```

### Pattern 3: Circuit Breaker + Retry for Inter-Agent Communication

Production-quality inter-agent communication layer with circuit breakers, exponential backoff, and automatic fallback routing:

```python
from dataclasses import dataclass, field
from typing import Callable, TypeVar, Optional
import time
import random
import asyncio


T = TypeVar("T")


@dataclass
class AgentInvocationResult:
    """Result of calling an agent with full context for observability."""
    agent_name: str
    success: bool
    output: Any
    error: Optional[str] = None
    latency_ms: float = 0.0
    trace_id: str = ""
    fallback_used: bool = False
    
    @property
    def summary(self) -> dict[str, Any]:
        return {
            "agent": self.agent_name,
            "success": self.success,
            "latency_ms": round(self.latency_ms, 2),
            "fallback": self.fallback_used,
            "trace_id": self.trace_id,
        }


class AgentInvocationService:
    """Production-grade agent invocation with circuit breaker and retry.
    
    Implements the reliability patterns from code-philosophy:
    - Law 1 (Early Exit): Guard clauses for unavailable agents
    - Law 4 (Fail Fast): Circuit open = immediate rejection, not delayed failure
    - Law 5 (Intentional Naming): All metrics and logs use consistent naming
    """
    
    def __init__(
        self,
        agent_clients: dict[str, Callable[..., Any]],
        default_max_retries: int = 3,
        circuit_failure_threshold: int = 5,
    ) -> None:
        self.agent_clients = agent_clients
        self.default_max_retries = default_max_retries
        self._circuit_breakers: dict[str, CircuitBreaker] = {
            name: CircuitBreaker(name=name, failure_threshold=circuit_failure_threshold)
            for name in agent_clients
        }

    async def invoke_with_fault_tolerance(
        self,
        agent_name: str,
        task: Any,
        fallback_chain: list[str] | None = None,
    ) -> AgentInvocationResult:
        """Invoke an agent with full fault tolerance: circuit breaker + retry + fallback.
        
        This is the primary entry point for all inter-agent communication.
        Every invocation follows this exact flow:
        1. Check circuit breaker (early exit if open)
        2. Attempt with retries and exponential backoff
        3. If all retries exhausted, try fallback agents
        4. Log complete result with trace ID
        
        Args:
            agent_name: Target agent to invoke.
            task: Task payload for the agent.
            fallback_chain: Ordered list of alternative agents if primary fails.
            
        Returns:
            AgentInvocationResult with full observability data.
        """
        start_time = time.monotonic()
        trace_id = f"inv-{uuid.uuid4().hex[:12]}"
        
        # Set trace context for downstream observability
        set_trace_context(trace_id, f"circuit:{agent_name}")
        
        # Step 1: Check circuit breaker (early exit — Law 4: Fail Fast)
        cb = self._circuit_breakers.get(agent_name)
        if cb and not cb.is_available:
            latency_ms = (time.monotonic() - start_time) * 1000
            log_orchestration_event("circuit_rejected", agent_name, "invoke", {
                "reason": f"Circuit open. Failures: {cb._failure_count}/{cb.failure_threshold}",
            }, level="warning")
            return AgentInvocationResult(
                agent_name=agent_name, success=False, output=None,
                error=f"Circuit open: {agent_name} unavailable",
                latency_ms=latency_ms, trace_id=trace_id,
            )
        
        # Step 2: Attempt primary agent with retry + backoff
        async def _call_primary() -> Any:
            if agent_name not in self.agent_clients:
                raise ValueError(f"Agent '{agent_name}' not registered")
            return await self.agent_clients[agent_name](task)
        
        try:
            output = await retry_with_backoff(
                func=_call_primary,
                max_retries=self.default_max_retries,
                base_delay=1.0,
            )
            
            # Record success with circuit breaker (helps recovery)
            if cb:
                cb.record_success()
            
            latency_ms = (time.monotonic() - start_time) * 1000
            log_orchestration_event("agent_call_complete", agent_name, "invoke", {
                "trace_id": trace_id,
                "success": True,
                "latency_ms": round(latency_ms, 2),
            })
            
            return AgentInvocationResult(
                agent_name=agent_name, success=True, output=output,
                latency_ms=latency_ms, trace_id=trace_id, fallback_used=False,
            )
            
        except CircuitDownstreamError as e:
            # All retries exhausted — try fallback chain
            if fallback_chain:
                result = await self._try_fallbacks(
                    agent_name, task, fallback_chain, start_time, trace_id
                )
                return result
            
            latency_ms = (time.monotonic() - start_time) * 1000
            log_orchestration_event("agent_call_error", agent_name, "invoke", {
                "trace_id": trace_id,
                "error": str(e),
                "retries_exhausted": True,
            }, level="error")
            
            return AgentInvocationResult(
                agent_name=agent_name, success=False, output=None,
                error=str(e), latency_ms=latency_ms, trace_id=trace_id,
                fallback_used=len(fallback_chain) > 0 if fallback_chain else False,
            )

    async def _try_fallbacks(
        self,
        primary: str,
        task: Any,
        fallback_chain: list[str],
        start_time: float,
        trace_id: str,
    ) -> AgentInvocationResult:
        """Try each fallback agent in order until one succeeds."""
        for fallback_agent in fallback_chain:
            log_orchestration_event("fallback_triggered", primary, "invoke", {
                "trace_id": trace_id,
                "attempted_fallback": fallback_agent,
            })
            
            result = await self.invoke_with_fault_tolerance(
                fallback_agent, task, fallback_chain=[]  # No nested fallbacks
            )
            result.fallback_used = True
            
            if result.success:
                log_orchestration_event("fallback_succeeded", primary, "invoke", {
                    "trace_id": trace_id,
                    "used_fallback": fallback_agent,
                })
                return result
        
        # All fallbacks failed
        latency_ms = (time.monotonic() - start_time) * 1000
        log_orchestration_event("all_fallbacks_failed", primary, "invoke", {
            "trace_id": trace_id,
            "fallback_chain": fallback_chain,
        }, level="error")
        
        return AgentInvocationResult(
            agent_name=primary, success=False, output=None,
            error=f"All fallbacks failed: {fallback_chain}",
            latency_ms=latency_ms, trace_id=trace_id, fallback_used=True,
        )
```

---

## Constraints

### MUST DO
- Always design the state schema before writing any agent nodes — shared state is the contract that binds agents together
- Implement checkpointing at natural boundaries for recovery after crashes (end of each major workflow phase)
- Log every routing decision with rationale for auditability — this is how you debug production failures
- Include health check endpoints for all orchestration services (`/health` returning agent status + circuit breaker states)
- Test failure scenarios: network partitions, agent crashes, data corruption, circuit breaker tripping
- Reference `code-philosophy` (5 Laws of Elegant Defense) when designing the control plane — especially Law 1 (Early Exit for unavailable agents) and Law 4 (Fail Fast with circuit breakers)

### MUST NOT DO
- Use implicit ordering between agents — always define explicit edges in the DAG, even if it feels verbose
- Let any single agent hold mutable global state without versioning — use immutable snapshots or versioned state
- Orchestrate more than 7 concurrent fan-out branches without a queue layer — unbounded parallelism causes resource exhaustion
- Bypass circuit breakers "for performance" — they exist for exactly this reason, and their overhead is negligible compared to agent call latency
- Hard-code agent names in routing logic — always use capability-based discovery via the `CapabilityRouter` pattern

---

## Output Template

When designing an orchestration framework, produce:

1. **Pattern Classification** — Which pattern (sequential pipeline, parallel fan-out/fan-in, hierarchical delegation, or dynamic graph) and why it fits the workload characteristics
2. **Framework Selection Rationale** — Why the chosen framework fits: LangGraph for deterministic state graphs, Temporal for durable async workflows, CrewAI/AutoGen for collaborative multi-agent research, Prefect for batch DAG pipelines
3. **State Schema Definition** — Typed schema (Pydantic) with local vs global boundaries documented per field
4. **Fault Tolerance Strategy** — Circuit breaker configuration (thresholds, recovery timeout), retry policies (max attempts, backoff schedule), checkpoint locations in the workflow
5. **Observability Plan** — Tracing strategy (trace ID propagation), metrics to collect (latency per step, success/fail rates, queue depth), and logging format

---

## Related Skills

| Skill | Purpose |
|---|---|
| `task-decomposition-engine` | Break monolithic tasks into subtasks that the orchestrator distributes across agents |
| `parallel-skill-runner` | Execute multiple skills concurrently with result aggregation in fan-out/fan-in patterns |
| `confidence-based-selector` | Score and select the best agent for each subtask dynamically based on capability matching |
| `dependency-graph-builder` | Build and validate DAGs of inter-agent dependencies before workflow execution |

---

## Live References

- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/) — Stateful multi-agent orchestration with explicit graph edges
- [Temporal.io Documentation](https://docs.temporal.io/) — Durable execution and distributed workflow orchestration
- [CrewAI Framework](https://docs.crewai.com/) — Role-based collaborative multi-agent frameworks
- [Microsoft AutoGen](https://microsoft.github.io/autogen/) — Conversable multi-agent conversation framework
- [Prefect Workflow Orchestration](https://docs.prefect.io/) — Modern workflow orchestration with DAG visualization
- [Circuit Breaker Pattern (Martin Fowler)](https://martinfowler.com/bliki/CircuitBreaker.html) — Original pattern definition and implementation guidance
- [Microservices Saga Pattern (InfoQ)](https://www.infoq.com/articles/saga-pattern/) — Multi-step distributed transaction orchestration

---

> This skill references `code-philosophy` (5 Laws of Elegant Defense) for control plane design principles. Always apply Law 1 (Early Exit) when agents are unavailable, and Law 4 (Fail Fast) when implementing circuit breaker logic to prevent cascading failures across the agent mesh.

Attribution

paulpaspaulpas
View sourceMore from paulpas →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Solution Architect

Designs system architecture, component specifications, and technical integration strategy. Use when: designing solutions, system architecture, technology stack, or integration approaches.

192 votes

Akorchak:Venture Assessment

Generate a comprehensive VC investment assessment report for a company

72 votes

Just Fucking Cancel

Find and cancel unwanted subscriptions by analyzing bank transactions. Detects recurring charges, calculates annual waste, and helps you cancel with direct URLs and browser automation. Use when: 'cancel subscriptions', 'audit subscriptions', 'find recurring charges', 'what am I paying for', 'save money', 'subscription cleanup', 'stop wasting money'. Supports CSV import (Apple Card, Chase, Amex, Citi, Bank of America, Capital One, Mint, Copilot) OR Plaid API for automatic transaction pull. Out...

6511 votes

Stock Analysis

Analyze stocks and cryptocurrencies using Yahoo Finance data. Supports portfolio management (create, add, remove assets), crypto analysis (Top 20 by market cap), and periodic performance reports (daily/weekly/monthly/quarterly/yearly). 8 analysis dimensions for stocks, 3 for crypto. Use for stock analysis, portfolio tracking, earnings reactions, or crypto monitoring.

6511 votes

Telegram Compose

Compose rich, readable Telegram messages using HTML formatting via direct Telegram API. Use when: (1) Sending any Telegram message beyond a simple one-line reply, (2) Creating structured messages with sections, lists, or status updates, (3) Need formatting unavailable via Clawdbot's Markdown conversion (underline, spoilers, expandable blockquotes, user mentions by ID), (4) Sending alerts, reports, summaries, or notifications to Telegram, (5) Want professional, scannable message formatting wit...

6511 votes
View all in business →