Welcome#
Week 9#
Subagents and real-time#
what changed this week?#
Sharing AI progress in mathematics (OpenAI, 6 October): 722 manuscripts on open problems from an unreleased model, with Lean proofs, on GitHub
The Mathocalypse (Scott Aaronson)
On OpenAI’s release of mathematical results (the IAS Advisory Group on Mathematics and AI)
today’s beats#
a demo: one task, two subagents
subagents
real-time updates: polling, SSE, WebSockets and pub/sub
demo: one task, two subagents, two worktrees: which would you keep?#
the class wants: [their call]. Use two subagents in parallel, each in its own worktree, to build it two genuinely different ways (you pick the two). Each one runs
mix setupin its worktree, starts the app in the background withPORT=4001 mix phx.server(4002 for the second), commits on its own branch, and reports back its branch, its port and three lines on how its approach works and what it costs. Merge neither.
subagents#
a subagent#
Subagents are specialized AI assistants that handle specific types of tasks. Use one when a side task would flood your main conversation with search results, logs, or file contents you won’t reference again: the subagent does that work in its own context and returns only the summary.
(Claude Code docs, “Create custom subagents”)
words; what do they even mean?#
subagent mechanics#
delegation: the main session writes a task message; the subagent sees that, and none of your conversation
its own loop: a fresh context window, its own system prompt, tools,
permissions and model (model: haiku for a cheap search)
the return: only its final summary lands in your main context
choosing one: Claude matches the task against each subagent’s description,
or you name one (“use a subagent to…”)
the bill: its tokens count toward the same limits as your session
defining a subagent#
---
name: code-reviewer
description: Reviews code for quality and best practices
tools: Read, Glob, Grep
model: sonnet
---
You are a code reviewer. Give specific, actionable feedback.where: .claude/agents/ (this project) or ~/.claude/agents/ (all)
built in: Explore and Plan (read-only), general-purpose (every tool)
ad hoc: no file, just ask (“use a subagent to…”)
an Explore run, on Haiku#
find how a new message on the board reaches every other open tab
1. Save: board_live.ex:24, handle_event("save", ...)
2. Insert: board.ex:27, Repo.insert()
3. Broadcast: board.ex:28, Phoenix.PubSub.broadcast(...)
4. Every tab: board_live.ex:36, handle_info/2 calls stream_insert()5 tool calls and 22k tokens in its context; four lines in yours
cost: 4.4¢ on Haiku 4.5 (about 16¢ on Opus 5.5)
two context windows#
Ben uses… ad-hoc subagents#
mostly for parallelism, asked for in the prompt rather than defined in a file
Fanning work out to subagents is encouraged for complex tasks
(from the
global CLAUDE.md)
worktrees#
two subagents editing one checkout overwrite each other; a git worktree gives each its own checkout, on its own branch
for a subagent, one line of frontmatter:
isolation: worktreefor a whole session, in a second terminal:
claude --worktree spike-polling(Claude Code docs, “Run parallel sessions with worktrees”)
a worktree is just a folder#
a second checkout of the same repo, in its own folder, on its own branch
git worktree add ../board-spike -b spike makes one by hand; the frontmatter
line and --worktree do it for you
reading: run several at once
writing: one worktree each
subagent costs#
tokens: Anthropic’s multi-agent research system used about 15× the tokens of a chat (one agent: about 4×)
context: the subagent knows only what the delegation message tells it, so a rule from your conversation has to be restated
split writes: in Cognition’s (2025) thought experiment, two subagents building halves of a Flappy Bird clone return a Super Mario background and a bird that doesn’t match it
when to use a subagent#
reads: search (inc. scraping), docs, logs, review: the output is long and you only need the conclusion
parallel reads: independent questions, explored at once and summarised
writes: keep them in one thread; “writes stay single-threaded and the additional agents contribute intelligence rather than actions” (Cognition, 2026)
a review: a fresh context reviews better than one that wrote the code
further reading#
Claude Code docs: subagents: every frontmatter field, the built-ins, foreground and background
Anthropic (2025): a lead agent with parallel search subagents; better research, at about 15× the tokens
Cognition (2026), makers of the Devin coding agent: ten months after their “don’t build multi-agents” post, read-only subagents and review loops work; parallel writers still don’t
Ben uses… different models#
default subagents to sonnet (haiku for purely mechanical search/list/transform work), and reserve the session’s top model for the genuinely hard subtasks
(from the
global CLAUDE.md;
the model: field does the same for a subagent you define)
ultracode
yes, and…#
how does it get pushed further? weird ideas welcome
Web#
real-time, in Phoenix LiveView#
the worked instance today is Phoenix LiveView: server-rendered pages in Elixir that stay connected to the server over a WebSocket
next week’s is SvelteKit: the ideas carry across stacks, and your final project’s stack is your call
request and response#
real-time in the brief#
a form submission re-renders the submitter’s page; everyone else sees the change on their next request
for them to see it sooner, the client asks often or the server pushes
the brief’s real-time: a change one person makes appears in every other open session within about a second, with no reload
polling#
server-sent events#
server-sent events, on the wire#
one HTTP response that never ends; a blank line closes each event
HTTP/1.1 200 OK
Content-Type: text/event-stream
id: 4
data: {"body": "hello from tab A"}
id: 5
data: {"body": "and again"}if the connection drops, the browser reconnects and sends Last-Event-ID: 5
(MDN,
“Event stream format”)
WebSockets#
what the server holds open#
SSE is one-way HTTP; a WebSocket upgrades off HTTP and runs both ways
| transport | the browser sends | the server sends | held open per tab |
|---|---|---|---|
| polling | a request every second | a response to each | nothing |
| SSE | ordinary requests | events, down one response | one response |
| WebSocket | messages, on the socket | messages, on the socket | one socket |
| LiveView | events, on the socket | the changed HTML | a socket, plus a stateful process |
polling, server-sent events, WebSockets#
polling: the client asks on a timer
setInterval(() => fetch("/api/messages").then(render), 1000);server-sent events: one long response the server keeps writing to, one way only
new EventSource("/api/events").onmessage = (e) => render(e.data);WebSockets: a two-way connection; either side sends at any time
const ws = new WebSocket("wss://example.fly.dev/live");the LiveView life-cycle#
first: a plain HTTP request; mount/3 runs and the server sends the whole
page as HTML
then: the browser opens a WebSocket, mount/3 runs again in a process that
stays alive, and connected?(socket) is now true
after that: events go up the socket, and only the parts of the page that changed come back down
(LiveView docs, “Life-cycle”)
publish and subscribe#
subscribe: each connected page registers interest in a topic (the board’s
Board.subscribe(), in mount/3)
publish: the code that saves a message broadcasts it to the topic once
(Phoenix.PubSub.broadcast(…))
deliver: every subscriber’s handle_info/2 gets it and sends the change
down its own socket
LiveView with PubSub#
the board, in LiveView#
# board.ex, after the insert
Phoenix.PubSub.broadcast(LectureDemos.PubSub, @topic,
{:new_message, message})
# board_live.ex (abridged)
def mount(_params, _session, socket) do
if connected?(socket), do: Board.subscribe()
{:ok, stream(socket, :messages, Board.list_messages())}
end
def handle_info({:new_message, message}, socket) do
{:noreply, stream_insert(socket, :messages, message, at: 0)}
endpub/sub in Rails and Django#
Rails (Turbo Streams): the model broadcasts, the view subscribes
after_create_commit { broadcast_prepend_to "messages" }Django (Channels): a consumer joins a group, and a save sends to it
async_to_sync(layer.group_send)("messages", event)plain Node: an EventEmitter as the topic, and an SSE endpoint per open tab
presence#
who’s connected to a topic right now, kept up to date as people join and leave (Phoenix.Presence)
if connected?(socket) do
MyAppWeb.Presence.track(self(), "messages", user_id,
%{name: name})
end
MyAppWeb.Presence.list("messages")
#=> %{"123" => %{metas: [%{name: "Ada", phx_ref: ...}]}}when a tab closes, its process exits and it drops out of the list
pub/sub across machines#
one machine: a broadcast reaches the subscribers on that machine
more machines: Phoenix.PubSub spans a cluster of connected Erlang nodes;
other stacks share a broker (Redis, Postgres LISTEN/NOTIFY)
your final project: one Fly machine, so an in-memory topic is enough
further reading#
MDN, Using server-sent events: the event stream format, and the six-connection limit without HTTP/2
MDN, The WebSocket API: the two-way alternative, and what’s replacing it (WebTransport)
lecture-demos: the board, in full, as of week 9
Phoenix LiveView, Phoenix.PubSub and Phoenix.Presence: today’s worked instance
Turbo Streams and Django Channels: the same pattern in Rails and Django
the existential bit#
there’s so much to cover here, so start early, use your brain and your agent, and ask for help if you’re stuck
yes, and…#
how does it get pushed further? weird ideas welcome
C9: all at once#
make your final project real-time, then decide how it behaves when several people use it at once, and write down why
a change one person makes appears in every other open session within about a second, with no reload
at the crit your pod opens it on their own devices, at the same time
deadline radar#
a2 marks released tomorrow
your week 10 session: C9 live two hours before it, with
reflections/crit-9.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#
a subagent does a side task in its own context and returns only a summary
parallel subagents work best on reads; keep writes in one thread, and give each writer its own worktree
real-time means the server pushes each change to every subscriber, through polling, SSE or a WebSocket
ask your agent#
ask until you can explain these back:
- delegation: which parts of my current task could a subagent do without my conversation?
- a spike: two approaches to my real-time feature, each in its own worktree, with what each costs
- transports: polling, SSE or WebSockets for my app: which, and why?
- broadcast: how does my stack send one change to every connected client?