How to design, deploy, and orchestrate agents and specialized sub-agents on top of self-hosted language models — written so both newcomers and engineers can follow along.
Before touching any infrastructure, it helps to have the mental model right. Use the toggle below to switch between a plain-language explanation and the technical one throughout this guide.
Think of an agent as a digital employee: you give it a goal, and instead of just answering once, it plans, takes actions (like searching, writing, or calling other tools), checks its own results, and keeps going until the goal is done.
A sub-agent is a specialist you hire under that employee — one who's only good at one thing, like "research" or "writing code." The main agent acts like a manager: it breaks a big job into smaller ones and hands each piece to the right specialist.
An agent is an LLM wrapped in a control loop: it receives a task, generates a plan or next action, invokes tools via function calling, observes the result, and repeats until a stop condition is met (goal satisfied, max steps, or human interrupt).
A sub-agent is a narrower instance of that same loop — often a smaller or fine-tuned model, a restricted tool set, and a focused system prompt — invoked by a parent orchestrator agent that handles task decomposition, routing, and result aggregation.
↑ Switch to Technical mode above to see this same explanation in engineering terms.
↑ Switch to Plain-language mode above to see this same explanation without jargon.
Architecture is simply how the manager and specialists are organized — who talks to whom, and how work flows between them.
Agent architecture is the org chart. In the simplest setup, one manager (the orchestrator) hands tasks directly to specialists and collects their answers — like a small team reporting to one boss. In bigger systems, managers can supervise other managers, specialists can pass work straight to each other, or everyone can share a common notice board instead of talking one-on-one.
Agent architecture defines the topology of control and data flow between the orchestrator and its sub-agents. Common topologies include hierarchical (single orchestrator, flat sub-agents), sequential / pipeline (fixed hand-off order), blackboard (shared mutable state instead of direct messages), and peer-to-peer (decentralized negotiation, no fixed leader). The topology you choose determines coupling, latency, and how failures propagate through the system.
↑ Switch to Technical mode above to see this in engineering terms.
↑ Switch to Plain-language mode above to see this without jargon.
The four concrete patterns — hierarchical, pipeline, blackboard, and peer-to-peer — are diagrammed in detail under Architecture Patterns below.
Self-hosting trades convenience for control. That trade is worth it when privacy, cost at scale, or customization matter more than getting started quickly.
| Self-hosted | Hosted API | |
|---|---|---|
| Data privacy | Data never leaves your infrastructure | Data sent to a third party |
| Cost at scale | Fixed hardware cost, cheaper per-token at volume | Pay per token, scales linearly |
| Latency | Depends on your hardware & network | Usually fast, provider-managed |
| Customization | Full control — fine-tune, quantize, swap models | Limited to provider's options |
| Operational burden | You manage uptime, scaling, GPUs | Provider handles it |
| Setup time | Higher — infra, drivers, serving stack | Minutes |
Every agent hierarchy, however complex, is assembled from the same handful of parts.
The "manager" agent. Decides what needs doing and who should do it.
Specialists with a narrow job, a focused prompt, and limited tools.
Functions an agent can call — search the web, run code, query a database.
What the system remembers: the current conversation, and anything stored long-term.
The structured format agents use to pass tasks and results between each other.
Checks, timeouts, and human approval points that keep the system safe and on track.
Choose a pattern based on how much the sub-tasks depend on each other, and how much autonomy each specialist needs.
Hierarchical (most common). One orchestrator decomposes the task and delegates to specialists, then merges their results. Best default for most projects.
Sequential pipeline. Fixed order of agents, each one's output feeds the next. Predictable and easy to debug, but rigid.
Blackboard. Agents read and write to shared state instead of talking directly. Good when contributions don't need a strict order.
Peer-to-peer / swarm. No fixed leader — agents negotiate directly. Powerful but harder to control; use only when you need emergent behavior.
Ten steps from a bare model to a monitored, production system.
Pick a serving stack that matches your hardware and latency needs.
You can hand-roll a control loop, but a framework saves time on routing, state, and retries.
This agent's only job is to decide who does what — it shouldn't do the work itself.
Narrow scope is what makes sub-agents reliable — resist the urge to make them general-purpose.
Tools are how agents act on the world beyond generating text.
Agents need a shared, structured "language" to pass work between each other.
Distinguish between what an agent needs right now and what the system should remember long-term.
Autonomy without oversight is where agent systems get expensive or unsafe.
Treat agent behavior like any other system under test.
Move from a single script to a resilient service.
A working example in Python using an OpenAI-compatible client against a self-hosted model server — this API shape is served by Ollama, vLLM, and TGI alike, so the same code runs against any of them.
orchestrator.py on your machine. Then, in a terminal opened in that same folder, run pip install openai once, followed by python orchestrator.py each time you want to execute it. Make sure your self-hosted model is already running first — e.g. ollama run llama3.1:8b in a separate terminal tab — before you launch the script.
"""
Minimal orchestrator + sub-agent example against a self-hosted,
OpenAI-compatible endpoint (Ollama, vLLM, or TGI).
Install:
pip install openai
Run a local model first, e.g.:
ollama run llama3.1:8b
"""
from openai import OpenAI
import json
# 1. Connect — point the client at your self-hosted server
client = OpenAI(
base_url="http://localhost:11434/v1", # Ollama's OpenAI-compatible endpoint
api_key="not-needed", # self-hosted servers usually ignore this
)
MODEL = "llama3.1:8b"
# 2. Define sub-agents — small, focused functions with their own prompts
def research_agent(query: str) -> str:
"""Sub-agent specialised in summarising information for a query."""
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": (
"You are a research specialist. Given a topic, return a "
"concise, factual summary in 3 bullet points. Do not answer "
"anything outside the research task."
)},
{"role": "user", "content": query},
],
temperature=0.2,
)
return response.choices[0].message.content
def coding_agent(spec: str) -> str:
"""Sub-agent specialised in writing small code snippets."""
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": (
"You are a coding specialist. Given a specification, return "
"only working Python code with no explanation."
)},
{"role": "user", "content": spec},
],
temperature=0.1,
)
return response.choices[0].message.content
# 3. Build a tool directory — how the orchestrator "sees" its sub-agents
SUBAGENT_TOOLS = [
{
"type": "function",
"function": {
"name": "research_agent",
"description": "Use for gathering or summarising factual information.",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "coding_agent",
"description": "Use for writing or fixing small code snippets.",
"parameters": {
"type": "object",
"properties": {"spec": {"type": "string"}},
"required": ["spec"],
},
},
},
]
DISPATCH = {
"research_agent": research_agent,
"coding_agent": coding_agent,
}
# 4. Orchestrator loop — delegate, collect results, repeat until done
def orchestrator(task: str) -> str:
messages = [
{"role": "system", "content": (
"You are the orchestrator. You do not answer tasks yourself — "
"you decide which specialist tool to call for each part of the "
"task, then combine their outputs into a final answer."
)},
{"role": "user", "content": task},
]
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=SUBAGENT_TOOLS,
tool_choice="auto",
)
msg = response.choices[0].message
while msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
fn = DISPATCH[call.function.name]
args = json.loads(call.function.arguments)
result = fn(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=SUBAGENT_TOOLS,
tool_choice="auto",
)
msg = response.choices[0].message
return msg.content
# 5. Call it
if __name__ == "__main__":
result = orchestrator(
"Summarise what a vector database is, then write a Python "
"function that computes cosine similarity between two vectors."
)
print(result)
base_url to http://localhost:8000/v1; for TGI, use its OpenAI-compatible route. The tool-calling loop itself doesn't change — only the endpoint and model name do.
You send the orchestrator one task. Behind the scenes it quietly asks the research specialist for a summary and the coding specialist for a snippet, then hands you back one merged answer — you never see the two specialists as separate replies, only the final combined result.
The single orchestrator() call triggers two tool-call round trips (one per sub-agent), appends each tool message to the conversation, then makes a final completion request with no tool_calls in the response — at which point the while loop exits and msg.content holds the merged text.
Running python orchestrator.py against a local Llama 3.1 model produces output similar to this:
$ python orchestrator.py
A vector database stores data as high-dimensional vectors and supports
similarity search rather than exact matching.
- It indexes embeddings so you can retrieve the items "nearest" to a query.
- Common uses include semantic search, recommendation systems, and RAG.
- Popular options include Qdrant, Weaviate, and Chroma.
def cosine_similarity(a, b):
dot = sum(x * y for x, y in zip(a, b))
norm_a = sum(x ** 2 for x in a) ** 0.5
norm_b = sum(y ** 2 for y in b) ** 0.5
return dot / (norm_a * norm_b)
There's no single correct stack — this is a sensible starting point to adapt.
| Layer | Options | Notes |
|---|---|---|
| Model serving | vLLM, Ollama, TGI, llama.cpp | vLLM for throughput; Ollama for simplicity |
| Orchestration | LangGraph, CrewAI, AutoGen | Match to your architecture pattern |
| Vector store | Qdrant, Weaviate, Chroma | For long-term / RAG memory |
| Observability | Langfuse, OpenTelemetry | Trace every agent step and cost |
| Task queue | Redis, RabbitMQ, Celery | Decouples request intake from GPU load |
| Containers | Docker, Kubernetes | Independent scaling per component |
Hermes Agent, the self-improving open-source agent from Nous Research, ships most of the hierarchy in this guide as a built-in feature rather than something you build from scratch. Here's how the two map onto each other.
Hermes Agent is a ready-made "employee" you install rather than code yourself. Instead of writing the orchestrator loop from our reference script, you just talk to Hermes and it decides on its own when to hand a piece of work to a specialist — and it keeps a growing notebook of tricks it's learned, so it gets better at repeat tasks over time.
Hermes Agent (MIT licensed, Nous Research, released Feb 2026) implements the orchestrator / sub-agent loop natively via a delegate_task tool, with configurable depth, concurrency, tool restrictions, and per-child model overrides — plus a persistent skills-and-memory layer this guide's generic pattern doesn't cover at all.
↑ Switch to Technical mode above to see this in engineering terms.
↑ Switch to Plain-language mode above to see this without jargon.
| This guide | Hermes Agent equivalent |
|---|---|
| Orchestrator agent | the main session calling delegate_task |
| Sub-agent | child agent, role="leaf" (default) |
| Hierarchical pattern | flat delegation — max_spawn_depth: 1 (default) |
| Nested orchestration | role="orchestrator" + max_spawn_depth: 2+ |
| Narrow sub-agent tool access | blocked by default: delegate_task, clarify, memory, execute_code |
| Cheaper model for sub-agents | delegation.model override in config.yaml |
| Step-limit guardrail | max_iterations (default 50), child_timeout_seconds |
| Monitoring / tracing | /agents overlay — live tree, cost per branch |
| Self-hosted model connection | model.base_url custom endpoint (Ollama, vLLM, etc.) |
Same idea as the reference script earlier — Hermes talks to any OpenAI-compatible endpoint. Set it in ~/.hermes/config.yaml:
# ~/.hermes/config.yaml
model:
default: qwen2.5-coder:32b
provider: custom
base_url: http://localhost:11434/v1 # Ollama; use :8000/v1 for vLLM
api_key: "" # empty for local endpoints
# Optional: route sub-agents to a cheaper/faster model
delegation:
model: "qwen2.5-coder:7b"
max_iterations: 50 # per-child step limit
max_concurrent_children: 3
max_spawn_depth: 1 # raise to 2+ for nested orchestration
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash, run hermes model and choose "Custom endpoint (self-hosted / VLLM / etc.)", then just talk to it — Hermes delegates to sub-agents automatically when a task is complex enough; you don't need to ask it to.
A sub-agent with a broad mandate behaves unpredictably. Narrower is more reliable, even if it means more sub-agents.
Without a hard cap on delegation depth or loop count, a confused orchestrator can spin indefinitely — burning GPU time and money.
Passing results as prose between agents invites misparsing. Structured, schema-validated messages catch errors early.
Agent behavior drifts as prompts, models, and tools change. Without golden tasks, regressions go unnoticed until a user hits them.
Running every specialist on your biggest model wastes GPU budget. Match model size to task difficulty.
Sending emails, spending money, or deleting data should pause for approval until you trust the system's track record.
No. A single consumer GPU (or even CPU with llama.cpp) is enough to prototype a hierarchy with one orchestrator and a couple of sub-agents using a quantized 7B–8B model. Scale hardware once you've validated the design.
Yes — many teams route routine, high-volume sub-agent tasks to a self-hosted model and reserve a hosted frontier model for the orchestrator or the hardest sub-tasks. The communication protocol between agents doesn't care where each model runs.
There's no fixed number, but complexity grows fast. Start with 2–4 sub-agents covering distinct responsibilities; add more only when you can point to a specific task the current set handles poorly.
No, and often they shouldn't. A smaller, cheaper, possibly fine-tuned model is usually sufficient for a narrow sub-agent task, while the orchestrator benefits from a stronger general-reasoning model.
Unbounded loops and irreversible actions taken without review. Cap step counts, validate every structured output, and gate anything irreversible behind human approval until the system has a proven track record.