Welcome

--:-- recording — let's go

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 setup in its worktree, starts the app in the background with PORT=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…”)

two context windows

A subagent's context window The main session's context window holds the harness and the conversation. It sends a task message to a subagent, whose separate context window holds only its own harness and that message. The subagent's window fills with five tool calls and results; a four-line summary goes back to the main window, and the subagent's window is discarded. your session system prompt and CLAUDE.md your conversation so far every prompt, file read and tool result in this session the subagent its own system prompt and tools the task message task sees none of your conversation find . -name '*.ts' … ls -la find . -name '*.ex' … Read board_live.ex Read board.ex 22k tokens its four-line summary summary discarded

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: worktree

for 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

request and response: a new message reaching three open tabs tab A (you) 3 messages 4 messages tab B 3 messages tab C 3 messages server database POST /messages insert a new page B and C see it on their next request

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

polling: a new message reaching three open tabs tab A (you) 3 messages 4 messages tab B 3 messages 4 messages tab C 3 messages 4 messages server database anything new? no every tab asks again, every second POST /messages insert next poll: yes

server-sent events

server-sent events: a new message reaching three open tabs tab A (you) 3 messages 4 messages tab B 3 messages 4 messages tab C 3 messages 4 messages server database each tab holds one response open POST /messages insert data: {…} down every stream

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

WebSockets: a new message reaching three open tabs tab A (you) 3 messages tab B 3 messages tab C 3 messages server database each tab holds one two-way connection new message insert which sockets get it?

what the server holds open

SSE is one-way HTTP; a WebSocket upgrades off HTTP and runs both ways

transportthe browser sendsthe server sendsheld open per tab
pollinga request every seconda response to eachnothing
SSEordinary requestsevents, down one responseone response
WebSocketmessages, on the socketmessages, on the socketone socket
LiveViewevents, on the socketthe changed HTMLa 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

LiveView with PubSub: a new message reaching three open tabs tab A (you) 3 messages 4 messages tab B 3 messages 4 messages tab C 3 messages 4 messages server process A process B process C PubSub topic database save insert broadcast handle_info/2 in every process the changed HTML, down each socket

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)}
end

pub/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?

See you in the studio