Welcome#
Week 10#
Logs, harnesses and multi-user apps#
what changed this week?#
dynamic workflows came to Claude Managed Agents (Anthropic, 9 October): Claude Code’s workflows, moved to the API
a lead agent writes a program that runs many agents in phases, on Anthropic’s servers (“flows”, as the presenters called them at AI Engineer New York)
today’s beats#
a demo: one symptom, two harnesses, the same logs
logs, and the harness around the model
multi-user apps: sessions, authorisation, two people at once
demo: same model, same logs — what could be causing this?#
the class noticed: [their call]. Read the app’s recent logs with
mise exec -- flyctl logs -a lecture-demos --no-tailand tell us what could be causing it: your two likeliest explanations, and the evidence that would separate them. Change no code.
logging#
a log#
Logs are the stream of aggregated, time-ordered events collected from the output streams of all running processes and backing services.
(The Twelve-Factor App, “XI. Logs”)
the app writes to stdout; the platform collects it (on Fly, fly logs)
reading a request log#
[287967] GET /rooms/1 200 8ms user=vQwIQ2r1
[01e7ba] card_created user=vQwIQ2r1 room=1 card=1
[01e7ba] POST /rooms/1?/add 200 11ms user=vQwIQ2r1the tag is the request id: it ties each request to the events inside it
SvelteKit writes no request log of its own; the board’s is a
handle hook, and each event is one
line:
logEvent(event, "card_created", { room: roomId, card: added.id });where the logs are#
what to log#
each event that matters: who, what, when, one line each
each error: enough context to reproduce it
never: secrets, tokens, or anything a user wouldn’t want in a log file
when something breaks: paste the lines around the failure into the agent, not a description of the symptom
further reading#
The Twelve-Factor App, XI. Logs: logs as an event stream, and why the app shouldn’t manage log files
harnesses#
a harness#
Claude Code is the layer around the model that provides the tools and manages the context the model sees. This surrounding layer is what the term agentic harness refers to.
(Claude Code docs, “How Claude Code works”)
the course says “harness” for your CLAUDE.md and checks: the outer harness you build on top (Böckeler, 2026)
words; what do they even mean?#
Claude Code and pi, before your first prompt#
(fresh installs, same model: Claude Code 2.1.296, pi 1.1.0)
where they differ#
| feature | Claude Code | pi |
|---|---|---|
| permissions | asks, or a mode decides | no prompt per tool call; use a container |
| long-running commands | backgrounded; output read as it arrives | bash waits until it exits; background it to a file, or use tmux |
| plan mode, subagents | built in | example extensions, or pi -p from bash |
| MCP | yes | yes, since 1.0 |
| the session | JSONL; /branch forks a copy | a JSONL tree; /tree branches in place |
how much the harness builds in#
the harness’s own log#
Claude Code: ~/.claude/projects/<dir>/<session>.jsonl
pi: ~/.pi/agent/sessions/--<dir>--/<time>_<id>.jsonl
plain JSONL, so any file tool reads them (your agent too). The tool calls:
# Claude Code
jq -r 'select(.type=="assistant") | .message.content[]?
| select(.type=="tool_use") | .name' <session>.jsonl
# pi
jq -r 'select(.type=="message") | .message.content[]?
| select(.type=="toolCall") | .name' <session>.jsonlwhat carries over#
CLAUDE.md: Claude Code, pi and opencode all read it (and Claude Code
now falls back to AGENTS.md)
skills: the same SKILL.md format; pi looks in ~/.pi/agent/skills/ and
~/.agents/skills/
hooks, permission rules, plugins: Claude Code’s own; they don’t carry
course credits: any harness that speaks the Anthropic API (other tools); Claude Code is the supported path, the rest is going off piste
pi (using your course key)#
install: mise use -g npm:@earendil-works/pi-coding-agent
configure: models.json and settings.json in ~/.pi/agent/, both in full
on the course-credits page
you can’t run pi on a Claude sub
subagents and workflows#
(Claude Code docs, “When to use a workflow”)
dynamic workflows#
A dynamic workflow is a JavaScript script that orchestrates many subagents at once. Claude writes the script for the task you describe, and a runtime executes it in the background while your session stays responsive.
start one: ultracode in a prompt; /workflows to watch it
keep one: saved to .claude/workflows/, it runs as /<name>
a workflow run#
a workflow script#
export const meta = { name: "audit-routes", description: "…" };
const found = await agent("List every .ts file under src/routes/.", {
schema: FILES,
});
const audits = await pipeline(found.files, (file) =>
agent(`Audit ${file} for missing auth checks.`, { label: file }),
);
return audits.filter(Boolean);the script only starts agents and combines what they return; each agent() call
is a subagent with its own tools
(docs)
orchestration in Claude Code and pi#
| feature | Claude Code | pi |
|---|---|---|
| subagents | Agent tool, built in | example extension: each child is a separate pi process |
| workflows | Workflow tool and its runtime, built in | not in core; community extensions run Claude Code’s scripts |
| scripts over tools | only through agents | codemode runs JavaScript over tools, but not over chat models |
| concurrency, resume | Anthropic’s defaults: 16 at once, 1,000 per run | whatever the extension does |
evals#
an eval runs the same tasks through each harness and grades the results; public benchmarks (Terminal-Bench) use their tasks, not yours
replay your history: take a few past tasks from your git log, reset to the parent commit, give each harness the same prompt, grade with the checks you already have
start from failures: each time an agent gets something wrong, save the prompt and the check that caught it
Ben uses… vibes#
mostly no evals: Ben judges a harness or model by how it goes on real work
further reading#
Claude Code docs, how Claude Code works: the loop, the tools, the context window and where sessions are stored
Zechner (2025): why pi is four tools and a short prompt (some of it, like no MCP, has since changed)
pi docs: configuration, sessions and the session format
Böckeler (2026), harness engineering: the harness you build around a coding agent
Claude Code docs, dynamic workflows: subagents, agent teams and workflows compared, and the script API
Ben uses… a weekly retrospective#
an agent reads the week’s session logs and proposes a rule for each correction Ben repeated; one from 4 October:
A global CLAUDE.md line: to hand Ben text he will send or paste, … pipe it to
clipboard-copy
course usage so far#
strproxy logs every request your harness makes (we last looked in week 4)
(totals and anonymous rankings — no names anywhere)
yes, and…#
how does it get pushed further? weird ideas welcome
Web#
multi-user, in SvelteKit#
the worked instance today is the board in lecture-demos: SvelteKit, with better-auth for sign-in and Drizzle over SQLite
Django (week 8) and Phoenix (week 9) come with these parts built in; here you choose them, as your agent will for your final project
a multi-user app#
two or more people, each in their own browser, act on shared state, and the app tells them apart
(the final project’s wording)
markers test it: two Chrome profiles, so no shared cookies or storage
three things follow: telling people apart, deciding what each may do, and handling two of them at once
telling people apart#
sign-in sets a cookie holding a token (MDN):
__Secure-better-auth.session_token=EAzs…; HttpOnly; SameSite=Laxeach request, a handle hook looks the token up in the session table:
const session = await auth.api.getSession({
headers: event.request.headers,
});
if (session) event.locals.user = session.user;the session is a database row, so it survives a restart
a session, request by request#
authorisation#
authentication says who; authorisation says what they may change, checked on the server for every action
const { changes } = db
.delete(card)
.where(and(eq(card.id, cardId), eq(card.userId, me.id)))
.run();
if (changes === 0) error(404, "No such card");Bo, deleting Ada’s card:
[2c9315] POST /rooms/1?/delete 404 3ms user=IES9bBIpauthentication, then authorisation#
two people at once#
| time | Ada’s browser | Bo’s browser |
|---|---|---|
| 11:02:01 | opens card 1: “buy milk” | |
| 11:02:05 | opens card 1: “buy milk” | |
| 11:02:20 | saves “buy oat milk” | |
| 11:02:31 | saves “buy milk and bread” |
card 1 now reads “buy milk and bread”: Ada’s edit is gone (a lost update), and the log shows two clean saves
[37a745] card_edited user=vQwIQ2r1 room=1 card=1
[e029e7] card_edited user=IES9bBIp room=1 card=1race conditions#
what should happen when two people save the same thing depends on what your app is for: last write wins, a version check, a lock, or merging the edits
talk it through with your agent, then have it write a test where two users save at once
further reading#
MDN, Using HTTP cookies and the OWASP Session Management Cheat Sheet: sessions, and how they go wrong
better-auth, session management: the cookie, the table and expiry
SvelteKit docs, form actions: the board’s writes, and the CSRF check on every form POST
SQLite, transactions: one writer, and what a transaction does and doesn’t cover
yes, and…#
how does it get pushed further? weird ideas welcome
next week: a guest lecture#
Tom Griffiths, one of your tutors, spent 15 years as a product manager in tech companies small and not so small
Claude Code in industry: how it’s really used, and how to thrive when you walk into a job where everyone has it
no lecture in week 12#
Thurs 29 Oct, 11am: an (optional) panel instead, Scientific Research in the Age of Gen AI, in Hanna Neumann 1.33
(nothing to do with this course’s assessment; its focus is academic research, not agentic coding)
register here only if you’re going: the room holds about 70
Fri 30 Oct, 1pm: a final-project drop-in, in our lecture room (Fulton Muir 2.02)
how A3’s requirements are tested#
multi-user: two Chrome profiles (incognito counts), so no shared cookies
real-time: both on screen at once, one per marking viewport
persists: back the next day, after your machine has restarted
(see how it’s marked)
C10: fly by instruments#
make your final project tell people apart, decide on the server what each may see and change, and write down why
at the crit two of your pod use it from the URL alone, in separate Chrome profiles
you don’t touch it: you narrate from your logs who did what
deadline radar#
your week 11 session: C10 live two hours before it, with
reflections/crit-10.md in the repo
noon Monday 9 November: the final project is due
12–2pm Wednesday 11 November: the final project showcase in the Birch building (optional)
tl;dr#
logs help only when a command the agent can run prints them
a harness is everything around the model, spawning other agents included; the same model works differently inside Claude Code and pi
a multi-user app tells people apart with a session, checks every action on the server, and decides what happens when two people save at once
ask your agent#
ask until you can explain these back:
- logging: what should my app log for each user action, without secrets or personal data?
- harnesses: what’s in your system prompt and tools, and what would differ in pi?
- sessions: how does my stack tell two people apart, after a restart too?
- conflicts: what happens when two people save the same thing at once?