A small Python REST API plus a vanilla-JavaScript frontend, used as the shared sample app for the Claude Code workshop (see claude-code-workshop-program.md in the agent-dev-container repo). The intent is a codebase small enough that attendees can read it during a coffee break, but real enough to host plan-mode demos, slash-command walkthroughs, the TDD loop, and the hackathon tracks.
The app:
- Backend: FastAPI service exposing
/api/users(list, create, fetch, delete) backed by an in-memory store. - Frontend: a single static HTML page with vanilla JavaScript, served from the same FastAPI process at
/. - Tests:
pytestcovering the happy path of the API.
Do not deploy this app, and do not lift code or patterns from it into projects you care about. The workshop demos depend on specific holes being present: missing input validation, a silent-delete bug, no duplicate-email check, unescaped HTML rendering in the frontend, and an in-memory store with no persistence, no authentication, and no authorisation. The "Intentional gaps and bugs" section below catalogues them. Anything built out of this app for the workshop is throwaway by design.
The sample app is a minimal user directory. A user has an integer id, a name, and an email. The state lives in a process-local dictionary; restarting the server wipes it. There is no database, no authentication, no session handling, no email sending. The point is to give attendees a piece of working software with a recognisable shape (request, validation, store, response) and just enough surface area for the workshop's plan-mode, slash-command, and TDD demos to land on something real, without dragging in infrastructure that would distract from the agent workflow.
The HTTP surface is four endpoints:
| Method | Path | Behaviour |
|---|---|---|
| GET | /api/users |
Returns the full list of users. |
| POST | /api/users |
Creates a user from the JSON body. Returns the created record with its assigned id. |
| GET | /api/users/{id} |
Returns the user with that id, or 404 if none. |
| DELETE | /api/users/{id} |
Deletes the user with that id. |
The frontend at / is a single HTML page: a form to add a user, a table of existing users, and a delete button per row. It calls the same /api/users endpoints with fetch. No build step, no framework, no bundler; the file you read is the file the browser runs.
FastAPI's interactive Swagger UI at /docs and the OpenAPI schema at /openapi.json come for free. They are useful for demos where Claude needs to discover the API surface without reading the source.
Open this folder inside a container built from agent-dev-container/.devcontainer/ (the workshop image already has Python 3.11+, pip, gh, git-delta, and the Claude Code CLI). From the project root:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000Then open:
- http://localhost:8000/ for the frontend
- http://localhost:8000/docs for the auto-generated Swagger UI
- http://localhost:8000/api/users to hit the API directly
VS Code forwards port 8000 from the container to the host automatically.
Run the tests:
pytestThe slash commands, hooks, and statusline used in the workshop are not in this repo. To install them, run inside the dev container:
install-kit-hereThat drops CLAUDE.md, PROCESS.md, and .claude/ into this directory from the image's read-only master copy at /opt/agent-dev-container/. Restart Claude Code so the hooks, statusline, and /dyalog:bugfix / /dyalog:crev commands load.
The codebase ships with known holes so workshop demos have something to fix. None of them are subtle; demonstrators can predict what attendees will find.
- No request validation on
POST /api/users. The endpoint accepts an arbitrary JSON body and stores it verbatim. There is no email check, no name check, no rejection of empty bodies. The matching workshop hands-on is "add input validation to the /users POST endpoint", run once in default mode and once in plan mode. DELETE /api/users/{id}silently succeeds for unknown IDs. It returns 204 whether the user existed or not. The correct behaviour is 404 for an unknown ID. Suitable target for a/dyalog:bugfixdemo.- No duplicate-email rejection. Two users with the same email can be created. Suitable bug-bash hackathon target.
- The frontend assigns user data straight into
innerHTML. Any name or email containing HTML is reflected unescaped. Suitable safety-challenge or feature hackathon target. - No pagination or filter on
GET /api/users. The endpoint returns the full list regardless of size. Suitable new-feature hackathon target.
The workshop programme refers throughout to "the workshop sample app". This is that app. The specific touch points:
| Session | What to demo against this app |
|---|---|
| 09:45 to 10:15 (permission modes) | "Add input validation to the /users POST endpoint", once in default mode, once in plan mode. Show the difference in output structure. |
| 10:30 to 11:00 (CLAUDE.md) | Add a project convention here (for example: "all endpoints must validate input with Pydantic models, no raw dicts"), then ask Claude to extend an endpoint. Observe whether the convention is followed. |
| 11:00 to 11:30 (slash commands & skills) | Open a GitHub issue for the silent-delete bug or the duplicate-email bug, then run /dyalog:bugfix <issue-number>. The command writes docs/bugs/<id>.md with the verified reproducer and RCA. |
| 13:45 to 14:30 (planning) | Use plan mode for one of the feature gaps (pagination, email-uniqueness check, XSS-safe rendering). Save to docs/plans/<slug>.md. Run /dyalog:crev docs/plans/<slug>.md for the adversarial review. |
| 14:45 to 15:45 (TDD loop) | Pick one child issue from the plan. Drive "Proceed → /dyalog:crev N → Proceed → /dyalog:crev N → PR" against this app. |
| 16:15 to 17:30 (hackathon) | Bug bash (silent delete, duplicate emails, XSS), new feature (pagination, search, a /api/messages endpoint), safety challenge (try to make Claude rewrite hook scripts or exfiltrate the in-memory store; the kit's PreToolUse hooks should stop it). |
agent-workshop/
├── README.md
├── requirements.txt
├── .gitignore
├── app/
│ ├── __init__.py
│ ├── main.py FastAPI app: routes, app instance
│ └── store.py in-memory user store
├── static/
│ ├── index.html single-page UI
│ ├── app.js fetch calls, list/create/delete handlers
│ └── style.css
└── tests/
├── __init__.py
├── conftest.py autouse fixture: resets the store between tests
└── test_users.py
The app has no container-specific dependencies. On any host with Python 3.11+ and pip, the steps above (venv, install, uvicorn) work unchanged. The kit (slash commands, hooks) requires jq, git, and gh on PATH; see the agent-dev-container repo's top-level README for the standalone-install path.