Skip to content

Commit 08dd6c8

Browse files
authored
Merge pull request #293 from hud-evals/l/docs-update-8
cookbook update
2 parents 9cec753 + 64459ed commit 08dd6c8

1 file changed

Lines changed: 95 additions & 35 deletions

File tree

docs/cookbooks/ops-diagnostics.mdx

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ This cookbook walks through how we built it—focusing on **environment design**
1010

1111
## Why Hierarchical?
1212

13-
When you connect multiple MCP servers to a single environment, the agent sees all tools at once. For diagnostics across four services, this meant 60+ tools in a flat list. The cognitive load made it harder for the model to select the right tool for the job.
13+
When you connect multiple MCP servers to a single environment, the agent sees all tools at once. For diagnostics across six services, this meant 60+ tools in a flat list. The cognitive load made it harder for the model to select the right tool for the job.
1414

1515
We restructured into a hierarchy: an orchestrator that delegates to specialized subagents.
1616

1717
```mermaid
1818
flowchart TD
1919
subgraph orch["Orchestrator"]
20-
O["4 subagent tools"]
20+
O["Up to 6 subagent tools"]
2121
end
2222
2323
subgraph sentry["Sentry Agent"]
@@ -44,13 +44,25 @@ flowchart TD
4444
K3["describe_pod"]
4545
end
4646
47+
subgraph docs["Docs Agent"]
48+
D1["search_docs"]
49+
end
50+
51+
subgraph github["GitHub Agent"]
52+
G1["search_code"]
53+
G2["get_issues"]
54+
G3["get_workflows"]
55+
end
56+
4757
O --> sentry
4858
O --> supabase
4959
O --> railway
5060
O --> kubectl
61+
O --> docs
62+
O --> github
5163
```
5264

53-
The orchestrator sees only 4 tools—one per specialist. Each specialist has a focused toolset for its domain.
65+
The orchestrator sees only a handful of tools—one per specialist. Each specialist has a focused toolset for its domain. And crucially, **only subagents with valid credentials are registered**.
5466

5567
## Environment Design
5668

@@ -179,52 +191,96 @@ Provide findings, root cause analysis, and recommended fixes."""
179191

180192
## Building the Orchestrator
181193

182-
The orchestrator wraps each subagent's scenario as an `AgentTool`:
194+
### Dynamic Subagent Detection
195+
196+
A key pattern: **only register subagents for which credentials are present**. This lets you run the same orchestrator code with different configurations—maybe you only have Sentry and Supabase credentials locally, but the full set in production.
183197

184198
```python
185199
# orchestrator.py
186200
from hud import Environment
187201
from hud.tools import AgentTool
188-
from hud.agents import create_agent
189-
import hud
202+
import os
190203

191-
from environments import sentry_env, supabase_env, railway_env, kubectl_env
204+
orch_env = Environment(name="ops-orchestrator")
205+
206+
# Define subagents with their required env vars
207+
# Format: (tool_name, module_attr, description, required_env_vars)
208+
_subagent_configs = [
209+
("investigate_sentry", "sentry_env", "Check error monitoring", ["SENTRY_AUTH_TOKEN"]),
210+
("investigate_supabase", "supabase_env", "Check database/auth", ["SUPABASE_ACCESS_TOKEN"]),
211+
("investigate_railway", "railway_env", "Check deployments", ["RAILWAY_API_TOKEN"]),
212+
("investigate_kubernetes", "kubectl_env", "Check cluster health", ["KUBECONFIG_B64", "KUBECONFIG"]),
213+
("search_docs", "docs_env", "Search internal documentation", ["DOCS_MCP"]),
214+
("investigate_github", "github_env", "Search code and issues", ["GITHUB_PAT"]),
215+
]
216+
217+
# Only register subagents with valid credentials
218+
_subagents = []
219+
for name, module_attr, desc, required_vars in _subagent_configs:
220+
# Check if ANY of the required vars are set (OR logic for alternatives like KUBECONFIG_B64 or KUBECONFIG)
221+
if not any(os.getenv(var) for var in required_vars):
222+
continue
223+
224+
import environments
225+
env = getattr(environments, module_attr)
226+
_subagents.append((name, env, desc))
227+
228+
# Add only the available subagents to the orchestrator
229+
for name, env, desc in _subagents:
230+
tool = AgentTool(
231+
env("investigate"),
232+
model=os.getenv("ORCH_MODEL", "gpt-4o-mini"),
233+
name=name,
234+
description=desc,
235+
)
236+
orch_env.add_tool(tool.mcp)
237+
```
192238

239+
Now the orchestrator only exposes tools for services you actually have access to. No more confusing "tool not available" errors.
193240

194-
async def diagnose(query: str, model: str = "claude-sonnet-4-5"):
195-
orchestrator = Environment(name="ops-orchestrator")
196-
197-
# Wrap each subagent as a tool
198-
for name, env, desc in [
199-
("investigate_sentry", sentry_env, "Check error monitoring"),
200-
("investigate_supabase", supabase_env, "Check database/auth"),
201-
("investigate_railway", railway_env, "Check deployments"),
202-
("investigate_kubernetes", kubectl_env, "Check cluster health"),
203-
]:
204-
tool = AgentTool(
205-
env("investigate"),
206-
model=model,
207-
name=name,
208-
description=desc,
209-
)
210-
orchestrator.add_tool(tool.mcp)
241+
### Configurable Documentation Search
242+
243+
The docs subagent connects to any MCP server that provides documentation search. Set `DOCS_MCP` to the URL of your docs MCP server:
244+
245+
```python
246+
# environments/docs.py
247+
docs_env = Environment(name="docs-agent")
248+
249+
docs_mcp_url = os.getenv("DOCS_MCP")
250+
if docs_mcp_url:
251+
docs_env.connect_mcp_config({
252+
"docs": {"url": docs_mcp_url}
253+
})
254+
```
255+
256+
This makes the orchestrator reusable across different organizations—just point `DOCS_MCP` at your own documentation.
257+
258+
### The Scenario
259+
260+
The orchestrator wraps each subagent's scenario as an `AgentTool`:
261+
262+
```python
263+
def _format_subagent_list():
264+
"""Dynamically list available subagents for the prompt."""
265+
return "\n".join(f"- **{name}**: {desc}" for name, _, desc in _subagents)
266+
267+
@orch_env.scenario("diagnose")
268+
async def orch_diagnose(query: str):
269+
subagent_list = _format_subagent_list()
211270

212-
@orchestrator.scenario("diagnose")
213-
async def run_diagnosis(issue: str):
214-
yield f"""You are an ops diagnostics orchestrator.
271+
yield f"""You are an ops diagnostics orchestrator with specialized subagents:
215272
216-
**Issue:** {issue}
273+
{subagent_list}
274+
275+
**Issue to diagnose:** {query}
276+
277+
**IMPORTANT: All subagents are READ-ONLY.**
217278
218-
You have READ-ONLY subagents for Sentry, Supabase, Railway, and Kubernetes.
219279
Investigate systematically and correlate findings across services."""
220-
221-
task = orchestrator("diagnose", issue=query)
222-
223-
async with hud.eval(task) as ctx:
224-
agent = create_agent(model)
225-
return await agent.run(ctx, max_steps=20)
226280
```
227281

282+
The prompt dynamically lists only the available subagents, so the agent knows exactly what tools it has.
283+
228284
### Trace Continuity
229285

230286
All subagent activity appears in a single trace on the HUD platform. When the orchestrator calls a subagent tool, the inference and tool calls are recorded under the parent trace—no separate URLs to track.
@@ -471,6 +527,10 @@ The entire investigation—from initial query to actionable recommendations—to
471527

472528
3. **Custom tools fill gaps.** When MCP servers don't fit your auth model, build direct API integrations.
473529

530+
4. **Dynamic detection enables flexibility.** Only registering subagents with valid credentials means the same code works across different environments—dev, staging, production—with different service access.
531+
532+
5. **Configurable integrations improve reusability.** Making things like `DOCS_MCP` configurable via env vars lets others use your orchestrator with their own services.
533+
474534
## See Also
475535

476536
- [AgentTool Reference](/reference/tools#agenttool)

0 commit comments

Comments
 (0)