A workshop reference codebase for the Hands-On AI Agent Engineering workshop.
This is a small Kotlin/Spring Boot application that uses the Embabel agent framework to search documents and produce research digests. It is intentionally messy — the code contains the kind of real-world inconsistencies that accumulate over time in any active project. Your job during the workshop is to discover those inconsistencies, extract conventions from them, and build a governance harness that prevents future drift.
If you cannot run the workshop exercises on your own codebase (restricted access, not enough history, wrong language), use this repo instead.
It is a stand-alone project. You do not need a real OpenAI API key to read the code and do the convention-extraction and harness exercises.
git clone https://github.com/russmiles/research-digest-agent.git
cd research-digest-agentYou do not need to build or run the application during the exercises. All the interesting material is in the source tree.
src/main/kotlin/com/workshop/agent/
├── AgentApplication.kt Spring Boot entry point
├── ResearchDigestAgent.kt The @Agent — top-level entry point
├── actions/
│ ├── SearchDocuments.kt Finds relevant documents
│ ├── ExtractKeyPoints.kt Extracts key points from a document set
│ └── GenerateDigest.kt Produces the final research digest
├── config/
│ └── AgentConfiguration.kt Spring @Configuration — PromptConfig lives here
├── model/
│ ├── ResearchTopic.kt Domain models: ResearchTopic, Document, DocumentSet
│ ├── DigestResult.kt New sealed class for success/failure (canonical)
│ └── DigestResponse.kt Legacy flat model — being removed (see issue #47)
└── processing/
└── DocumentProcessor.kt Document pre-processing utilities
The codebase has four classes of inconsistency that mirror real-world migration debt. You will surface these during the exercises.
| # | Area | Pattern in use | Target pattern |
|---|---|---|---|
| A | Error handling | Result<T> (canonical), nullable returns, RuntimeException |
Result<T> everywhere |
| B | Response model | DigestResponse (legacy) alongside DigestResult (new) |
DigestResult everywhere |
| C | Prompt management | Inline hardcoded prompt strings in two action classes | All prompts in PromptConfig |
| D | Entry point naming | execute(), process(), run() across action classes |
execute() everywhere |
Each git tag marks the state of the repo at the end of an exercise.
| Tag | State |
|---|---|
start |
Clean starting state — just the source code |
after-ex3 |
CLAUDE.md / copilot-instructions.md added after Exercise 3 |
after-ex4 |
.claude/HARNESS.md and .claude/settings.json added after Exercise 4 |
after-ex5 |
CI harness job wired after Exercise 5 |
To reset to the start of any exercise:
git checkout <tag>To see the worked example for an exercise, check out the next tag and compare:
git diff after-ex3..after-ex4reference/ contains completed artefacts you can compare your output against:
| File | Produced by |
|---|---|
reference/CLAUDE.md |
Convention extraction (Exercise 3) |
reference/copilot-instructions.md |
Same content, Copilot format |
reference/HARNESS.md |
Harness construction (Exercise 4 + 5) |
These are not the only correct answers — your extraction interview will surface conventions specific to your own team and project. Use them as a calibration reference, not a copy-paste target.
./gradlew testThe test suite covers DocumentProcessor. The action classes are integration-tested
against a live LLM in CI; for the workshop you do not need them to pass.