Appearance
Capstone: The Foreman
The Foreman is an on-call engineering assistant you page from Slack or Telegram. It researches an incident against your own runbooks, proposes a fix, tests that fix in a sandbox, remembers what worked for next time, and reports back, all from a phone message.
It's built from two cooperating agents, deliberately named after this course's own mascots:
- Owl , the research/triage half. Reads your runbooks, decides what's actually wrong, writes a structured incident report. Slow, careful, doesn't touch anything.
- Gnome , the execution half. Runs sandboxed diagnostics, tries fixes, and, the actual payoff of Day 3's skill-building lesson, writes down what worked as a reusable skill so it doesn't have to re-derive the fix next time.
Module map, where each piece comes from
| Lesson | Role in The Foreman |
|---|---|
| Prompting & Context Engineering | System prompts for Owl and Gnome, Owl's prompt rewards caution, Gnome's rewards concrete action |
| Vibe Coding with AI CLIs | Gemini CLI used during development, a custom /audit:code slash command checks the codebase before every deploy |
| Calling AI APIs Reliably | Resilient multi-provider calling, Owl fails over to a second provider if the primary is down (the exact scenario an on-call tool can't afford to skip) |
| Running Models Locally (optional course) | Local inference fallback, a small local model handles triage if the network itself is the incident |
| Spec-Driven Design | The tool-call contract between Owl, Gnome, and the LangGraph supervisor |
| Getting Reliable Structured Data | Owl's incident report is a guaranteed-valid Pydantic object, not free text, the report schema is the contract the rest of the system depends on |
| Giving Agents Memory | Runbooks and past-incident postmortems, chunked and indexed; Owl retrieves the right section, not just a keyword match |
| Automating Workflows with Events | A FastAPI webhook bridge turns a real monitoring alert into a message OpenClaw posts to your chat |
| Running Agent Code Safely | Gnome's diagnostic and fix scripts never touch the host directly, sandboxed per this lesson's isolation patterns |
| Deploying Your Agent | Deploying The Foreman itself so it's running when you're not at your desk |
| Multi-Agent Workflows & State Management | The Owl/Gnome split is a LangGraph supervisor-worker graph with a human approval gate before any fix executes for real |
| Building Reusable Skills | Gnome's self-improving skill loop, built on the real Hermes Agent pattern, not a metaphor, the actual SKILL.md mechanism |
| Exposing Agents to Users | OpenClaw is the whole point of contact, you never open a terminal to talk to The Foreman |
| Streaming Agent UIs | An SSE dashboard streams the graph's live reasoning when you are at your desk and want to watch |
| Observability | Every incident run is traced, you can audit exactly what Owl retrieved and what Gnome executed after the fact |
Prerequisites
Complete before starting:
- Giving Agents Memory: a working vector store with at least a few documents indexed (your own runbooks, or the lesson's example corpus)
- Running Agent Code Safely: a locked-down Docker sandbox works on your machine
- Multi-Agent Workflows & State Management: comfortable with LangGraph's
StateGraph, nodes, and conditional edges - Building Reusable Skills: Hermes Agent installed (
hermes doctorpasses) - Exposing Agents to Users: OpenClaw installed (
openclaw gateway statusshows the Gateway listening on18789) and at least one channel (Telegram or Slack) connected
bash
hermes doctor
openclaw gateway statusPart 1 , Owl: Research & Triage
Owl's only job is to turn "something's wrong" into a structured, evidence-backed incident report. It never executes anything.
Runbook retrieval
Index your runbooks the same way Giving Agents Memory indexes any corpus, sensible chunking, not fixed-size splitting, so a fix procedure doesn't get cut in half:
python
# owl/retrieval.py
from typing import TypedDict
class RunbookMatch(TypedDict):
title: str
section: str
content: str
score: float
def retrieve_runbook_context(query: str, k: int = 5) -> list[RunbookMatch]:
"""Hybrid search over the runbook vector store, reranked per the memory lesson's
cross-encoder pattern before returning the top k."""
... # see Giving Agents Memory for the full pgvector + reranking implementationThe incident report contract
This is the schema everything downstream depends on, Owl either produces a valid one or the graph halts and asks a human, it never passes through a best-guess:
python
# owl/schema.py
from pydantic import BaseModel, Field
from typing import Literal
class IncidentReport(BaseModel):
severity: Literal["low", "medium", "high", "critical"]
summary: str = Field(..., max_length=280)
likely_cause: str
matched_runbook: str | None
proposed_fix: str
requires_human_approval: bool
confidence: float = Field(..., ge=0.0, le=1.0)Resilient provider calling
Owl is the piece that has to work at 3am when one provider is degraded, this is the exact case Calling AI APIs Reliably's backoff/failover pattern exists for:
python
# owl/agent.py
from tenacity import retry, stop_after_attempt, wait_random_exponential
PROVIDERS = ["primary", "fallback"] # e.g. Claude, then Gemini
@retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(3))
def _call_provider(provider: str, prompt: str) -> str:
... # per-provider client call, per Calling AI APIs Reliably
def triage(alert_text: str, runbook_context: list) -> "IncidentReport":
for provider in PROVIDERS:
try:
raw = _call_provider(provider, build_prompt(alert_text, runbook_context))
return IncidentReport.model_validate_json(raw)
except Exception:
continue
raise RuntimeError("All providers exhausted, falling back to local inference")Part 2 , Gnome: Execute & Fix
Gnome only runs once Owl's report exists and (for anything above low severity) a human has approved it. Its two jobs: run the fix safely, and remember it.
Sandboxed execution
Every diagnostic or fix script runs the same way Running Agent Code Safely teaches, no host access, no network during validation, resource-capped:
bash
docker run --rm -m 128m --network none \
--runtime=runsc \
-v "$(pwd)/fix_script.py:/app/fix_script.py:ro" \
python:3.11-slim python /app/fix_script.pyReal self-improving skills
This is the actual Hermes Agent mechanism, not a simplified version of it. Once a fix works, Gnome writes it down exactly the way Building Reusable Skills's skill_manage tool does:
text
hermes> The disk-space alert fix worked. Save this as a reusable skill called 'clear-log-overflow'.Produces ~/.hermes/skills/clear-log-overflow/SKILL.md:
markdown
---
name: clear-log-overflow
description: Clears rotated logs when disk usage crosses 90% on app servers.
metadata:
hermes:
tags: [ops, disk, incident]
---
# Clear Log Overflow
## When to Use
Disk usage alert fires above 90% and `/var/log` is the largest consumer.
## Procedure
1. Run `du -sh /var/log/* | sort -rh | head -5` to confirm log growth is the cause.
2. Run `find /var/log -name "*.gz" -mtime +14 -delete` inside the sandbox.
3. Re-check disk usage; if still above 90%, escalate to a human, do not resize volumes automatically.
## Verification
Disk usage drops below 80% after cleanup.The next time this exact alert fires, Gnome doesn't re-derive the fix , openclaw skills list (or the Hermes equivalent) already has it, and the whole incident resolves in the time it takes to run the skill.
Part 3 , Wiring Owl and Gnome
A LangGraph supervisor routes between Owl and Gnome, with a hard-coded human approval gate before Gnome ever runs a fix for real:
python
# graph.py
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import MemorySaver
class ForemanState(TypedDict):
messages: Annotated[list, add_messages]
report: dict | None
approved: bool
next_step: str
def owl_node(state: ForemanState):
report = triage(state["messages"][-1].content, retrieve_runbook_context(...))
needs_approval = report.severity in ("high", "critical") or not report.requires_human_approval is False
return {"report": report.model_dump(), "next_step": "await_approval" if needs_approval else "gnome"}
def gnome_node(state: ForemanState):
result = execute_fix(state["report"])
return {"messages": [{"role": "assistant", "content": result}], "next_step": "end"}
workflow = StateGraph(ForemanState)
workflow.add_node("owl", owl_node)
workflow.add_node("gnome", gnome_node)
workflow.add_edge(START, "owl")
workflow.add_conditional_edges("owl", lambda s: s["next_step"], {
"gnome": "gnome",
"await_approval": END, # graph pauses here; resumes on human approval via checkpointer
})
workflow.add_edge("gnome", END)
app = workflow.compile(checkpointer=MemorySaver())Part 4 , Exposing The Foreman via OpenClaw
The Gateway is the only interface a human touches. Install and connect it the real way (not the fictional git-clone/@skill-decorator flow from an earlier version of this doc):
bash
curl -fsSL https://openclaw.ai/install.sh | bash
openclaw onboard --install-daemon
openclaw gateway status # confirms port 18789The Foreman itself is exposed as a markdown skill , openclaw skills list should show it once this is in place:
~/.openclaw/workspace/skills/
└── foreman/
└── SKILL.mdSave the following markdown configuration as your SKILL.md inside that directory. This defines the system instructions and task routing for the Foreman agent when it receives incoming alert messages:
markdown
---
name: foreman
description: On-call engineering assistant, triages alerts, proposes and sandboxes fixes.
---
# The Foreman
When the user reports an incident, or a message arrives prefixed "ALERT:", run:
```bash
python3 ~/AI_BOOTCAMP/labs/foreman/run_graph.py "$MESSAGE"
```
Relay the returned incident report verbatim. If `requires_human_approval` is true,
ask explicitly for a yes/no before invoking Gnome's fix step.Part 5 , Triggering from a real alert
A small FastAPI bridge is the Automating Workflows with Events payoff, it turns an actual monitoring webhook into a message in your chat, using the same async-bridge pattern that lesson teaches:
python
# webhook_bridge.py
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
@app.post("/alerts/incoming")
async def incoming_alert(request: Request):
payload = await request.json()
message = f"ALERT: {payload.get('summary', 'unspecified incident')}"
async with httpx.AsyncClient() as client:
await client.post(
"http://localhost:18789/api/messages",
json={"channel": "ops-oncall", "text": message},
)
return {"status": "forwarded"}Part 6 , Watching it think
When you're at your desk, an SSE endpoint mirrors the LangGraph's step events so you can watch Owl retrieve and Gnome execute in real time, using the same async-generator pattern from Streaming Agent UIs:
python
# dashboard_stream.py
from fastapi.responses import StreamingResponse
async def stream_graph_events(thread_id: str):
async for event in app.astream_events({"messages": [...]}, {"configurable": {"thread_id": thread_id}}):
yield f"event: {event['event']}\ndata: {event['data']}\n\n"Part 7 , Observability
Every run, Owl's retrieval, the provider that answered, Gnome's sandboxed execution, the skill it wrote, gets an OTel span, so a bad incident response is auditable after the fact, not just in the moment:
python
from langfuse.decorators import observe
@observe(name="foreman.incident")
def handle_incident(alert_text: str):
return app.invoke({"messages": [{"role": "user", "content": alert_text}]})Runtime Verification
To verify that the entire system is properly wired and integrated, run the following status checks and send a mock alert payload to validate the webhook ingestion pipeline:
bash
# Gateway live and skill registered
openclaw gateway status
openclaw skills list | grep foreman
# Send a test alert through the webhook bridge
curl -X POST http://localhost:8000/alerts/incoming \
-H "Content-Type: application/json" \
-d '{"summary": "disk usage 94% on app-server-3"}'
# Confirm it reached your chat, then reply to approve the fix if promptedConfirm the full loop: alert → chat message → Owl's report → your approval → Gnome's sandboxed fix → a new or reused SKILL.md → a summary back in chat.
Failure mode reference:
| Symptom | Cause | Fix |
|---|---|---|
| No message arrives in chat | Webhook bridge can't reach the Gateway | Confirm openclaw gateway status shows port 18789, check the bridge's POST URL |
| Owl's report fails schema validation | Provider returned free text instead of the contracted JSON | Tighten the prompt's format instructions per Getting Reliable Structured Data; add a repair/retry pass |
| Gnome's fix never runs | Graph is paused at the approval gate | Check approved state in the checkpointer; approval must resume the graph explicitly |
| Skill doesn't apply next time | SKILL.md frontmatter malformed, or gated by an unmet metadata.openclaw.requires | Validate frontmatter; see Exposing Agents to Users's What Goes Wrong section for the exact pattern |
| All providers fail in Owl | Both API providers down simultaneously | Confirm the local-inference fallback path is wired in, not just logged as a TODO |