# Week 8: Decision records and persistence

> The final project brief is out; then architecture decision records, and how a full-stack app keeps its data across restarts and schema changes

# Welcome

---

# Week 8

## Decision records and persistence

---

## what changed this week?

[Claude Sonnet 5.5](https://www.anthropic.com/claude-sonnet-5-5): claims up to
30% less per task through fewer tokens and tool calls

[Local models on strproxy](#/local-models): free for you (no quota)

[Course plugin](https://github.com/comp4020-agentic-coding-studio/core) update
(0.14.26): run `claude plugin update comp4020@comp4020`, then `/comp4020:doctor`

---

## local models on the course proxy

- **models**: `soco-fast` (quicker) and `soco-smart` (more capable), on School
  GPU servers
- **cost**: free, and outside your weekly budget
- **limits**: weaker than Claude on hard agentic work, and slow when busy

**in a session**: `/model soco-fast`, and `/model claude-sonnet-5` to switch
back (it's saved as your default, so do switch back)

**from launch**: `ANTHROPIC_MODEL=soco-fast` ([LLM access](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/llm-access/))

---

## today's beats

the final project brief

decision records

persistence: restarts, schemas and migrations

---

> make a multi-user, real-time website that's good
>
> — the [final project](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/assessments/final-project/) brief

---

## the final project

**fixed**: multi-user, real-time and persists (as per
[the brief](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/assessments/final-project/#the-brief))

**yours**: everything else, including the stack and (especially!) what "good"
means

**the repo**: a placeholder, a Fly app and your own `spec/`

**one repo**: from C8 in week 9 to submission on Monday 9 November

---

## more open than A1 and A2

A1 and A2 built constraints into the provocation; this one has none

the spec doesn't mention accounts, feeds, rooms or scores: those are other
developers' answers to questions you now answer yourself

small is fine: an app for twelve people, one street, one book club or one
afternoon

the crit agents build it too, so we'll see the median answer

---

"what should it do?"

"what would make it **good**?"

---

## the word "good"

**what it does**: you can't say what good means for an app without saying who
it's for and what it does

**how well**: an app can have every feature on the list and still not be worth
using

**the median**: ask an agent for "a multi-user, real-time website" and you get
the most common one; deciding what good means is up to you

**the harness**: you write good down in `README.md` (the argument), `CLAUDE.md`
(the rules) and `spec/` (the checks), and the project is marked on whether the
three are consistent

---

## "is it any good?"

a friend asks "what'd you make?", and you say "a site where my book club argues
about the chapter we're up to"

then they ask "is it any good?"

your answer is about those people and that use: they actually come back, nobody
gets a spoiler, and last week's argument is still there

that answer is the "good" we mean, not a universal theory of the good

---

## assessment in the age of Claude

what value do **you** add beyond pasting the brief straight into Claude Code?

---

## good, crit by crit

**C8**: a first answer to "is it any good?" in `README.md`, rough and meant to
change

**C9**: one decision about several people at once, made using that definition

**C10**: logs of what people actually do, as evidence for or against it

**submission**: the definition, the rules and the checks are consistent, and the
app meets them

---

## stack constraints

**one machine**: a small [Fly machine](https://fly.io/docs/machines/), with one
[volume](https://fly.io/docs/volumes/overview/) for anything that persists
(what's fixed is set in
[`fly.toml`](https://fly.io/docs/reference/configuration/) and the
[`Dockerfile`](https://fly.io/docs/languages-and-frameworks/dockerfile/))

**persists**: across restarts and redeploys (later today)

**real-time**: a change reaches every open session within about a second (next
week)

**your README**: whatever you promise there, the stack has to make possible

---

## choosing a stack

**what good needs**: start from what you promised in the README

**what you can read**: you review and debug the agent's code, so pick code you
can follow

**what's in the training data**: models write better code for widely used,
well-documented stacks

**how a change reaches everyone**: some frameworks build real-time in (Phoenix
LiveView, next week); others leave it to you

it's a first choice: if it stops fitting, switch, and write a record saying why

---

ask me about the **final project**

---

# decision records

---

## an architecture decision record

> a short document that captures and explains a single decision relevant to a
> product or ecosystem

([Martin Fowler](https://martinfowler.com/bliki/ArchitectureDecisionRecord.html), 2026)

the format is
[Michael Nygard's](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions)
(2011): **title**, **status**, **context**, **decision**, **consequences**

---

# **words**; what do they even mean?

---

## one, in full

```text
# 0001. A git-based CMS for non-technical editors

Status: deprecated (the theme dropped the CMS)
Context: staff who don't use git need to edit pages;
  content is MDX in the repo, on self-hosted GitLab
Options: Sveltia (in the browser, commits to GitLab);
  TinaCMS (GitHub only, needs a server);
  Payload (a database, replacing the MDX files)
Decision: Sveltia; content stays as files in git
Consequences: editors need GitLab accounts; the CMS
  schema is a second copy, kept in step by hand
```

(from a [Backlog.md](https://github.com/MrLesk/Backlog.md) task for the theme
this site is built on)

---

## record keeping

**one file each**, numbered: `doc/adr/0001-htmx-for-active-web-pages.md`

**status**: proposed, then accepted

**a changed mind** is a new record that supersedes the old one; an accepted
record is never edited

**in the repo** it applies to, so it's versioned with the code it's about

(Fowler; [adr.github.io](https://adr.github.io/) collects templates and tools)

---

## with an agent

> lay out three options for where this app stores X, and what each costs. Don't
> pick one yet

then in `CLAUDE.md`, so the agent reads the records in every new session:

```text
Decisions live in doc/adr/, one numbered file each.
Read them before changing the stack, storage or data
model. To change one, write a record superseding it.
```

---

## choosing a stack, with an agent

> read my README, then quiz me one question at a time about what this app needs:
> what counts as a person, what has to persist, and how a change reaches
> everyone else's open session. Give each option's pros and cons. Don't
> recommend a stack until I've answered

then, once you've chosen:

> write the decision record for the stack I picked, with the options we ruled
> out and why

---

## record-worthy decisions

decisions that are **expensive to reverse**, or that someone will **ask about**
later

in the final project: the stack, what counts as a person, what persists, how a
change reaches every open session

not every library pick/UI tweak

---

## further reading

[Nygard (2011)](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions):
the original post and format

[Fowler (2026)](https://martinfowler.com/bliki/ArchitectureDecisionRecord.html):
status, superseding and where records live

[adr.github.io](https://adr.github.io/): templates (MADR, Y-statements) and
tooling

[joelparkerhenderson/architecture-decision-record](https://github.com/joelparkerhenderson/architecture-decision-record):
many worked examples

---

## Ben uses… Backlog.md

[Backlog.md](https://github.com/MrLesk/Backlog.md): tasks as markdown files in
`backlog/`, committed with the code

yaml frontmatter + free-form prose

the built-in `backlog decision create` writes a standalone record for a decision
too big for its task

(the [task tracking](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/task-tracking/) topic compares it with the
alternatives)

---

# Web

---

## persistence, in Django

the worked instance today is [Django](https://www.djangoproject.com/): python's
most famous web framework

next week's is [Phoenix LiveView](https://www.phoenixframework.org/) (similar,
but in Elixir): the ideas carry across stacks, and your final project's stack is
your call

---

## schema constraints

```python
class Room(models.Model):
    name = models.CharField(max_length=40, unique=True)
class Message(models.Model):
    room = models.ForeignKey(Room, on_delete=models.CASCADE)
```

**`unique=True`**: no two rooms share a name; **no `null=True`**: every message
has a room; **`ForeignKey`**: its room exists

```text
IntegrityError: UNIQUE constraint failed: chat_room.name
IntegrityError: FOREIGN KEY constraint failed
```

the database raises these, whatever code the agent wrote

---

## a migration is a recorded step

add a `topic` field to `Message`, run `makemigrations`, and Django writes
`0002_message_topic.py`:

```python
dependencies = [('chat', '0001_initial')]
operations = [migrations.AddField(model_name='message',
    name='topic',
    field=models.CharField(default='general', max_length=40))]
```

the guestbook had the same migration in week 7, generated by Drizzle:

```sql
ALTER TABLE `messages` ADD `topic` text DEFAULT 'general' NOT NULL;
```

---

## migrations on Fly

**at boot**: with SQLite on a volume, the server runs its migrations as it
starts, since the volume is only on that machine

**not `release_command`**: Fly runs that on a temporary machine without your
volume

**the same everywhere**: Rails (Active Record), Phoenix (Ecto) and Drizzle all
use a schema and a migration history ([persistence](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/persistence/))

---

## further reading

[The Twelve-Factor App: processes](https://12factor.net/processes) and
[backing services](https://12factor.net/backing-services): why the server keeps
no state, and the database lives outside the code

[Fowler and Sadalage, "Evolutionary Database Design"](https://martinfowler.com/articles/evodb.html):
migrations as small, versioned steps committed with the code

[Django's migrations guide](https://docs.djangoproject.com/en/5.2/topics/migrations/):
the worked instance, including what to do when two branches both add one

---

## C8: it's alive

ship the first working version of your final project, and a first go at saying
what good means for it

live at its `*.fly.dev` URL, where a stranger can do the core thing and find
their trace still there when they come back

a first `README.md`, published at `/readme/`, with what you read to get there

the repo goes public at the cutoff and stays public

---

## week 9: Monday groups

Monday 5 October is a public holiday, so neither Monday crit runs in its usual
slot

**Shítāo**: Tuesday 6 October, 14:00–15:30, usual room

**Bādà**: Wednesday 7 October, 15:30–17:00, **Room 3.05**

your C8 cutoff moves with your session ([crits](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/crits/))

---

## deadline radar

**this week**: deploy the placeholder by hand with your Fly token, before you've
chosen anything else

**your week 9 session**: C8 live two hours before it, with `README.md` at
`/readme/` and `reflections/crit-8.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

final project: you decide what makes your app good, and what it does follows

a decision record: one expensive choice and its options, in the repo for later
sessions

only the database on the volume survives a restart; migrations change its schema
one recorded step at a time

---

## ask your agent

ask until you can explain these back:

- **the stack**: three stacks that fit my README's idea of good, and what each
  costs on one small Fly machine
- **a record**: a decision record for the one I pick
- **the schema**: what does my schema make impossible, and what should it?
- **migrations**: how does my framework run migrations on deploy, and where does
  that happen on Fly?

---

# See you in the studio
