← all diagrams

When one side fails, the state survives

Each spore is two things at once: a small process tree inside mycelia (Elixir) and a pod in the cluster (Kubernetes). Either can die at any moment, because neither keeps anything it can't get back. Running processes only hold copies; the real state lives in three stores that outlive them.

■ Postgres

Every message of every conversation, saved as it streams in. The Agent's state comes from here.

■ Kubernetes API

Which pods exist, and whose they are. The Host's state comes from here: it asks, rather than remembering.

■ home volume

The spore's files, its git work and Claude Code's session. The pod's state comes from here.

Tap any box to break it and watch, in the log, what's lost, what's kept, and which store it's rebuilt from.

mycelia · Elixir
mycelia podDeployment · 1 replica
BEAMthe Elixir VM
Spores.Supervisor+ Registry · every spore
spore → its processes
Spores.Tree · spore Arest_for_one
HostGenServer
owns the pod
which pod is A's
AgentGenServer
holds the chat
the chatlast event #
Spores.Tree · spore Banother spore
B's chat
Postgresoutlives mycelia
every messageevery spore
spore A · Kubernetes
Kubernetes APIoutlives every pod
which pods existwhose they are
a machineKubernetes node
spore pod2 CPU · 3 GiB
bridge.pyHTTP :8080
numbered event lines
Claude Codedoing the work
the step it's on
home volume/home/spore · outlives pods
files · git workClaude's session
or pick one:
◆ state in memory: dies with its box■ state in a store: outlives it
runningfailedstopped as a resultstarting again who fixes it: BEAM · Kubernetes · bridge · Postgres
Tap a box, or pick a failure above.

Where every piece of state lives

Read across: what a box holds, where the real copy is, and how it gets the state back after a restart.

StateHeld in memory byReal copy inHow it's kept up to dateRebuilt by
the conversation, as people see itAgent■ Postgresthe Agent saves each event to Postgres as it arrives, and pushes it to the pageloading it when the Agent starts
how far the Agent has readAgent■ Postgresthe bridge numbers every line, so the last saved event says where it got toasking the bridge for lines after the last saved one
which pod is spore A'sHost■ Kubernetes APIKubernetes is the one that creates and deletes pods, so it's always rightthe Host asking Kubernetes, never trusting its memory
which spores are runningRegistry■ Kubernetes APIa spore is awake exactly when its pod existsreattach listing pods at boot, or the next use of a spore
the spore's files and git work(nobody)■ home volumewritten straight to disk; pushed to the colony's repo for safetymounting the same volume in the new pod
Claude Code's sessionClaude Code■ home volumeClaude Code writes it to ~/.claude as it goesClaude Code resuming the session
the step it's working onClaude Codenowhereit isn't: this is the one thing that can be lostthe person, or Claude, trying that step again
The rule both sides follow: a process may hold state but never own it. The Agent's chat is a copy of Postgres, and Claude Code's own working copy is a file on the home volume. The Host's idea of its pod is a copy of what the Kubernetes API says. The pod's files live on the home volume, not in the pod. So any of them can die at any moment, and the replacement starts by reading the store, not by asking whoever died. That's also why the two sides don't depend on each other: neither one holds anything the other needs to get back.

Two copies of the chat

Claude Code could resume on its own: its session is a file on the home volume, and that's what it picks up after any restart. mycelia keeps a second copy in Postgres anyway, because the two copies have different readers.

Claude Code's sessionmycelia's copy
Lives in■ home volume (~/.claude)■ Postgres
Read byClaude, to carry on workingthe page, and the people in the colony
Needs the pod?yes: it's only reachable from inside itno: a sleeping spore's pod is deleted, but its chat still shows
Holdswhat Claude needs; older parts get summarized when it grows longeverything shown: who said what, mycologist replies, deploys, the activity graph
Can the agent change it?yes, it's a file in its own homeno, it's outside the pod's reach

So the home volume is enough for Claude to keep working. Postgres is what lets people see and trust the conversation. A restarted Agent reads Postgres because it serves the page, not because Claude needs it.

How the home volume survives

In Kubernetes, a pod and its storage are separate objects. mycelia makes two for each spore, both named spore-<id>:

Podthe running container: Claude Code + bridgedeleted at every sleep or crash
mounts at
/home/spore
→
PersistentVolumeClaim"5 GiB, local-path"stays until someone deletes it
bound to
→
a folder on the machine's diskmade by k3s's local-path provisionerwhere the files really are
  1. The first time, Kubernetes sees the claim and the local-path provisioner makes a folder for it on the machine's own disk.
  2. Everything written to /home/spore lands in that folder, not inside the container.
  3. When the pod is deleted, the claim and the folder stay.
  4. The Host creates a new pod naming the same claim, and Kubernetes mounts the same folder again: every file is back.
What happens/home/sporeeverything else in the container
the container restarts inside the same podkeptreset to the image
the pod is deleted and made again (sleep and wake, out of memory)kept: same claim, same folderreset to the image
the machine goes downkept, but out of reach until it's backgone
the claim is deleted (likely when a spore is deleted)gonegone

The catch is "local-path": the folder exists on one machine only, so Kubernetes has to put the new pod on that machine. Network storage (EFS on AWS, for example) can be mounted from any machine. And the last row is why work is also pushed to the colony's git repo, which outlives any spore. (From inside a spore, df shows the whole machine's disk rather than 5 GiB, which fits local-path: it doesn't enforce the size. The folder's exact location is k3s's default, not checked on this cluster.)

Who notices, who fixes it

What failsWho noticesWho brings it backState lost
Agent GenServerits Spores.Tree, at once (they're linked)The Tree restarts the Agent alone.its in-memory copy only
Host GenServerits Spores.TreeThe Tree restarts the Host, then the Agent (rest_for_one).both copies only
Spores.Supervisor or Registrymycelia's top supervisorRestarted empty; each tree starts again when its spore is used.in-memory copies only
mycelia pod (or its BEAM)the kubelet / the DeploymentA new mycelia pod; reattach rebuilds every tree.in-memory copies only
Claude Code exitsbridge.pyThe bridge starts it again.the step it was on
spore pod (out of memory, evicted)the Agent: its connection dropsThe Host creates a new pod on the same home volume.the step it was on, /tmp, running programs
the machine under the podKubernetes, after a few minutesThe Host creates a new pod, which likely waits for that machine.the same as a pod, plus time
Not certain: three parts of this page are a reading of the setup, not something seen happening. That a new Agent picks up the event stream from the last line it saved follows from the bridge numbering its lines. That the Host asks Kubernetes for its pod rather than remembering it follows from mycelia finding pods again at boot and after waking. And spore home volumes are local-path volumes, which live on one machine's own disk, so a replacement pod can probably only start on that machine. Planned updates of mycelia itself are gentler than a crash: the new mycelia pod starts before the old one stops, and the old one hands its trees over (see the explainer).