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.
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.
| State | Held in memory by | Real copy in | How it's kept up to date | Rebuilt by |
|---|---|---|---|---|
| the conversation, as people see it | Agent | ■ Postgres | the Agent saves each event to Postgres as it arrives, and pushes it to the page | loading it when the Agent starts |
| how far the Agent has read | Agent | ■ Postgres | the bridge numbers every line, so the last saved event says where it got to | asking the bridge for lines after the last saved one |
| which pod is spore A's | Host | ■ Kubernetes API | Kubernetes is the one that creates and deletes pods, so it's always right | the Host asking Kubernetes, never trusting its memory |
| which spores are running | Registry | ■ Kubernetes API | a spore is awake exactly when its pod exists | reattach listing pods at boot, or the next use of a spore |
| the spore's files and git work | (nobody) | ■ home volume | written straight to disk; pushed to the colony's repo for safety | mounting the same volume in the new pod |
| Claude Code's session | Claude Code | ■ home volume | Claude Code writes it to ~/.claude as it goes | Claude Code resuming the session |
| the step it's working on | Claude Code | nowhere | it isn't: this is the one thing that can be lost | the person, or Claude, trying that step again |
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 session | mycelia's copy | |
|---|---|---|
| Lives in | ■ home volume (~/.claude) | ■ Postgres |
| Read by | Claude, to carry on working | the page, and the people in the colony |
| Needs the pod? | yes: it's only reachable from inside it | no: a sleeping spore's pod is deleted, but its chat still shows |
| Holds | what Claude needs; older parts get summarized when it grows long | everything shown: who said what, mycologist replies, deploys, the activity graph |
| Can the agent change it? | yes, it's a file in its own home | no, 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>:
/home/spore→
→
- The first time, Kubernetes sees the claim and the local-path provisioner makes a folder for it on the machine's own disk.
- Everything written to
/home/sporelands in that folder, not inside the container. - When the pod is deleted, the claim and the folder stay.
- The Host creates a new pod naming the same claim, and Kubernetes mounts the same folder again: every file is back.
| What happens | /home/spore | everything else in the container |
|---|---|---|
| the container restarts inside the same pod | kept | reset to the image |
| the pod is deleted and made again (sleep and wake, out of memory) | kept: same claim, same folder | reset to the image |
| the machine goes down | kept, but out of reach until it's back | gone |
| the claim is deleted (likely when a spore is deleted) | gone | gone |
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 fails | Who notices | Who brings it back | State lost |
|---|---|---|---|
| Agent GenServer | its Spores.Tree, at once (they're linked) | The Tree restarts the Agent alone. | its in-memory copy only |
| Host GenServer | its Spores.Tree | The Tree restarts the Host, then the Agent (rest_for_one). | both copies only |
| Spores.Supervisor or Registry | mycelia's top supervisor | Restarted empty; each tree starts again when its spore is used. | in-memory copies only |
| mycelia pod (or its BEAM) | the kubelet / the Deployment | A new mycelia pod; reattach rebuilds every tree. | in-memory copies only |
| Claude Code exits | bridge.py | The bridge starts it again. | the step it was on |
| spore pod (out of memory, evicted) | the Agent: its connection drops | The Host creates a new pod on the same home volume. | the step it was on, /tmp, running programs |
| the machine under the pod | Kubernetes, after a few minutes | The Host creates a new pod, which likely waits for that machine. | the same as a pod, plus time |
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).