teachyou.ai academy
← All posts
Hermes Agent

What a Production-Grade Capstone Demo Actually Looks Like

Pramod Dutta · May 15, 2026 · 13 min read

The Demo That Dies the Moment Someone Else Clicks It

You've seen it a hundred times in course cohorts and portfolio reviews: a candidate shares their screen, runs a Python script in a Jupyter notebook, an agent calls a tool, prints a nice JSON blob, and everyone nods. Then someone asks the obvious question — "can I try it?" — and the answer is a long pause followed by "uh, let me set up my .env file first."

That pause is the whole problem. A capstone project that only works on the author's laptop, with their API keys, in their terminal, is not a demo. It's a private experiment with good PR. Production-grade means something specific and testable: a stranger can open a URL, interact with your agent, watch it fail gracefully when it fails, and read a README that tells them exactly what they're looking at — all without you sitting next to them explaining the missing pieces.

This distinction matters more now than it did two years ago, because "I built an agent" has become table stakes. Every bootcamp grad and every LinkedIn post claims it. What separates a hireable engineer from someone who followed a tutorial is whether their agent survives contact with someone else's browser, someone else's network, and someone else's edge cases. This article walks through exactly what that looks like in practice — the deployed endpoint, the monitoring, the error handling, the documentation — using the same standard we hold capstones to inside 30 Days of Hermes Agent.

Why "It Works on My Machine" Isn't a Demo

Before the checklist, it's worth being precise about why local-only demos fail as evidence of skill.

  • They hide the hardest 20% of the work. Anyone can call an LLM API in a script. The hard part — session handling, rate limits, retries, secrets management, cost control — only shows up once the thing runs somewhere other than your own shell.
  • They can't be evaluated asynchronously. A hiring manager or a course reviewer looking at 40 submissions is not going to clone 40 repos, install 40 sets of dependencies, and hunt for 40 missing API keys. If your project requires a live walkthrough to be understood, it will get skipped.
  • They don't prove the agent handles failure. Local demos are almost always the "happy path" run twenty times until it looks smooth on camera. Production traffic doesn't cooperate. A reviewer needs to see what happens on request twenty-one.
  • They erase the operational half of the skill. Building an agent that reasons well is maybe half the job. The other half — deploying it, keeping it observable, keeping costs bounded, keeping it from leaking a stack trace to a random user — is the part that actually gets you hired to maintain systems, not just prototype them.

There's also a quieter reason this matters: the habits you build while shipping a capstone are the habits you carry into your first real job. If your only rep is "make it run once, on my machine, for the demo," you haven't practiced the skill that actually gets tested on the job — keeping something running for strangers, over time, under conditions you didn't choose. A capstone is the cheapest place to fail at that. Production systems at a real employer are a much more expensive place to learn it for the first time.

None of this means your capstone needs to be a venture-scale platform. It doesn't need Kubernetes, a multi-region deployment, or a dedicated SRE rotation. It means it needs to clear a specific, achievable bar — the same bar a small, serious engineering team would hold any internal tool to before letting a colleague depend on it. Here's that bar, broken into four pillars.

Pillar One: A Deployed, Reachable Endpoint

The single non-negotiable requirement is that your agent lives at a URL, not on your laptop. This sounds obvious and is skipped constantly.

What "deployed" actually requires:

  • A live HTTP endpoint (or a hosted UI) that responds without you running anything locally first.
  • Environment variables and secrets configured on the hosting platform itself — not read from a local .env file that only exists on your machine.
  • A health check route that returns quickly and tells you the service is alive, separate from the actual agent logic.
  • Cold-start behavior you've actually tested — if you're on a serverless platform, the first request after idle time can time out if your agent does expensive setup on every boot.

A minimal FastAPI wrapper around an agent, structured for real deployment rather than a notebook cell, looks like this:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import logging
import time
import os

app = FastAPI(title="Hermes Capstone Agent")
logger = logging.getLogger("hermes_agent")

class AgentRequest(BaseModel):
    query: str
    session_id: str | None = None

class AgentResponse(BaseModel):
    answer: str
    latency_ms: int
    session_id: str

@app.get("/health")
def health_check():
    return {"status": "ok", "version": os.environ.get("APP_VERSION", "dev")}

@app.post("/agent/run", response_model=AgentResponse)
def run_agent(payload: AgentRequest):
    start = time.time()
    try:
        result = execute_agent_turn(payload.query, payload.session_id)
    except RateLimitError:
        logger.warning("upstream_rate_limited", extra={"session_id": payload.session_id})
        raise HTTPException(status_code=503, detail="Agent is temporarily overloaded, please retry.")
    except Exception:
        logger.exception("agent_run_failed", extra={"session_id": payload.session_id})
        raise HTTPException(status_code=500, detail="Something went wrong processing your request.")

    latency_ms = int((time.time() - start) * 1000)
    logger.info("agent_run_ok", extra={"session_id": payload.session_id, "latency_ms": latency_ms})
    return AgentResponse(
        answer=result,
        latency_ms=latency_ms,
        session_id=payload.session_id or "anonymous",
    )

Notice what this snippet is doing beyond "call the model": it separates the health check from the business logic, it distinguishes between an upstream failure (rate limiting) and an internal failure (a bug), and it never returns a raw exception message to the caller. Deploy this behind any standard platform — a container host, a small VM, a managed Python app runner — and you already clear the first pillar.

The reachability checklist:

  1. The endpoint responds from a machine that isn't yours (test from your phone's data connection, not your home WiFi).
  2. Secrets are set as environment variables on the host, and you've confirmed the app boots without your local .env file present.
  3. There's a public URL you can paste into a message with zero setup instructions attached.
  4. The service restarts cleanly if it crashes — you've actually killed the process once and watched it come back.

Pillar Two: Monitoring That Tells You Something Broke Before a User Does

A production system without monitoring is a system you find out about from an angry user, not from a dashboard. For a capstone, monitoring doesn't need to be enterprise-grade — it needs to answer three questions at any moment: is it up, is it slow, and is it erroring.

Minimum viable monitoring for an agent demo:

  • Structured logs, not print() statements. Every request should log a request ID, latency, token usage if applicable, and outcome (success, upstream failure, internal error).
  • A latency signal. Agents call LLMs, and LLM calls are the slowest part of your stack. You need to know your p50 and p95 response times, not just "it felt fast when I tried it."
  • An error rate signal. Even a simple counter of 5xx responses over time, visible somewhere, beats nothing.
  • An alert path, even a crude one. A free-tier uptime checker that pings your health endpoint every few minutes and emails you on failure is enough for a capstone. The point is you find out before your reviewer does.

A basic structured logging setup, extending the FastAPI example above, might look like this:

import logging
import json
import sys

class JsonLogFormatter(logging.Formatter):
    def format(self, record):
        payload = {
            "level": record.levelname,
            "message": record.getMessage(),
            "logger": record.name,
            "timestamp": self.formatTime(record),
        }
        for key in ("session_id", "latency_ms", "status_code"):
            if hasattr(record, key):
                payload[key] = getattr(record, key)
        return json.dumps(payload)

handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonLogFormatter())
logging.getLogger("hermes_agent").addHandler(handler)
logging.getLogger("hermes_agent").setLevel(logging.INFO)

Shipping logs as JSON to stdout matters because almost every hosting platform captures stdout and lets you search it — you don't need a separate logging service to get real observability, you just need logs that are structured enough to filter.

Demo checklist for monitoring:

  • You can answer "what's my current error rate" in under 30 seconds without SSHing into anything.
  • You've simulated a failure (bad API key, malformed input, upstream timeout) and confirmed it shows up in your logs with enough context to debug it.
  • You have some form of alerting, even if it's a free uptime monitor hitting /health every five minutes.
  • Token usage or cost per request is logged somewhere, so a spike doesn't surprise you with a bill.

Pillar Three: Error Handling That Assumes Everything Will Go Wrong

This is where most capstones lose points, because error handling isn't visible in a screen recording of the happy path — it only shows up when someone deliberately or accidentally breaks your agent. And they will.

The failure modes a reviewer will actually try:

  • Empty or garbage input (an empty string, 10,000 characters of random text, emoji-only input).
  • The upstream LLM API being slow, rate-limited, or briefly down.
  • A tool call inside your agent failing (a web search returning nothing, a database lookup timing out).
  • Concurrent requests — two users hitting the agent at the same time, especially if you're maintaining any session or memory state.
  • Malicious-ish input, like prompt injection attempts embedded in a user query.

Handling these doesn't mean building a bulletproof system. It means every one of these cases produces a clear, bounded response instead of a stack trace, an infinite hang, or a silent wrong answer presented with total confidence.

import time
from functools import wraps

def with_retry_and_timeout(max_retries=2, timeout_seconds=15):
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            last_error = None
            for attempt in range(max_retries + 1):
                start = time.time()
                try:
                    result = fn(*args, **kwargs)
                    if time.time() - start > timeout_seconds:
                        raise TimeoutError("agent step exceeded timeout")
                    return result
                except (TimeoutError, ConnectionError) as e:
                    last_error = e
                    wait = min(2 ** attempt, 8)
                    time.sleep(wait)
                except Exception:
                    raise
            raise last_error
        return wrapper
    return decorator

@with_retry_and_timeout(max_retries=2, timeout_seconds=15)
def call_tool(tool_name: str, tool_input: dict):
    return execute_tool(tool_name, tool_input)

This pattern — bounded retries with backoff, a hard timeout, and a clear distinction between "retry this" and "don't retry this, surface it" — is the difference between an agent that degrades gracefully under real-world flakiness and one that either hangs forever or crashes on the first hiccup.

Error handling checklist:

  • Empty, oversized, and malformed inputs all return a clean 4xx-style message, not a 500.
  • Every external call (LLM API, tool, database) has a timeout. None are allowed to hang indefinitely.
  • Retries exist for transient failures, with backoff, and a ceiling so you never retry forever.
  • Errors returned to the user never include raw stack traces, internal file paths, or API keys.
  • You've tried to break your own agent with adversarial input at least once before calling it done.

Pillar Four: A README That Lets a Stranger Understand the Project in Two Minutes

The README is the artifact reviewers actually read first, and often the only one they read in full. A production-grade README isn't documentation for documentation's sake — it's the interface between your work and someone who has never seen it before and has limited time.

What belongs in the README, in order:

  1. One paragraph: what this agent does and why it exists. Not a feature list — the actual problem it solves, in plain language.
  2. The live demo link, placed at the top, not buried at the bottom after installation instructions nobody needed.
  3. Architecture, briefly. A short diagram or even a bulleted description of the flow: request comes in, agent reasons, tools get called, response goes out. Enough that someone can form a mental model in 30 seconds.
  4. How to run it locally, for the reviewer who does want to dig in — clear setup steps, required environment variables listed by name (not values), and the exact command to start it.
  5. Known limitations. This is the section people skip and it's the one that signals seniority. Every real system has limits — rate limits, cost ceilings, edge cases you haven't handled. Saying so explicitly is a mark of engineering maturity, not a confession of failure.
  6. What you'd do next. A short, honest list of what you'd build with another week. This shows you understand the gap between "demo" and "product," which is exactly the point of the exercise.

A README that's missing the live link, missing setup instructions, or missing known limitations reads as unfinished even if the underlying code is solid — because the reviewer has no way to verify that without doing your work for you.

Putting It Together: The Full Capstone Demo Checklist

Here's the consolidated list we actually use when evaluating whether a capstone project is production-grade or still a prototype.

  • Deployment

- Live public URL, reachable with zero local setup. - Secrets configured on the host, not read from a local file. - Health check endpoint separate from core agent logic. - Confirmed clean restart after a crash.

  • Monitoring

- Structured logs with request ID, latency, and outcome. - Visible latency and error-rate signal. - Basic uptime alerting on the health endpoint. - Token or cost tracking per request.

  • Error handling

- Clean responses for empty, oversized, and malformed input. - Timeouts on every external call. - Bounded retries with backoff for transient failures. - No stack traces, file paths, or secrets ever returned to the caller.

  • Documentation

- README leads with what the project does and the live demo link. - Local setup instructions that actually work if followed cold. - Explicit, honest list of known limitations. - A short "what's next" section.

If you can check every box on this list, you don't have a tutorial project — you have something you can put in front of a hiring manager, a client, or a course reviewer and say "click it, break it, I've thought about what happens when you do."

The Gap Between "It Runs" and "It's Production-Grade"

Here's the uncomfortable truth: the reasoning logic inside most agent capstones — the prompt design, the tool selection, the planning loop — is often fine. What's usually missing is everything around it. And "everything around it" is not a footnote. It's the part of the job that turns a clever demo into software someone else can depend on.

This gap is exactly why so many technically competent projects don't land in interviews or portfolios the way they should. A reviewer skimming forty submissions isn't measuring whose prompt is cleverest. They're measuring whose project they can click on, trust, and understand in under three minutes — and whose project falls over the moment they type something the author didn't anticipate.

The fix isn't more cleverness in the agent's reasoning. It's discipline in the four pillars above, applied consistently, before you call the project done.

Where This Fits Into 30 Days of Hermes Agent

This exact standard — deployed endpoint, real monitoring, defensive error handling, a README that respects the reader's time — is the bar every capstone is held to inside 30 Days of Hermes Agent. The course doesn't stop at "get the agent to reason correctly." It walks through shipping that agent as something a stranger can open, use, and trust, with dedicated modules on deployment, observability, and failure handling that mirror everything in this checklist.

If you've already got an agent that works on your machine, the gap between that and a capstone you'd actually put on your resume is smaller than it feels — but it's a real gap, and it's the one this course is built to close.