teachyou.ai academy
← All posts
Hermes Agent

Portfolio Projects That Get You Hired as an AI Agent Engineer

Pramod Dutta · Jun 28, 2026 · 14 min read

The Portfolio Problem Nobody Talks About

Every AI agent job posting says the same thing: "experience building production agents." Almost nobody applying has that experience, because agent engineering as a discipline is barely two years old. So hiring managers have quietly agreed on a workaround — they stop asking about your resume and start asking to see your GitHub. If you have three or four agent projects that show real judgment about tool design, error handling, and memory, you will out-interview candidates with fancier titles and no evidence.

The trap is that most "AI agent" portfolios look identical: a LangChain wrapper around GPT-4 that answers questions about a PDF. That project proves you can follow a tutorial. It does not prove you can build something that survives contact with a real user, a flaky API, or an ambiguous instruction. What follows are project ideas specifically chosen because each one forces you to solve a problem that tutorials skip — state management, tool reliability, cost control, evaluation, multi-agent coordination — and each one produces something you can demo live in an interview instead of just describing.

Treat this as a menu, not a checklist. Two or three of these, built with real depth instead of six built shallowly, will do more for your career than a dozen half-finished repos. The hiring bar for agent engineering roles right now is unusually forgiving on credentials and unusually strict on evidence. Nobody expects you to have shipped an agent that serves millions of requests, but everybody expects you to have run into the problems that show up the moment an agent leaves a notebook: tools that fail silently, context windows that fill up with irrelevant history, costs that creep up without anyone noticing, and outputs that are subtly wrong instead of obviously wrong. A portfolio that shows you've met those problems head-on, in miniature, reads as far more senior than a portfolio that's simply bigger.

There's also a practical reason to build rather than just study: agent engineering interviews increasingly include a live coding or debugging segment where you extend or fix somebody else's agent code under time pressure. The muscle memory you build wiring up retries, memory stores, and eval loops in your own projects transfers directly. You stop thinking about these patterns as things you read about and start thinking about them as things you've typed before, which changes how fast and how calmly you move in that kind of interview.

Project 1: A Tool-Calling Agent With a Real Failure Budget

Start here even if it feels basic, because most candidates get this one wrong. The idea is simple: build an agent that can call three or four real external tools — a weather API, a calculator, a search API, a file reader — and complete multi-step tasks that require chaining them. The differentiator is not the tools. It's what happens when a tool call fails.

Interviewers want to see that you understand agents are not deterministic programs. APIs time out. Models hallucinate arguments. JSON comes back malformed. A portfolio project that only demos the happy path signals you haven't operated one of these in production. Build in retry logic with exponential backoff, a maximum tool-call budget per task so the agent can't loop forever burning tokens, and a fallback path where the agent tells the user it's stuck instead of confabulating an answer.

import time
import json

MAX_TOOL_CALLS = 6

def run_agent(user_task, tools, model_client):
    messages = [{"role": "user", "content": user_task}]
    calls_made = 0

    while calls_made < MAX_TOOL_CALLS:
        response = model_client.chat(messages=messages, tools=tools)

        if response.stop_reason != "tool_use":
            return response.text

        tool_call = response.tool_call
        calls_made += 1

        result = execute_with_retry(tool_call, tools, max_attempts=3)
        messages.append({"role": "assistant", "content": response.raw})
        messages.append({
            "role": "tool_result",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        })

    return "I couldn't complete this task within the allowed steps. Here's what I found so far."


def execute_with_retry(tool_call, tools, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            fn = tools[tool_call.name]
            return fn(**tool_call.arguments)
        except Exception as err:
            if attempt == max_attempts - 1:
                return {"error": str(err), "tool": tool_call.name}
            time.sleep(2 ** attempt)

Write a README section titled "Failure Modes" that lists every way you deliberately broke the agent while testing — bad API keys, rate limits, malformed arguments — and how it recovered. That section is what actually gets read in a screening interview.

Project 2: A Long-Running Agent With Persistent Memory

Single-turn demos are easy to build and easy to forget. What's harder, and much more valuable to show, is an agent that remembers things across sessions — a research assistant that recalls what you asked it last week, or a coding agent that remembers architectural decisions from three days ago. This project forces you to confront the real engineering problem behind "memory": deciding what's worth storing, how to retrieve it cheaply, and how to keep stale context from poisoning new conversations.

Scope it as a personal research agent. The user gives it topics to track over days or weeks. Each session, it pulls relevant prior notes, does new work, and writes updated notes back to storage — a lightweight version of the same PARA-style memory pattern used in serious agent frameworks, minus the framework.

import sqlite3
from datetime import datetime

class AgentMemory:
    def __init__(self, db_path="agent_memory.db"):
        self.conn = sqlite3.connect(db_path)
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS notes (
                id INTEGER PRIMARY KEY,
                topic TEXT NOT NULL,
                content TEXT NOT NULL,
                created_at TEXT,
                importance INTEGER DEFAULT 1
            )
        """)

    def save_note(self, topic, content, importance=1):
        self.conn.execute(
            "INSERT INTO notes (topic, content, created_at, importance) VALUES (?, ?, ?, ?)",
            (topic, content, datetime.utcnow().isoformat(), importance)
        )
        self.conn.commit()

    def recall(self, topic, limit=5):
        cursor = self.conn.execute(
            """SELECT content, created_at FROM notes
               WHERE topic = ? ORDER BY importance DESC, created_at DESC LIMIT ?""",
            (topic, limit)
        )
        return cursor.fetchall()

    def prune_stale(self, topic, keep_last=20):
        self.conn.execute("""
            DELETE FROM notes WHERE id NOT IN (
                SELECT id FROM notes WHERE topic = ?
                ORDER BY created_at DESC LIMIT ?
            ) AND topic = ?
        """, (topic, keep_last, topic))
        self.conn.commit()

Don't reach for a vector database on day one just because it sounds impressive. A SQLite table with a topic column and a decent retrieval query demonstrates you understand the underlying problem. If you later add embeddings for semantic recall, document why the upgrade was necessary — that comparison is a better interview talking point than the vector DB alone.

Project 3: A Multi-Agent Pipeline With Explicit Handoffs

Single agents plateau fast. The next tier of maturity is knowing when to split work across multiple specialized agents instead of asking one model to do everything with a giant prompt. Build a content or research pipeline with three agents: a planner that breaks a task into subtasks, a worker that executes each subtask using tools, and a reviewer that checks the worker's output against the original goal before it's returned to the user.

The engineering value here isn't the multi-agent buzzword — it's the handoff protocol. What exact data structure passes from planner to worker? What happens when the reviewer rejects output — does it go back to the worker with feedback, or escalate to a human? Design decisions like these are what separate "I used CrewAI" from "I understand orchestration."

def run_pipeline(goal, planner, worker, reviewer, max_revisions=2):
    plan = planner.create_plan(goal)
    results = []

    for subtask in plan.subtasks:
        revision = 0
        output = worker.execute(subtask)

        while revision < max_revisions:
            review = reviewer.check(subtask, output)
            if review.approved:
                break
            output = worker.execute(subtask, feedback=review.feedback)
            revision += 1

        results.append({
            "subtask": subtask.description,
            "output": output,
            "approved": review.approved,
            "revisions": revision
        })

    return results

Log every handoff to a file and include a sample trace in your README — planner output, worker attempts, reviewer feedback, final result. A reviewer skimming your repo should be able to follow the entire decision path for one real task in under two minutes.

Project 4: An Agent That Operates a Real Browser or Codebase

Tool-calling against clean APIs is one skill. Operating messy, real-world surfaces — a live website, a filesystem, a codebase — is a different and more marketable one, because it's exactly what computer-use and coding agents do in production. Pick one: a browser agent that fills out forms and extracts data from sites that weren't built for automation, or a coding agent that reads a repository, makes a scoped change, and runs the test suite to verify it didn't break anything.

The coding-agent version is more accessible if you don't want to manage browser infrastructure. Constrain the scope tightly: the agent can read files, propose a diff, apply it, and run one test command. Refuse to let it touch anything outside a designated project folder — sandboxing is itself a feature worth demonstrating, not an afterthought.

import subprocess
import os

ALLOWED_ROOT = os.path.abspath("./sandbox_project")

def safe_path(path):
    full = os.path.abspath(os.path.join(ALLOWED_ROOT, path))
    if not full.startswith(ALLOWED_ROOT):
        raise ValueError(f"Path escapes sandbox: {path}")
    return full

def apply_patch(file_path, new_content):
    target = safe_path(file_path)
    with open(target, "w") as f:
        f.write(new_content)

def run_tests():
    result = subprocess.run(
        ["python", "-m", "pytest", ALLOWED_ROOT, "-q"],
        capture_output=True, text=True, timeout=60
    )
    return {
        "passed": result.returncode == 0,
        "output": result.stdout[-2000:]
    }

def agent_edit_cycle(file_path, new_content):
    apply_patch(file_path, new_content)
    test_result = run_tests()
    if not test_result["passed"]:
        return {"status": "reverted", "reason": test_result["output"]}
    return {"status": "applied"}

This project is a strong signal because sandboxing, path traversal prevention, and test-gated changes are the exact concerns a hiring team has about letting an agent touch production code. Showing you thought about them unprompted moves you ahead of candidates who only demoed the "it works" case.

Project 5: A Cost and Latency Dashboard for Your Own Agent

Almost nobody builds this, which is exactly why it stands out. Take any agent from the projects above and instrument it: log every model call's token count, latency, and estimated cost to a simple database, then build a small dashboard showing spend over time, average latency per tool, and which tasks are the most expensive. This project answers a question every engineering manager actually has — can this person ship something that a business can afford to run?

import time
import sqlite3

class UsageTracker:
    def __init__(self, db_path="usage.db"):
        self.conn = sqlite3.connect(db_path)
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS calls (
                id INTEGER PRIMARY KEY,
                task_id TEXT,
                input_tokens INTEGER,
                output_tokens INTEGER,
                latency_ms INTEGER,
                cost_usd REAL,
                timestamp REAL
            )
        """)

    def log_call(self, task_id, input_tokens, output_tokens, latency_ms,
                 input_price_per_million=3.0, output_price_per_million=15.0):
        cost = (input_tokens / 1_000_000 * input_price_per_million +
                output_tokens / 1_000_000 * output_price_per_million)
        self.conn.execute(
            """INSERT INTO calls
               (task_id, input_tokens, output_tokens, latency_ms, cost_usd, timestamp)
               VALUES (?, ?, ?, ?, ?, ?)""",
            (task_id, input_tokens, output_tokens, latency_ms, cost, time.time())
        )
        self.conn.commit()

    def summary(self):
        cursor = self.conn.execute(
            "SELECT COUNT(*), SUM(cost_usd), AVG(latency_ms) FROM calls"
        )
        count, total_cost, avg_latency = cursor.fetchone()
        return {
            "total_calls": count,
            "total_cost_usd": round(total_cost or 0, 4),
            "avg_latency_ms": round(avg_latency or 0, 1)
        }

Wrap this around any earlier project and you get a genuinely different artifact: not just "an agent that works" but "an agent I can tell you the unit economics of." Bring the actual numbers to your interview. Even small ones — "this cost me $4.30 across 200 test runs, averaging 1.8 seconds per task" — read as more credible than any adjective you could use to describe your project.

Project 6: An Evaluation Harness, Not Just a Demo

The single biggest gap between hobbyist agent projects and professional ones is evaluation. Anyone can show an agent succeeding on the one example they tried. Almost nobody shows what happens across twenty or fifty varied inputs, including the ones designed to break it. Build a small eval harness: a set of test cases with expected behaviors, a runner that executes your agent against each one, and a scoring function that checks whether the output meets a bar you defined in advance.

test_cases = [
    {
        "id": "basic_lookup",
        "input": "What's the weather in Austin?",
        "check": lambda output: "austin" in output.lower() or "temperature" in output.lower()
    },
    {
        "id": "ambiguous_location",
        "input": "What's the weather there?",
        "check": lambda output: "clarify" in output.lower() or "which" in output.lower()
    },
    {
        "id": "tool_failure_recovery",
        "input": "Get weather for a fake city like Zzyxlopolis",
        "check": lambda output: "couldn't" in output.lower() or "not found" in output.lower()
    },
]

def run_eval(agent_fn, test_cases):
    results = []
    for case in test_cases:
        try:
            output = agent_fn(case["input"])
            passed = case["check"](output)
        except Exception as err:
            output, passed = str(err), False
        results.append({"id": case["id"], "passed": passed, "output": output})

    pass_rate = sum(r["passed"] for r in results) / len(results)
    return {"pass_rate": pass_rate, "results": results}

Include the ambiguous and adversarial cases deliberately — a location-less weather query, a nonexistent city, an instruction that contradicts an earlier one. These are the cases junior builders never test and senior engineers always do. When you can say "my agent passes 17 of 20 eval cases and here's the breakdown of what fails and why," you've made an argument about quality that a live demo alone cannot make.

How to Package All of This So Someone Actually Looks

Building the projects is half the work. The other half is making them legible to a hiring manager who will spend maybe four minutes on your GitHub before deciding whether to read further. Put a short, honest README at the top of each repo: what the project does, what it deliberately does not handle, and one paragraph on the hardest bug you hit and how you found it. Skip the marketing language — "leverages cutting-edge LLM orchestration" reads as filler, while "the agent kept looping when a tool returned an empty list instead of an error, so I added an explicit empty-result check" reads as engineering.

Record a two-minute screen capture for your one or two strongest projects. Interviewers rarely clone and run a repo before a screening call, but they will watch a short video. Show a failure case recovering gracefully, not just the happy path — that's the moment that actually differentiates you.

Finally, pick projects that form a narrative rather than a random pile. "I built a tool-calling agent, then added memory, then added evaluation" tells a story about how you think an agent should mature. Six disconnected demos tell no story at all, even if each one is individually fine.

Common Mistakes That Sink Otherwise Good Projects

The most frequent mistake is wrapping a single API call to a hosted model and calling it an agent. If there's no tool use, no multi-step reasoning, and no state, it's a chatbot with a nicer prompt — reviewers spot this instantly. The second is hardcoding API keys or secrets directly into the repo; use environment variables and a .env.example file, and mention in the README that you handled this deliberately.

The third mistake is skipping error handling entirely and only showing the case where everything works. The fourth is over-engineering the wrong layer — spinning up Kubernetes and a message queue for a demo that gets ten requests a day, while leaving the actual agent logic thin. Match your infrastructure to your problem size and be ready to explain that tradeoff out loud; "I kept this simple on purpose because the volume didn't justify a queue" is a stronger answer than a complex diagram nobody asked for.

The fifth, and most avoidable, is having zero tests. Even a handful of pytest cases around your tool functions and your retry logic signals you write code you intend to maintain, not code you intend to abandon after the demo.

Where to Go From Here

None of these six projects requires access to expensive infrastructure or a research team — they require the same core skills tested in production agent systems: reliable tool use, memory design, multi-agent coordination, sandboxing, cost awareness, and evaluation. Build two or three of them with real depth, document the failures you found and fixed, and you will have a portfolio that argues for itself before you say a word in an interview.

If you want a structured path through all of this instead of assembling it project by project, that's exactly what 30 Days of Hermes Agent is built for. The course walks through building a real agent from a single tool call up through memory, multi-agent handoffs, sandboxed code execution, and evaluation — the same progression laid out above, but with guided exercises, checkpoints, and code review baked into each day. If you've been meaning to build one of these projects but keep stalling at "where do I even start," that's the fastest way in.