# Week 10: Logs, harnesses and multi-user apps

> What a deployed app should log, and how two agent harnesses — Claude Code and pi — read the same logs; then what a multi-user app has to handle — telling people apart, what each may do, and two of them acting at once

# Welcome

---

# Week 10

## Logs, harnesses and multi-user apps

---

## what changed this week?

[dynamic workflows](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration)
came to Claude Managed Agents (Anthropic, 9 October): Claude Code's
[workflows](#/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](https://www.latent.space/p/claude-dynamic-workflows))

---

## 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

---

(qr: <https://lecture-demos.fly.dev>)

---

## 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-tail` and 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](https://12factor.net/logs), "XI. Logs")

the app writes to `stdout`; the platform collects it (on Fly, `fly logs`)

---

## reading a request log

```text
[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=vQwIQ2r1
```

the 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](https://svelte.dev/docs/kit/hooks#handle), and each event is one
line:

```ts
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](https://12factor.net/logs): logs as an event
stream, and why the app shouldn't manage log files

[Fly.io, logging overview](https://fly.io/docs/monitoring/logging-overview/)

[SvelteKit docs, hooks](https://svelte.dev/docs/kit/hooks)

---

# 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](https://code.claude.com/docs/en/how-claude-code-works), "How
Claude Code works")

the course says "harness" for your CLAUDE.md and checks: the
[outer harness](https://martinfowler.com/articles/harness-engineering.html) 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:

```sh
# 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>.jsonl
```

---

## what carries over

**`CLAUDE.md`**: Claude Code, pi and opencode all read it (and Claude Code
[now falls back to `AGENTS.md`](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#21277))

**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](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/llm-access/#other-tools)); Claude Code is the supported
path, the rest is
[going off piste](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/software-and-platforms/#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](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/llm-access/#pi)

---

you can't run pi on a Claude **sub**

---

## spawning agents

a [subagent](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/lectures/week-9/#/subagent-context) is a tool call: the harness
starts a second loop with a fresh context and returns its result as the tool's
output ([last week](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/lectures/week-9/))

**Claude Code**: the `Agent` tool, built in

**pi**: no such tool in core; add one as an extension, or run `pi -p` from bash

---

## subagents and workflows

([Claude Code docs](https://code.claude.com/docs/en/workflows), "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.

([Claude Code docs](https://code.claude.com/docs/en/workflows))

**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

```js
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](https://code.claude.com/docs/en/workflows#what-the-saved-script-looks-like))

---

## orchestration in Claude Code and pi

| feature                 | Claude Code                                     | pi                                                                                                                                                            |
| ----------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **subagents**           | `Agent` tool, built in                          | [example extension](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/examples/extensions/subagent): each child is a separate `pi` process |
| **workflows**           | `Workflow` tool and its runtime, built in       | not in core; [community extensions](https://github.com/tintinweb/pi-subagents) run Claude Code's scripts                                                      |
| **scripts over tools**  | only through agents                             | [`codemode`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/codemode.md) 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](https://www.tbench.ai/)) 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

([Anthropic, demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents))

---

## 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](https://code.claude.com/docs/en/how-claude-code-works):
the loop, the tools, the context window and where sessions are stored

[Zechner (2025)](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/): why
pi is four tools and a short prompt (some of it, like no MCP, has since changed)

[pi docs](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/docs):
configuration, sessions and the session format

[Böckeler (2026)](https://martinfowler.com/articles/harness-engineering.html),
harness engineering: the harness you build around a coding agent

[Claude Code docs, dynamic workflows](https://code.claude.com/docs/en/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](https://github.com/comp4020-agentic-coding-studio/lecture-demos):
[SvelteKit](https://svelte.dev/docs/kit), with
[better-auth](https://www.better-auth.com/) for sign-in and
[Drizzle](https://orm.drizzle.team/) 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](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/assessments/final-project/) 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies)):

```text
__Secure-better-auth.session_token=EAzs…; HttpOnly; SameSite=Lax
```

each request, a `handle` hook looks the token up in the `session` table:

```ts
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](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/persistence/#what-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

```ts
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:

```text
[2c9315] POST /rooms/1?/delete 404 3ms user=IES9bBIp
```

---

## authentication, 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

```text
[37a745] card_edited user=vQwIQ2r1 room=1 card=1
[e029e7] card_edited user=IES9bBIp room=1 card=1
```

---

## race 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies)
and the
[OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html):
sessions, and how they go wrong

[better-auth, session management](https://www.better-auth.com/docs/concepts/session-management):
the cookie, the table and expiry

[SvelteKit docs, form actions](https://svelte.dev/docs/kit/form-actions): the
board's writes, and the CSRF check on every form POST

[SQLite, transactions](https://www.sqlite.org/lang_transaction.html): 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](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/people/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](https://events.humanitix.com/gen-ai-pannel-2026) 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](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/assessments/final-project/#how-its-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?

---

# See you in the studio
