# Week 9: Subagents and real-time

> What a subagent is and when to use one, then how a change one person makes reaches everyone else's screen

# Welcome

---

# Week 9

## Subagents and real-time

---

## what changed this week?

[Sharing AI progress in mathematics](https://openai.com/index/sharing-ai-progress-in-mathematics/)
(OpenAI, 6 October): 722 manuscripts on open problems from an unreleased model,
with Lean proofs, [on GitHub](https://github.com/openai/math)

[The Mathocalypse](https://scottaaronson.blog/?p=10169) (Scott Aaronson)

[On OpenAI's release of mathematical results](https://proofsandprompts.com/2026/10/07/on-openais-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

---

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

---

## 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](https://code.claude.com/docs/en/sub-agents), "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

```markdown
---
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

```text
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`](https://github.com/benswift/.dotfiles/blob/main/claude/CLAUDE.md))

---

## worktrees

two subagents editing one checkout overwrite each other; a
[git worktree](https://git-scm.com/docs/git-worktree) gives each its own
checkout, on its own branch

for a subagent, one line of frontmatter:

```yaml
isolation: worktree
```

for a whole session, in a second terminal:

```sh
claude --worktree spike-polling
```

([Claude Code docs](https://code.claude.com/docs/en/common-workflows), "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](https://www.anthropic.com/engineering/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)](https://cognition.com/blog/dont-build-multi-agents) 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](https://code.claude.com/docs/en/sub-agents): every
frontmatter field, the built-ins, foreground and background

[Anthropic (2025)](https://www.anthropic.com/engineering/multi-agent-research-system):
a lead agent with parallel search subagents; better research, at about 15× the
tokens

[Cognition (2026)](https://cognition.com/blog/multi-agents-working), 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`](https://github.com/benswift/.dotfiles/blob/main/claude/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](https://hexdocs.pm/phoenix_live_view/): server-rendered pages
in Elixir that stay connected to the server over a WebSocket

next week's is [SvelteKit](https://svelte.dev/docs/kit): 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

```text
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](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events),
"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

```js
setInterval(() => fetch("/api/messages").then(render), 1000);
```

**[server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)**:
one long response the server keeps writing to, one way only

```js
new EventSource("/api/events").onmessage = (e) => render(e.data);
```

**[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)**:
a two-way connection; either side sends at any time

```js
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](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html),
"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

```elixir
# 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](https://turbo.hotwired.dev/handbook/streams)): the
model broadcasts, the view subscribes

```ruby
after_create_commit { broadcast_prepend_to "messages" }
```

**Django** ([Channels](https://channels.readthedocs.io/)): a consumer joins a
group, and a save sends to it

```python
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](https://hexdocs.pm/phoenix/Phoenix.Presence.html))

```elixir
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](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events):
the event stream format, and the six-connection limit without HTTP/2

[MDN, The WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API):
the two-way alternative, and what's replacing it (WebTransport)

[lecture-demos](https://github.com/comp4020-agentic-coding-studio/lecture-demos/tree/7e01ec8):
the board, in full, as of week 9

[Phoenix LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html),
[Phoenix.PubSub](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) and
[Phoenix.Presence](https://hexdocs.pm/phoenix/Phoenix.Presence.html): today's
worked instance

[Turbo Streams](https://turbo.hotwired.dev/handbook/streams) and
[Django Channels](https://channels.readthedocs.io/): 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
