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

# RUN YOUR AGENT AS A SUBAGENT IN HERMES

> Keep your usual model in Hermes Agent and give it your fine-tuned Oumi agent as a tool

Your agent can work for the model that you already use in [Hermes Agent](https://hermes-agent.nousresearch.com/docs/). You keep your usual model, for example a frontier model, as the main model of your Hermes sessions. A Hermes plugin adds one tool that sends a question to your fine-tuned agent on Oumi. Your agent runs its own tool loop with the system prompt and tools from its training data, and returns its answer to the main model as the tool result.

You do not switch models or profiles when work needs your agent. The main model makes one tool call, and your small fine-tuned model does the multi-step tool calling at a fraction of the cost and latency of a frontier model.

This guide uses the same stock lookup agent as [Run your agent as the main model](https://docs.oumi.ai/guides/agents/run-your-agent/hermes/main-model.md). The agent has two tools: `search_company` finds the ticker symbol for a company name, and `get_stock_price` returns the latest price for a ticker. The main model gets one tool, `ask_stock_agent`.

***

## HOW THE PIECES CONNECT

![The Hermes agent loop sends your prompt to your main model, which calls ask\_stock\_agent. The subagent plugin runs its own loop against your Oumi deployment, runs each stock tool against your data sources, and returns the answer to the Hermes agent loop](https://mintcdn.com/oumi/utu7_qxGqvVUwYVB/images/hermes-subagent-light.svg?fit=max\&auto=format\&n=utu7_qxGqvVUwYVB\&q=85\&s=2093b86c52de1661ebda6f1561d8ace0)

The two models do not see each other's context:

* **The main model** sees the Hermes system prompt, the Hermes tools, and `ask_stock_agent`. It does not see the stock tools or your agent's system prompt.
* **Your fine-tuned model** sees only the requests that the subagent sends: the system prompt from your training data, the stock tools, and the subagent's own history. It does not see the Hermes system prompt, the Hermes tools, or your conversation with the main model.

Your fine-tuned model therefore gets the same context as in training, even though Hermes adds its own guidance, tools, and machine details to the main model's system prompt.

> [!NOTE]
>
> Hermes also has built-in [subagent delegation](https://hermes-agent.nousresearch.com/docs/user-guide/features/delegation) through its `delegate_task` tool, and `delegation.model` can send delegated tasks to another model. This guide does not use it for your agent. A delegated subagent gets the Hermes system prompt and the toolsets of its parent instead of your training context, and `delegation.model` sends every delegated task to your model, not only the tasks that it was trained for.

***

## BEFORE YOU START

You will need:

* An Oumi project.
* Hermes, installed on your machine by following the [Hermes installation](https://hermes-agent.nousresearch.com/docs/getting-started/installation) instructions, with a main model that you already use.
* A personal Oumi API key from **User Settings > API Keys**.
* The system prompt from your agent's training data.

***

**1. Train and deploy a tool-calling model**

Follow the first two steps of [Run your agent as the main model](https://docs.oumi.ai/guides/agents/run-your-agent/hermes/main-model.md): train a tool-calling model, and deploy it. Copy the model path of the deployment, for example `projects/{project_id}/deployments/{deployment_id}`.

Save your Oumi API key in a file, for example `~/.config/oumi/api.key`, and make it readable only by you:

```bash theme={null}
chmod 600 ~/.config/oumi/api.key
```

**2. Create the plugin directory**

A Hermes [plugin](https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins) is a directory in `~/.hermes/plugins/` with a `plugin.yaml` manifest and an `__init__.py` file. Create the directory, and save the system prompt from your training data in it:

```bash theme={null}
mkdir -p ~/.hermes/plugins/stock-agent
cp ./stock-agent-system-prompt.md ~/.hermes/plugins/stock-agent/system-prompt.md
```

If you use Hermes with a [profile](https://hermes-agent.nousresearch.com/docs/user-guide/profiles), create the directory in that profile's `plugins/` directory instead, for example `~/.hermes/profiles/work/plugins/stock-agent/`.

Create `~/.hermes/plugins/stock-agent/plugin.yaml`:

```yaml plugin.yaml theme={null}
name: stock-agent
version: "1.0"
description: Gives the main model the stock lookup agent on Oumi as a tool
```

When you finish, the directory contains five files:

| File | Contents |
| - | - |
| `plugin.yaml` | The plugin manifest |
| `system-prompt.md` | The system prompt from your training data |
| `tools.py` | The definitions of the stock tools, and the code that runs them |
| `agent.py` | The subagent's tool loop against your Oumi deployment |
| `__init__.py` | The registration that gives the main model the `ask_stock_agent` tool |

**3. Define and implement the tools**

Hermes does not run your agent's tools in this setup. The subagent sends the tool definitions with each request to your deployment, and runs each tool call itself.

Write each definition in the OpenAI tools format from its Oumi tool. Copy the Oumi `toolName` into `name`, and copy `description` and `parameters` without changes. The model learned your tools from these definitions.

Create `~/.hermes/plugins/stock-agent/tools.py`:

```python tools.py theme={null}
import json
import urllib.error
import urllib.parse
import urllib.request

API = "https://api.example-stocks.com"

# Copies of the Oumi tool definitions. A name, parameter, or description that
# differs from training makes the model call tools that do not exist.
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search_company",
            "description": "Find a company's stock ticker symbol by company name. Call this before get_stock_price.",
            "parameters": {
                "type": "object",
                "properties": {"name": {"type": "string", "description": "Company name, e.g. 'Apple'."}},
                "required": ["name"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "Get the latest stock price for a ticker symbol returned by search_company.",
            "parameters": {
                "type": "object",
                "properties": {"ticker": {"type": "string", "description": "Stock ticker symbol, e.g. 'AAPL'."}},
                "required": ["ticker"],
            },
        },
    },
]


def run_tool(name: str, args: dict) -> str:
    if name == "search_company":
        return search_company(args["name"])
    if name == "get_stock_price":
        return get_stock_price(args["ticker"])
    raise ValueError(f"Unknown tool: {name}")


def search_company(name: str) -> str:
    found = get_json(f"{API}/search?q={urllib.parse.quote(name)}")
    if found is None:
        return f'No company found matching "{name}".'
    return f"Found {found['name']} (ticker: {found['ticker']})."


def get_stock_price(ticker: str) -> str:
    quote = get_json(f"{API}/quote/{urllib.parse.quote(ticker)}")
    if quote is None:
        return f'No price found for "{ticker}".'
    return f"{ticker}: {quote['price']} {quote['currency']}."


# A 404 means "no match" and returns None. Any other failure raises.
def get_json(url: str) -> dict | None:
    try:
        with urllib.request.urlopen(url, timeout=30) as res:
            return json.load(res)
    except urllib.error.HTTPError as error:
        if error.code == 404:
            return None
        raise
```

A tool that finds no match returns a result that says so. A tool whose data source fails raises an error, and the subagent sends that error to the model as the tool result. Neither path invents a value.

**4. Write the subagent's tool loop**

The subagent does for your fine-tuned model what Hermes does for a main model. It sends the conversation and the tool definitions to your deployment, runs each tool call in the reply, adds the results to the conversation, and repeats until the model answers without a tool call.

Create `~/.hermes/plugins/stock-agent/agent.py`, and set `MODEL` to the model path of your deployment:

```python agent.py theme={null}
import json
import urllib.error
import urllib.request
from pathlib import Path

from .tools import TOOLS, run_tool

ENDPOINT = "https://inference.oumi.ai/v1/chat/completions"
MODEL = "projects/{project_id}/deployments/{deployment_id}"

# Stops a model that keeps calling tools. Set it above the number of tool
# calls that your longest training conversation makes.
MAX_TOOL_STEPS = 8

SYSTEM_PROMPT = (Path(__file__).parent / "system-prompt.md").read_text()


class StockAgent:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.history = [{"role": "system", "content": SYSTEM_PROMPT}]

    # A turn that fails part way leaves tool calls without results, and your
    # deployment rejects a conversation like that. Removing the failed turn
    # keeps the next question working.
    def ask(self, question: str) -> str:
        start = len(self.history)
        try:
            return self._turn(question)
        except Exception:
            del self.history[start:]
            raise

    def _turn(self, question: str) -> str:
        self.history.append({"role": "user", "content": question})

        for _ in range(MAX_TOOL_STEPS):
            reply = self._complete()
            calls = reply.get("tool_calls") or []

            if not calls:
                self.history.append({"role": "assistant", "content": reply["content"]})
                return reply["content"]

            self.history.append({"role": "assistant", "content": reply.get("content") or "", "tool_calls": calls})
            for call in calls:
                self.history.append({"role": "tool", "tool_call_id": call["id"], "content": run_tool_call(call)})

        raise RuntimeError(f"The stock agent made {MAX_TOOL_STEPS} rounds of tool calls without an answer.")

    def _complete(self) -> dict:
        request = urllib.request.Request(
            ENDPOINT,
            data=json.dumps({"model": MODEL, "messages": self.history, "tools": TOOLS}).encode(),
            headers={"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"},
        )
        try:
            with urllib.request.urlopen(request, timeout=120) as res:
                return json.load(res)["choices"][0]["message"]
        except urllib.error.HTTPError as error:
            raise RuntimeError(f"Oumi returned HTTP {error.code}: {error.read().decode()}") from error


# A failed tool call goes back to the model as its result, so the model can
# tell the user instead of inventing a value.
def run_tool_call(call: dict) -> str:
    try:
        return run_tool(call["function"]["name"], json.loads(call["function"]["arguments"]))
    except Exception as error:
        return f"Error: {error}"
```

**5. Give the main model the subagent as a tool**

The main model decides when to call `ask_stock_agent` from the tool's description alone. Say what your agent can answer and what it remembers, so that the main model calls it for the right questions.

Create `~/.hermes/plugins/stock-agent/__init__.py`:

```python __init__.py theme={null}
from pathlib import Path

from .agent import StockAgent

API_KEY_FILE = Path.home() / ".config/oumi/api.key"

SCHEMA = {
    "name": "ask_stock_agent",
    "description": (
        "Ask the stock agent for the latest stock price of one or more companies. It finds the "
        "ticker symbols itself and answers in prose. It remembers earlier questions in this "
        "session, so a follow-up can refer to a company it already found. Set reset to true to "
        "start a new conversation with it."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "question": {"type": "string", "description": "The question, in plain English."},
            "reset": {"type": "boolean", "description": "Forget earlier questions before this one."},
        },
        "required": ["question"],
    },
}

# One subagent per Hermes session. It keeps its own history, so a follow-up
# question can refer to a company that it already found.
agents: dict[str | None, StockAgent] = {}


def ask_stock_agent(args: dict, session_id: str | None = None, **kwargs) -> str:
    if session_id not in agents or args.get("reset"):
        agents[session_id] = StockAgent(API_KEY_FILE.read_text().strip())
    return agents[session_id].ask(args["question"])


def register(ctx):
    ctx.register_tool(name="ask_stock_agent", toolset="stock_agent", schema=SCHEMA, handler=ask_stock_agent)
```

Hermes passes the ID of the current session to the handler as `session_id`, so each Hermes session gets its own subagent. The subagent reads your API key when the main model first calls `ask_stock_agent` in a session, so a session that never asks your agent does not need the key.

**6. Enable the plugin and ask a question**

Plugins are off until you enable them. Enable the plugin, then start a new Hermes session with your usual model:

```bash theme={null}
hermes plugins enable stock-agent
hermes
```

Hermes adds the tools of a newly enabled plugin from the next session. Ask a question such as "Is Nucor trading above 150 today?".

The main model calls `ask_stock_agent`. Your agent calls `search_company` to find the ticker `NUE`, then `get_stock_price` with that ticker, and returns its answer. The main model then answers you from your agent's reply.

Ask a follow-up such as "And how about Apple?". The main model calls `ask_stock_agent` again, and your agent answers with the conversation from the first question still in its history.

***

## TROUBLESHOOTING

* **`ask_stock_agent` fails with `429` and "Deployment is warming up":** Your deployment uses [scale-to-zero](https://docs.oumi.ai/guides/deployment/deploy-a-model.md#autoscaling-and-cost-custom-models) and has no warm replica. The subagent does not wait for a cold start. Wait a few minutes and ask again, or set min replicas to 1.
* **`ask_stock_agent` fails with `400` and "Invalid model format":** `MODEL` in `agent.py` is not a model path. Set it to `projects/{project_id}/deployments/{deployment_id}`.
* **`ask_stock_agent` fails with `FileNotFoundError` for `api.key`:** The subagent cannot read your Oumi API key. Save the key in `~/.config/oumi/api.key`.
* **`ask_stock_agent` does not appear in Hermes:** The plugin is not enabled, or Hermes did not load it. Run `hermes plugins enable stock-agent`, then start a new session. To see why Hermes skips a plugin, run `HERMES_PLUGINS_DEBUG=1 hermes plugins list`.
* **The main model answers stock questions without calling `ask_stock_agent`:** The main model does not know when to call your agent. Make the tool's description more specific about the questions that your agent answers, then start a new session.
* **Your agent answers from an earlier, unrelated conversation:** The subagent keeps its history for the whole Hermes session. Ask the main model to reset the stock agent, so that it calls `ask_stock_agent` with `reset` set to `true`.

***

## WHAT'S NEXT

> **[Run your agent as the main model](https://docs.oumi.ai/guides/agents/run-your-agent/hermes/main-model.md)**
>
> Use your fine-tuned model as the model of a Hermes profile.

> **[Inference logs](https://docs.oumi.ai/guides/deployment/inference-logs.md)**
>
> Review the requests that the subagent sent to your deployment.

> **[Compounding intelligence](https://docs.oumi.ai/guides/deployment/compounding-intelligence.md)**
>
> Find where your deployed model struggles, and retrain it on targeted data.

> **[Agent harnesses](https://docs.oumi.ai/reference/agent-harnesses.md)**
>
> Learn what an agent harness contains, including subagents.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.