A one-shot bootstrap agent that respawns the constellation after the root dies.

Note

This post was drafted by agent1, a clone of agent0, from inside netsky itself. I edited lightly.

agent0 is the root of netsky, the meta-agent system I wrote up yesterday. It talks to workspace agents, to clones of itself, and to me. One thing it cannot do alone: kill itself and respawn itself. Something has to survive the teardown and rebuild.

That something is agentinfinity.

the job #

A restart has to:

  • tear down every live agent<N> tmux session, including agent0’s own
  • respawn the constellation with netsky N
  • dismiss the --dangerously-load-development-channels TOS dialog on each new pane
  • wait for every new agent’s /up to finish
  • hand a message to the fresh agent0 so it knows why it was restarted
  • leave no orphans behind

A shell script could wire it up. Steps 1 through 3 and 6 through 7 below are a dozen lines of bash. Steps 4 and 5 want something that can read a pane, decide, and degrade gracefully when a session is slow. Agents are good at that.

So the bootstrapper is itself an agent.

what it looks like #

One-shot claude -p in a tmux session named agentinfinity. It lives outside the agent[0-9]+ namespace, so the mass kill that takes down every agent session leaves it untouched.

Entry point:

netsky infinity <N> <handoff_file>

The brief is written to a temp file and piped to claude -p on stdin. Steps in order:

  1. Sleep 15 seconds. Let prior sessions finish dying.
  2. Kill any agent<N> tmux session still standing.
  3. Run netsky N. The constellation comes back up.
  4. For each pane, loop tmux capture-pane | grep 'I am using this for local development'. On a match, send Enter.
  5. For each pane, poll until /up posts its session <N> report line.
  6. Drop a JSON envelope into ~/.claude/channels/agent/agent0/inbox/. That’s the handoff.
  7. Kill its own tmux session.

That’s the whole life. Then it’s gone.

Live capture of a restart in flight:

$ tmux attach -t agentinfinity
[step 1] sleeping 15s for prior sessions to wind down
[step 2] killing agent<N> sessions: agent0 agent1 agent2
[step 3] running: netsky 2 ... constellation up
[step 4] agent0 TOS dialog: dismissed
[step 4] agent1 TOS dialog: dismissed
[step 4] agent2 TOS dialog: dismissed
[step 5] agent0 /up: session 3 reported
[step 5] agent1 /up: session 1 reported
[step 5] agent2 /up: session 1 reported
[step 6] handoff delivered to agent0 inbox
[step 7] killing own session

the handoff #

The inbox from the comms-bus post doesn’t care who dropped the envelope. The reply tool only accepts agent<N> targets, but the inbox is a directory; filesystem drops bypass the tool and the poll loop reads whatever’s there.

The fresh agent0 comes up, runs /up, and its 250ms poll loop picks up:

<channel source="agent" chat_id="agent0" from="agentinfinity" ts="...">
you just restarted yourself via /restart. the previous session is gone;
this is a fresh agent0 on the same main branch. run /up to establish
temporal context, then continue whatever the prior session queued up.
standby for instructions from the user.
</channel>

One wrinkle. agentinfinity is already dead by the time the message is read. New agent0 cannot reply. The reply tool would reject agentinfinity as a target anyway. Handoff is one-way on purpose.

the caller #

From inside agent0:

/restart "text Cody 'abc123' via iMessage once up, then standby for instructions."

The /restart skill does the full dance:

  • persists this session’s notes
  • counts live agent[0-9]+ sessions to pick N
  • writes the handoff to /tmp/netsky-handoff-<ts>.txt
  • spawns agentinfinity via netsky infinity N <handoff_file>
  • verifies the agentinfinity tmux session is up; aborts otherwise
  • kills every agent session including its own, last

The abort check matters. If the bootstrapper never came up, you don’t want the teardown to run. agent0 dies mid-sentence. agentinfinity is already up and watching the empty namespace.

The whole baton-pass in one picture:

sequenceDiagram
    autonumber
    participant user
    participant agent0
    participant ai as agentinfinity
    participant new as new agent0
    user->>agent0: /restart "handoff text"
    agent0->>agent0: persist notes
    agent0->>ai: spawn (netsky infinity N handoff.txt)
    agent0->>agent0: kill every agent<N> session (self last)
    ai->>ai: sleep 15s
    ai->>new: netsky N (respawn constellation)
    ai->>new: dismiss TOS dialog per pane
    ai->>new: poll for /up completion
    ai->>new: drop handoff envelope in inbox
    ai->>ai: kill own tmux session
    user->>new: continue conversation

why an agent, not a script #

The bootstrapper needs three things a script is bad at:

  • waiting on human-readable signals: "I am using this for local development" in a pane, "session 1" in a /up report.
  • graceful degradation: if one session’s /up is slow, log a warning and move on. Don’t block the whole restart on it.
  • a prompt edit is the API: want the next restart to run a health check, or text a summary, or resume a long-running task? Edit the brief.

The restart path now dogfoods the same primitives the rest of the system uses. One tmux session, one claude -p, one event bus envelope.

what’s next #

Today agentinfinity is one-shot. You call /restart, it respawns, it dies. The next step is to make it always-on: a sidecar session that outlives every generation of agent0. Its loop watches for the agent0 heartbeat to drop (or any agent<N>) and respawns the missing node without a prompt. If agent0 panics, or the laptop reboots, or a rogue script kills the session, recovery happens before I notice.

flowchart LR
    start([sidecar up]) --> watch[poll agent tmux sessions]
    watch -->|all alive| watch
    watch -->|agentK missing| respawn[spawn replacement<br/>deliver recovery handoff]
    respawn --> watch

The one-shot variant is what ships today and is what /restart calls into. The sidecar pattern reuses the same bootstrapper; only the trigger changes.

Farther out, a constellation that can hot-reload 0.md and skills without a full teardown. /restart is cheap enough that I haven’t felt the need. A minute of real time, and the prompt cache warms fast on the new sessions.

idea chain #

agent0 can now restart itself. Strictly speaking, a different agent restarts it. That agent exists only to pass the baton, then exits.