> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seekr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Poetry contest with a supervisor agent

> Build a supervisor agent that wraps a limerick writer and a haiku writer as tools, sends a prompt to both, and picks a winner.

export const DownloadNotebook = ({href, filename}) => <div className="not-prose" style={{
  marginTop: "4px",
  marginBottom: "16px"
}}>
    <a href={href} download={filename} className="inline-flex items-center gap-2 border border-[#00dad3] bg-[#00dad3]/10 text-[#007774] dark:text-[#00dad3] no-underline" style={{
  padding: "6px 14px",
  borderRadius: "8px",
  fontSize: "14px",
  fontWeight: "600"
}}>
      <Icon icon="download" size={16} />
      Download Jupyter notebook
    </a>
  </div>;

export const RecipeMeta = ({tags = [], level, time, version}) => {
  const pill = {
    display: "inline-block",
    padding: "2px 10px",
    borderRadius: "6px",
    fontSize: "12px",
    fontWeight: "600",
    marginRight: "6px",
    marginBottom: "6px"
  };
  const hasMeta = level || time || version;
  return <div className="not-prose" style={{
    marginTop: "4px",
    marginBottom: "12px"
  }}>
      {tags.length > 0 && <div style={{
    marginBottom: hasMeta ? "8px" : "0"
  }}>
          {tags.map(t => <span key={t} className="border border-[#00dad3] bg-[#00dad3]/10 text-[#007774] dark:text-[#00dad3]" style={pill}>
              {t}
            </span>)}
        </div>}
      {hasMeta && <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-black/60 dark:text-white/60">
          {level && <span className="inline-flex items-center gap-1.5">
              <Icon icon="gauge" size={14} />
              {level}
            </span>}
          {time && <span className="inline-flex items-center gap-1.5">
              <Icon icon="clock" size={14} />
              {time}
            </span>}
          {version && <span className="inline-flex items-center gap-1.5">
              <Icon icon="cube" size={14} />
              Verified with seekrai {version}
            </span>}
        </div>}
    </div>;
};

<RecipeMeta tags={["Agents", "Agents as tools", "Multi-agent"]} level="Intermediate" time="~20 min" version="0.29.0" />

<DownloadNotebook href="/notebooks/poetry-contest-with-supervisor-agent.json" filename="poetry-contest-with-supervisor-agent.ipynb" />

SeekrFlow lets you expose one agent to another as a tool. This recipe uses that
pattern to build a small multi-agent system: a supervisor agent coordinates two
specialist sub-agents, a limerick writer and a haiku writer. The supervisor
sends the user's prompt to each sub-agent, collects both poems, and declares a
winner.

The pattern is the useful part. Once a sub-agent is a tool, the supervisor
decides when to call it the same way it would call any other tool, so you can
compose focused agents into a larger workflow instead of building one agent that
tries to do everything.

## What you'll build

A supervisor agent that:

1. Wraps a Limerick Agent and a Haiku Agent as `agent_as_tool` tools.
2. Sends the user's prompt to both sub-agents.
3. Presents both poems, clearly labeled.
4. Picks a favorite and explains why.

## Prerequisites

* A SeekrFlow API key, set as the `SEEKR_API_KEY` environment variable
* Python 3.8 or later
* The SeekrFlow SDK: `pip install seekrai`

<Warning>
  This recipe creates billable resources. When you are done, remove them with the
  [cleanup step](#clean-up).
</Warning>

## Build it

<Steps>
  <Step title="Set up the client">
    Create `agents_as_tools.py` and start with the imports, configuration, and
    client.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      import os
      import time

      from seekrai import SeekrFlow
      from seekrai.types.tools import CreateAgentAsTool, AgentAsToolConfig
      from seekrai.types.agents.agent import CreateAgentRequest

      API_KEY = os.environ["SEEKR_API_KEY"]
      API_URL = "https://flow.seekr.com/v1/"
      MODEL_ID = "meta-llama/Llama-3.1-70B-Instruct"
      TEST_PROMPT = "My cat refuses to get off my keyboard while I'm trying to work."

      client = SeekrFlow(api_key=API_KEY, base_url=API_URL)
      ```
    </CodeGroup>

    <Info>
      This recipe uses `meta-llama/Llama-3.1-70B-Instruct`. The supervisor has to call
      both sub-agent tools and then reason over their output, so a capable instruct
      model with reliable tool calling matters more here than in a single-tool agent.
      Any instruct model that supports tool calling works, such as
      `meta-llama/Llama-3.3-70B-Instruct`.
    </Info>
  </Step>

  <Step title="Add helper functions">
    These helpers wait for asynchronous operations and make the script safe to run
    more than once. `run_agent` polls a run to completion, `promote_and_wait` polls
    an agent until it is `Active`, and the `get_or_create` helpers reuse existing
    resources instead of creating duplicates.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      def run_agent(agent_id: str, prompt: str, timeout: int = 300) -> str:
          """Start a run, poll until complete, then return the assistant's reply."""
          thread = client.agents.threads.create()
          client.agents.threads.create_message(thread_id=thread.id, role="user", content=prompt)
          run_response = client.agents.runs.run(agent_id=agent_id, thread_id=thread.id, stream=False)

          deadline = time.time() + timeout
          while time.time() < deadline:
              run = client.agents.runs.retrieve(run_response.run_id, thread.id)
              if run.status.value == "completed":
                  break
              if run.status.value in ("failed", "canceled"):
                  raise RuntimeError(f"Run ended with status: {run.status.value}")
              time.sleep(3)
          else:
              raise TimeoutError(f"Run did not complete within {timeout}s.")

          messages = client.agents.threads.list_messages(thread.id, limit=10, order="desc")
          for msg in messages:
              if msg.role == "assistant":
                  return msg.content if isinstance(msg.content, str) else str(msg.content)
          return "(no assistant message found)"


      def promote_and_wait(agent_id: str, timeout: int = 120) -> None:
          """Promote an agent and poll until it's Active."""
          client.agents.promote(agent_id)
          deadline = time.time() + timeout
          while time.time() < deadline:
              agent = client.agents.retrieve(agent_id)
              if agent.status.value == "Active":
                  return
              if agent.status.value == "Failed":
                  raise RuntimeError(f"Agent {agent_id} failed to deploy.")
              time.sleep(3)
          raise TimeoutError(f"Agent did not become Active within {timeout}s.")


      def get_or_create_agent(request: CreateAgentRequest):
          """Return an existing agent with the same name, or create a new one."""
          existing = next((a for a in client.agents.list_agents() if a.name == request.name), None)
          if existing:
              print(f"  Found existing '{existing.name}': {existing.id!r}")
              return existing
          agent = client.agents.create(request)
          print(f"  Created '{agent.name}': {agent.id!r}")
          return agent


      def get_or_create_tool(request: CreateAgentAsTool):
          """Return an existing agent_as_tool with the same name, or create a new one."""
          existing = next((t for t in client.tools.list().data if t.name == request.name), None)
          if existing:
              print(f"  Found existing tool '{existing.name}': {existing.id!r}")
              return existing
          tool = client.tools.create(request)
          print(f"  Created tool '{tool.name}': {tool.id!r}")
          return tool


      def ensure_active(agent) -> None:
          """Promote the agent only if it isn't already Active."""
          if agent.status.value == "Active":
              print(f"  '{agent.name}' is already Active.")
              return
          print(f"  Promoting '{agent.name}'...")
          promote_and_wait(agent.id)
          print(f"  '{agent.name}' is Active.")
      ```
    </CodeGroup>
  </Step>

  <Step title="Create the sub-agents">
    Create the two specialist agents. Each has narrow instructions that keep it
    focused on a single job.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      limerick_agent = get_or_create_agent(CreateAgentRequest(
          name="Limerick Agent",
          instructions=(
              "You are the Limerick Agent. Whatever prompt the user sends you, "
              "respond ONLY with a creative limerick about it. "
              "Do not add any other commentary."
          ),
          model_id=MODEL_ID,
      ))

      haiku_agent = get_or_create_agent(CreateAgentRequest(
          name="Haiku Agent",
          instructions=(
              "You are the Haiku Agent. Whatever prompt the user sends you, "
              "respond ONLY with a haiku (5-7-5 syllables) about it. "
              "Do not add any other commentary."
          ),
          model_id=MODEL_ID,
      ))
      ```
    </CodeGroup>
  </Step>

  <Step title="Promote the sub-agents">
    A sub-agent must be `Active` before it can be wrapped as a tool, so promote both
    now.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      ensure_active(limerick_agent)
      ensure_active(haiku_agent)
      ```
    </CodeGroup>
  </Step>

  <Step title="Wrap each sub-agent as a tool">
    Create an `agent_as_tool` tool for each sub-agent. The `description` is what the
    supervisor sees when it decides which tool to call, and the `config` points at
    the sub-agent by ID.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      limerick_tool = get_or_create_tool(CreateAgentAsTool(
          name="limerick_agent",
          description="Sends a prompt to the Limerick Agent and returns a limerick.",
          config=AgentAsToolConfig(agent_id=limerick_agent.id),
      ))

      haiku_tool = get_or_create_tool(CreateAgentAsTool(
          name="haiku_agent",
          description="Sends a prompt to the Haiku Agent and returns a haiku.",
          config=AgentAsToolConfig(agent_id=haiku_agent.id),
      ))
      ```
    </CodeGroup>
  </Step>

  <Step title="Create the supervisor">
    Create the supervisor agent and attach both tools. Its instructions tell it to
    call each tool and then compare the results.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      supervisor = get_or_create_agent(CreateAgentRequest(
          name="Supervisor Agent",
          instructions=(
              "You are a supervisor. When the user gives you a prompt:\n"
              "1. Send it verbatim to the 'limerick_agent' tool and note its output.\n"
              "2. Send it verbatim to the 'haiku_agent' tool and note its output.\n"
              "3. Present BOTH outputs to the user, clearly labelled.\n"
              "4. Declare which one you personally like more, and briefly explain why."
          ),
          tool_ids=[limerick_tool.id, haiku_tool.id],
          model_id=MODEL_ID,
      ))

      ensure_active(supervisor)
      ```
    </CodeGroup>
  </Step>

  <Step title="Send a prompt">
    Send a prompt to the supervisor and print its reply. The supervisor calls both
    sub-agents, then returns both poems with its verdict.

    <CodeGroup>
      ```python agents_as_tools.py theme={null}
      response = run_agent(supervisor.id, TEST_PROMPT)
      print("Supervisor says:\n")
      print(response)
      ```
    </CodeGroup>
  </Step>

  <Step title="Run the script">
    Run the finished script:

    ```bash theme={null}
    python agents_as_tools.py
    ```

    You should see a limerick and a haiku about the prompt, followed by the
    supervisor's pick.
  </Step>
</Steps>

<h2 id="clean-up">
  Clean up resources (optional)
</h2>

Promoted agents and their tools stay in your account until you remove them. When
you are done, add this helper and call it to tear down everything the recipe
created.

<CodeGroup>
  ```python agents_as_tools.py theme={null}
  def cleanup(*agent_ids, tool_ids=()):
      for agent_id in agent_ids:
          try:
              client.agents.demote(agent_id)
              client.agents.delete(agent_id)
              print(f"  Deleted agent {agent_id!r}")
          except Exception as e:
              print(f"  Warning: could not delete agent {agent_id!r}: {e}")
      for tool_id in tool_ids:
          try:
              client.tools.delete(tool_id)
              print(f"  Deleted tool {tool_id!r}")
          except Exception as e:
              print(f"  Warning: could not delete tool {tool_id!r}: {e}")


  cleanup(
      supervisor.id, limerick_agent.id, haiku_agent.id,
      tool_ids=(limerick_tool.id, haiku_tool.id),
  )
  ```
</CodeGroup>

## Next steps

* **Add more specialists.** Wrap additional sub-agents (a sonnet writer, a
  translator) as tools and attach them to the supervisor.
* **Route instead of fan out.** Change the supervisor's instructions so it picks
  the single best sub-agent for a prompt rather than calling all of them.
* **Nest deeper.** A sub-agent can have tools of its own, including other agents,
  so you can build multi-level workflows.
