# Skill: Use the Supervisor Pattern

Use when you want a single LLM to coordinate multiple agents by deciding which one to call
and when — instead of hardcoding the routing logic in graph edges.

## When Supervisor Fits vs Manual Graph

**Use supervisor when:**
- You have 2-3 agents with independent capabilities and simple routing
- The routing logic is "call the right agent based on what needs to be done"
- You are prototyping and want the LLM to figure out orchestration
- Agents do not share complex state (each agent is self-contained)

**Use manual graph when:**
- You have retry loops, review gates, or quality checks (see `skills/add-retry-loop/SKILL.md`)
- Agents write domain-specific state fields and depend on each other's output
- Performance matters — supervisor adds an extra LLM call per step
- You have more than 3 agents (supervisor prompts become complex and unreliable)
- You need predictable, deterministic routing (not LLM-decided)

The example in `examples/researcher_writer/pipeline.py` shows both patterns side-by-side.

## How Supervisor Works

The supervisor is a `create_react_agent` that has the sub-agents as its "tools". It receives
the user's request, decides which sub-agent to call first, reads its output (added to messages),
then decides the next call, and so on until it determines the work is done.

Each sub-agent is created with `create_react_agent` directly (not as a `BaseAgent` subclass).
The supervisor uses a messages-based interface, not `PipelineState`.

## Full Setup

### Install

```bash
pip install langgraph-supervisor
```

### Creating Sub-Agents

Each sub-agent is a compiled `create_react_agent`:

```python
from langgraph.prebuilt import create_react_agent
from arccrew.llm import get_model
from arccrew.tools.agent_tools import get_research_tools
from arccrew.tools.workspace import create_workspace_tools
from pathlib import Path
from arccrew.config import get_settings

model = get_model("default")
workspace = Path(get_settings().workspace_dir).resolve()

researcher = create_react_agent(
    model=model,
    tools=get_research_tools(),
    prompt=(
        "You are a research analyst. Use web_search to find information about the topic. "
        "Return structured findings with: key_findings (list), sources (list of URLs), "
        "and a summary (string)."
    ),
    name="researcher",
)

writer = create_react_agent(
    model=model,
    tools=create_workspace_tools(workspace),
    prompt=(
        "You are a content writer. Based on the conversation so far, write the requested content. "
        "Always save the content to a file using write_file. "
        "Return a summary with: files_created (list), word_count (int), summary (string)."
    ),
    name="writer",
)
```

### Creating the Supervisor

```python
from langgraph_supervisor import create_supervisor

supervisor = create_supervisor(
    agents=[researcher, writer],
    model=model,
    prompt=(
        "You are a project coordinator. You have access to a researcher and a writer.\n\n"
        "researcher: Investigates topics using web search. Call first to gather information.\n"
        "writer: Produces content and saves files. Call after the researcher has returned findings.\n\n"
        "For each user request:\n"
        "1. Call the researcher to investigate the topic.\n"
        "2. Call the writer to produce content based on the research.\n"
        "3. Provide a final summary of what was accomplished."
    ),
)

return supervisor.compile()
```

### The Supervisor Prompt

The supervisor prompt must tell it:
- What each agent does (one line per agent)
- When to call each agent (sequencing rules)
- What to do after all agents are done

Be explicit about sequencing. Without it, the supervisor may call agents in the wrong order
or skip agents.

```python
prompt=(
    "You coordinate a {domain} team. You have:\n"
    "- researcher: searches the web and returns structured findings\n"
    "- analyst: evaluates findings and produces recommendations\n"
    "- writer: creates final reports based on analysis\n\n"
    "Always call them in this order: researcher → analyst → writer.\n"
    "Do not skip steps. After writer completes, provide a brief completion summary."
)
```

## State Handling

The supervisor uses its own internal messages-based state, not `PipelineState`.
You invoke it differently from a manual graph:

```python
# Invoke supervisor (messages interface, not PipelineState)
result = await supervisor_graph.ainvoke({
    "messages": [("user", "Research AI trends in healthcare and write a report.")]
})

# Get the final output
messages = result.get("messages", [])
last_message = messages[-1]
content = last_message.content if hasattr(last_message, "content") else str(last_message)
```

**Accessing sub-agent outputs**: Unlike the manual graph where each agent writes structured
state fields, supervisor sub-agents write to the shared messages list. Their outputs are
embedded in the conversation history, not in dedicated state fields like `context` or `result`.

If you need structured output from a supervisor pipeline, add output parsing after `ainvoke`:

```python
final = await supervisor_graph.ainvoke({"messages": [("user", task)]})
# Parse the last message for structured data
last_content = final["messages"][-1].content
data = self.extract_json(last_content) or {"raw": last_content}
```

## Registering a Supervisor Pipeline with the API

The API expects `PipelineState` shape. To use a supervisor with the API, wrap it:

```python
# my_project/pipeline.py
from datetime import datetime
from arccrew.orchestrator.state import PipelineState

def create_supervisor_pipeline():
    supervisor = create_supervisor_graph()  # returns compiled supervisor

    async def run(state: dict):
        tasks = state.get("tasks", [])
        task_desc = " ".join(t.get("description", "") for t in tasks)

        result = await supervisor.ainvoke({
            "messages": [("user", task_desc)]
        })

        messages = result.get("messages", [])
        last = messages[-1].content if messages else ""
        return {"result": {"output": last}, "results": [{"output": last}]}

    from langgraph.graph import END, START, StateGraph
    graph = StateGraph(PipelineState)
    graph.add_node("supervisor", run)
    graph.add_edge(START, "supervisor")
    graph.add_edge("supervisor", END)
    return graph.compile()
```

## Converting an Existing Manual Graph to Supervisor

When to consider converting: you have a linear graph with no retries and the routing
logic is trivial (always A → B → C).

Steps:
1. For each `BaseAgent` subclass, create a `create_react_agent` with the same tools and a
   simplified inline prompt (supervisor pattern does not use the PromptManager)
2. Write a supervisor prompt that describes each agent and the order to call them
3. Remove the manual graph (StateGraph, add_node, add_edge)
4. Update the API registration wrapper if needed

Note: you lose `@track_timing`, `agent_timings` in state, and per-agent model overrides
when moving to supervisor. The manual graph is generally preferred for production pipelines.
