Appearance
Capstone: Building the Agentic Triad
This capstone integrates three systems from across the curriculum into a single running pipeline: a Gemini CLI workspace auditor, a Hermes self-improving agent, and an OpenClaw gateway that exposes both through Telegram. When complete, you can trigger multi-agent workflows from a phone message and receive structured results.
All three components run as Docker services on your Linux machine. Telegram is the unified control surface. The Gemini CLI is the development-time code auditor.
Prerequisites
Complete before starting this capstone:
- M09: Docker + gVisor configured (
docker run --runtime=runscworks) - M11: LangGraph state schema concepts
- M12: Hermes agent installation and
docker-compose upworking - M13: OpenClaw gateway installed, Telegram bot token in hand
- M15: Langfuse tracing running locally
bash
# Verify all services
docker ps | grep -E "(hermes|openclaw|langfuse|postgres)"
ollama list | grep llama-guard3Application 1: Gemini CLI Workspace Auditor
A terminal utility that monitors Python scripts for architectural violations. Uses Gemini CLI context injection to audit files on demand.
Configure workspace context
bash
cd ~/AI_BOOTCAMP
mkdir -p .gemini/commands/auditCreate .gemini/GEMINI.md — the workspace rules Gemini CLI loads automatically:
markdown
# AI_BOOTCAMP Workspace Audit Rules
## Enforcement Standards
- All async functions must use `await`, not `time.sleep()` or `requests.*`
- All function arguments and return values require type hints
- No hardcoded credentials, API keys, or passwords — use `os.environ`
- Pydantic v2 models must use `model_config = ConfigDict(...)` not class `Config`
- Docker Compose files must not expose databases on 0.0.0.0
## Reporting Format
Output a markdown table with columns: Line | Violation | Severity | Fix
After the table, output a fully corrected version of the file.Create the audit slash command at .gemini/commands/audit/code.toml:
toml
description = "Audits a Python file for async violations, type safety, and credential leaks."
prompt = """
You are a senior Python architect performing a security and quality audit.
Apply the rules from GEMINI.md to the injected file context.
Report format:
1. Violations table (Line | Violation | Severity | Fix)
2. Risk summary (Critical/High/Medium/Low counts)
3. Corrected file
Be specific about line numbers. If the file is clean, say so explicitly.
"""Run an audit
bash
# Launch Gemini CLI
npx @google/gemini-cli
# Inject a file for audit — @ injects the file content into context
gemini> /audit:code @./labs/event-automation/webhook_handler.pyThe auditor reads your workspace rules from GEMINI.md and applies them to whatever file you inject. Run it before every commit on agent-facing code.
Automate audits with a shell wrapper
bash
# audit.sh — audit any Python file from the terminal
#!/bin/bash
TARGET="${1:-}"
if [ -z "$TARGET" ]; then
echo "Usage: ./audit.sh path/to/file.py"
exit 1
fi
if [ ! -f "$TARGET" ]; then
echo "File not found: $TARGET"
exit 1
fi
echo "Auditing: $TARGET"
npx @google/gemini-cli --prompt "/audit:code @$TARGET" --no-interactivebash
chmod +x audit.sh
./audit.sh labs/hermes-agent/skill_executor.pyApplication 2: Hermes Self-Improving Agent
A persistent research assistant that writes code, sandboxes it, catches failures, self-heals, and registers successful implementations as reusable skills.
Boot Hermes
bash
cd ~/AI_BOOTCAMP/labs/hermes-agent
docker-compose up -d
# Confirm containers are up
docker-compose ps
# hermes-core running
# postgres runningConnect and assign a task
bash
docker exec -it hermes-agent-core hermes-cliAssign a task that requires Hermes to discover its own dependencies — the self-healing loop only fires when the first execution fails:
hermes> Create a new skill called 'scrape_ai_news'.
Specifications:
- Target: https://news.ycombinator.com
- Parse the top 10 posts. Filter for titles containing 'AI', 'LLM', or 'agent'.
- Format each as: "## [Title](url)\nScore: N | Comments: N"
- Save markdown to /app/data/ai_newsletter.md
- Return a summary of how many posts matched the filter.Observe the self-healing loop
Watch the console. The sequence should be:
[hermes] Drafting skill: scrape_ai_news
[hermes] Executing in sandbox...
[sandbox] ModuleNotFoundError: No module named 'beautifulsoup4'
[hermes] Installing missing dependency: beautifulsoup4
[sandbox] ModuleNotFoundError: No module named 'requests'
[hermes] Installing missing dependency: requests
[hermes] Retry 3: SUCCESS
[hermes] Compiling skill to /app/skills/scrape_ai_news.py
[hermes] Registering in memory store...
[hermes] Skill registered. 7 posts matched filter.Each failure is caught, the missing import is installed inside the sandbox, and execution retries. The successful implementation is persisted to the skills library and the pgvector memory store.
Verify the skill is registered
python
# check_skills.py — run from outside the container
import psycopg2
import os
conn = psycopg2.connect(
host="localhost",
port=5432,
database=os.environ["POSTGRES_DB"],
user=os.environ["POSTGRES_USER"],
password=os.environ["POSTGRES_PASSWORD"],
)
cursor = conn.cursor()
cursor.execute("""
SELECT name, created_at, execution_count
FROM agent_skills
ORDER BY created_at DESC
LIMIT 5;
""")
for row in cursor.fetchall():
print(f"Skill: {row[0]} | Created: {row[1]} | Runs: {row[2]}")
conn.close()bash
# Load credentials from .env, never hardcode
set -a && source .env && set +a
python3 check_skills.pyApplication 3: OpenClaw Remote Gateway
OpenClaw receives Telegram messages, parses them as commands, and dispatches to registered skills — including triggering Hermes tasks and querying the pgvector memory store.
Environment setup
bash
cd ~/AI_BOOTCAMP/labs/openclaw
cp .env.example .envEdit .env — fill real values, never commit this file:
bash
TELEGRAM_BOT_TOKEN=123456789:REPLACE_WITH_YOUR_BOT_TOKEN
TELEGRAM_ALLOWED_USER_IDS=REPLACE_WITH_YOUR_TELEGRAM_USER_ID
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=hermes
POSTGRES_USER=agent_user
POSTGRES_PASSWORD=REPLACE_WITH_YOUR_POSTGRES_PASSWORD
PORT=9000
DATA_DIR=~/AI_BOOTCAMP/labs/openclaw/dataWrite skills that connect to Hermes
python
# skills/hermes_bridge.py
from openclaw.skills import skill
import subprocess
import psycopg2
import os
import logging
logger = logging.getLogger(__name__)
def _get_db_conn():
return psycopg2.connect(
host=os.environ["POSTGRES_HOST"],
port=os.environ["POSTGRES_PORT"],
database=os.environ["POSTGRES_DB"],
user=os.environ["POSTGRES_USER"],
password=os.environ["POSTGRES_PASSWORD"],
)
@skill(description="Runs the Hermes AI news scraping skill and returns the summary.")
def run_news_scrape() -> str:
try:
result = subprocess.run(
["docker", "exec", "hermes-agent-core", "hermes", "run", "scrape_ai_news"],
capture_output=True,
text=True,
timeout=120,
)
if result.returncode != 0:
logger.error(f"Hermes skill failed: {result.stderr[:200]}")
return f"Scrape failed: {result.stderr[:100]}"
return result.stdout.strip() or "Scrape complete. Check /app/data/ai_newsletter.md"
except subprocess.TimeoutExpired:
return "Hermes skill timed out after 120s."
except Exception as e:
logger.exception("Unexpected error running Hermes skill")
return f"Error: {e}"
@skill(description="Returns the last N items from Hermes long-term memory.")
def recall_memories(n: int = 5) -> str:
try:
conn = _get_db_conn()
cursor = conn.cursor()
cursor.execute(
"SELECT content, created_at FROM agent_longterm_memories "
"ORDER BY created_at DESC LIMIT %s;",
(n,)
)
rows = cursor.fetchall()
conn.close()
if not rows:
return "No memories stored yet."
return "\n".join(f"- {row[0]} ({row[1].strftime('%Y-%m-%d')})" for row in rows)
except Exception as e:
logger.exception("Failed to query memory store")
return f"Memory query failed: {e}"
@skill(description="Lists all compiled Hermes skills.")
def list_skills() -> str:
try:
conn = _get_db_conn()
cursor = conn.cursor()
cursor.execute(
"SELECT name, execution_count FROM agent_skills ORDER BY execution_count DESC;"
)
rows = cursor.fetchall()
conn.close()
return "\n".join(f"• {r[0]} ({r[1]} runs)" for r in rows) or "No skills registered."
except Exception as e:
return f"Failed: {e}"Launch OpenClaw
bash
cd ~/AI_BOOTCAMP/labs/openclaw
set -a && source .env && set +a
nvm use default
npm install
npm run startTest from Telegram
Send these messages to your bot:
/run_news_scrape
/list_skills
/recall_memories 3You should receive formatted responses pulled from your Hermes agent's memory store and skill execution results.
Application 4: The Unified Triad Pipeline
All three components running together. The workflow:
- You open Telegram and message:
/run_news_scrape - OpenClaw receives the webhook, authenticates your Telegram user ID, routes to
run_news_scrape() - The skill triggers
docker exec hermes-agent-core hermes run scrape_ai_news - Hermes executes the compiled skill inside the gVisor sandbox
- Results write to
/app/data/ai_newsletter.mdand pgvector memory - OpenClaw sends the summary back to your Telegram chat
Meanwhile, from your development terminal:
- Run
./audit.sh labs/hermes-agent/skill_executor.pyto check the code quality - The Gemini CLI auditor reads GEMINI.md rules and flags any violations before you deploy changes
Bring everything up
bash
# Terminal 1: Hermes + Postgres
cd ~/AI_BOOTCAMP/labs/hermes-agent && docker-compose up
# Terminal 2: OpenClaw
cd ~/AI_BOOTCAMP/labs/openclaw && set -a && source .env && set +a && npm run start
# Terminal 3: Monitor
watch -n 2 'docker stats --no-stream'Add Langfuse tracing to the gateway
Every OpenClaw dispatch goes through Langfuse so you can trace latency, token counts, and self-healing cycles from the dashboard:
python
# skills/traced_bridge.py
from langfuse import Langfuse
from langfuse.decorators import observe
import os
langfuse = Langfuse(
public_key=os.environ["LANGFUSE_PUBLIC_KEY"],
secret_key=os.environ["LANGFUSE_SECRET_KEY"],
host=os.environ.get("LANGFUSE_HOST", "https://cloud.langfuse.com"),
)
@observe(name="openclaw.news_scrape")
def run_news_scrape_traced() -> str:
from hermes_bridge import run_news_scrape
return run_news_scrape()Open the Langfuse dashboard (http://localhost:3000 if self-hosted) and watch traces appear as you send Telegram commands.
Runtime Verification
bash
# All containers healthy
docker-compose -f ~/AI_BOOTCAMP/labs/hermes-agent/docker-compose.yml ps
docker-compose -f ~/AI_BOOTCAMP/labs/openclaw/docker-compose.yml ps
# OpenClaw is receiving webhooks (tail the log)
docker logs openclaw-gateway -f --tail=20
# Hermes memory is being written
psql -h localhost -U $POSTGRES_USER -d $POSTGRES_DB \
-c "SELECT count(*) FROM agent_longterm_memories;"
# Langfuse is recording traces
curl http://localhost:3000/api/health # {"status":"ok"}Failure mode reference:
| Symptom | Cause | Fix |
|---|---|---|
| Telegram bot not responding | TELEGRAM_ALLOWED_USER_IDS wrong | Get your ID via @userinfobot on Telegram |
Hermes exec fails | Container not named hermes-agent-core | Run docker ps and check the Names column, update the exec command |
| DB connection refused | Postgres port not exposed in docker-compose | Add ports: - "5432:5432" to the postgres service |
| Memory queries return empty | Hermes skill ran but memory write failed | Check POSTGRES_* env vars are loaded in Hermes container |
| Langfuse traces not appearing | Wrong host or keys | echo $LANGFUSE_PUBLIC_KEY — confirm env is loaded |