<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>warden | Blog</title><description>Run a fleet of Claude Code agents from one cockpit.</description><link>https://srjn45.github.io/</link><language>en</language><item><title>My whole agent fleet showed up dead. Every process was alive.</title><link>https://srjn45.github.io/warden/blog/tmux-isolation-footguns/</link><guid isPermaLink="true">https://srjn45.github.io/warden/blog/tmux-isolation-footguns/</guid><description>The cockpit showed six agents, all orphaned. ps showed six agents, all alive, happily working. Nothing had crashed. The bug wasn&apos;t in my code — it was in which tmux server my code was talking to. Three isolation footguns, and how each one bites.

</description><pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The cockpit showed six agents. Every one of them &lt;strong&gt;orphaned&lt;/strong&gt; — the status
warden uses for “the session backing this agent is gone.”&lt;/p&gt;
&lt;p&gt;So I checked. &lt;code dir=&quot;auto&quot;&gt;ps&lt;/code&gt; showed six agent processes, all alive. Their tmux sessions
were there. The agents were, at that very moment, happily editing code in the
dark. Nothing had crashed, nothing had logged an error, and the supervisor was
certain the entire fleet was dead.&lt;/p&gt;
&lt;p&gt;The bug wasn’t in my code. It was in &lt;strong&gt;which tmux server my code was talking
to&lt;/strong&gt; — and tmux never once told me I had the wrong one.&lt;/p&gt;
&lt;p&gt;Here’s the context you need, then the three footguns that took me embarrassingly
long to untangle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-model-one-tmux-session-per-agent&quot;&gt;The model: one tmux session per agent&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;warden runs a fleet of coding agents. The design decision underneath all of it
is boring on paper: &lt;strong&gt;every agent gets its own tmux session, and one daemon
supervises them all.&lt;/strong&gt; tmux buys persistence for free (detach, reattach, survive
a client crash), a stable handle to a live terminal, and a dead-simple way to
capture what an agent is doing right now (&lt;code dir=&quot;auto&quot;&gt;capture-pane&lt;/code&gt;). The daemon is the
single writer — it shells out to &lt;code dir=&quot;auto&quot;&gt;tmux&lt;/code&gt; to spawn, poke, and reap sessions, and
it decides an agent is alive by asking &lt;code dir=&quot;auto&quot;&gt;tmux has-session -t &amp;#x3C;id&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That last line is the whole story. &lt;code dir=&quot;auto&quot;&gt;has-session&lt;/code&gt; answers a yes/no question —
&lt;em&gt;does this session exist?&lt;/em&gt; — but it answers it &lt;strong&gt;about a specific server&lt;/strong&gt;, and
which server it means is decided by ambient state you never explicitly named. If
the daemon asks the wrong server, every session “doesn’t exist,” and a perfectly
healthy fleet reports as orphaned. Which is exactly what I was staring at.&lt;/p&gt;
&lt;p&gt;There are three separate ways to end up addressing the wrong server. They bite
in this order.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;footgun-1-tmux-hijacks-every-child-tmux-command&quot;&gt;Footgun 1: &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; hijacks every child tmux command&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;tmux sets an environment variable called &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; in &lt;strong&gt;every process it
spawns&lt;/strong&gt; — it looks like &lt;code dir=&quot;auto&quot;&gt;/tmp/tmux-1000/default,12345,0&lt;/code&gt;. That variable is how
a tmux &lt;em&gt;client&lt;/em&gt; knows which &lt;em&gt;server&lt;/em&gt; it belongs to. Handy for interactive use.
A landmine for a supervisor.&lt;/p&gt;
&lt;p&gt;Because when &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; is set, a bare &lt;code dir=&quot;auto&quot;&gt;tmux&lt;/code&gt; command binds to &lt;em&gt;that&lt;/em&gt; server —
regardless of any other configuration you thought you set. So retrace my dead
fleet:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I’d started the daemon from inside a tmux session (I was already in one — who
isn’t?).&lt;/li&gt;
&lt;li&gt;The daemon inherited &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; pointing at &lt;em&gt;my outer&lt;/em&gt; tmux server.&lt;/li&gt;
&lt;li&gt;The agent sessions lived on a different server than the one &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; named.&lt;/li&gt;
&lt;li&gt;The daemon ran &lt;code dir=&quot;auto&quot;&gt;tmux has-session -t agent-9d2e7b53&lt;/code&gt; to check liveness. That
command followed &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; to the &lt;strong&gt;outer&lt;/strong&gt; server, which had never heard of
&lt;code dir=&quot;auto&quot;&gt;agent-9d2e7b53&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;has-session&lt;/code&gt; returned non-zero. The poller concluded the session was gone
and marked the agent &lt;strong&gt;orphaned&lt;/strong&gt; — six times over.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nothing crashed. The agents were alive. tmux was doing exactly what it was told.
I’d just told it something I didn’t mean to, via a variable I didn’t set.&lt;/p&gt;
&lt;p&gt;In Go this is trivially easy to do by accident, because &lt;code dir=&quot;auto&quot;&gt;os/exec&lt;/code&gt; &lt;strong&gt;inherits the
parent environment when you don’t set &lt;code dir=&quot;auto&quot;&gt;Cmd.Env&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (ExecRunner) &lt;/span&gt;&lt;span&gt;Run&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context.Context, &lt;/span&gt;&lt;span&gt;dir&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CommandContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dir&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;// we set the working directory…&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CombinedOutput&lt;/span&gt;&lt;span&gt;()  &lt;/span&gt;&lt;span&gt;// …but Cmd.Env is nil, so the child&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// inherits the daemon&apos;s whole environment — $TMUX and all&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Cmd.Env == nil&lt;/code&gt; means “use &lt;code dir=&quot;auto&quot;&gt;os.Environ()&lt;/code&gt;”. So the daemon’s environment &lt;em&gt;is&lt;/em&gt;
every agent’s tmux environment, transitively. The supervisor’s launch context
silently became part of the architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix is to scrub &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; whenever a command must target a specific
server:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TMUX&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;has-session&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;agent-9d2e7b53&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The broader lesson is the one that generalizes past tmux: &lt;strong&gt;for a long-lived
supervisor, the environment it launches in is not incidental — it’s config.&lt;/strong&gt;
Pin it. That’s why warden’s daemon is meant to run from a known, clean
environment (a systemd unit, say) rather than “wherever I happened to type
&lt;code dir=&quot;auto&quot;&gt;warden daemon&lt;/code&gt;.” A stray &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; in that environment is enough to make the
whole fleet look dead.&lt;/p&gt;
&lt;p&gt;warden also leans on the &lt;em&gt;same&lt;/em&gt; variable deliberately, on the client side —
the cockpit checks it to decide its layout:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// InsideTmux reports whether we&apos;re running inside a tmux session.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;InsideTmux&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;os&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Getenv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TMUX&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you launch the TUI from inside tmux, it lays the cockpit out as native tmux
panes instead of nesting a second tmux inside the first. Same variable — a
signal when you read it on purpose, a footgun when you inherit it by accident.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;footgun-2-the-default-socket-is-not-where-you-think-it-is&quot;&gt;Footgun 2: the default socket is not where you think it is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Say you’ve learned footgun 1 and you want real isolation: a throwaway warden
instance for a demo, on its own tmux server, touching nothing real. So you set
&lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt; to point tmux at a private directory and move on.&lt;/p&gt;
&lt;p&gt;Then you try to talk to that server explicitly with &lt;code dir=&quot;auto&quot;&gt;-S&lt;/code&gt; and guess the socket
path — and you guess wrong. The default tmux socket is &lt;strong&gt;not&lt;/strong&gt;
&lt;code dir=&quot;auto&quot;&gt;$TMUX_TMPDIR/default&lt;/code&gt;. It’s:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$TMUX_TMPDIR/tmux-&amp;#x3C;uid&gt;/default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That &lt;code dir=&quot;auto&quot;&gt;tmux-&amp;#x3C;uid&gt;&lt;/code&gt; subdirectory (e.g. &lt;code dir=&quot;auto&quot;&gt;tmux-1000&lt;/code&gt; for uid 1000) is easy to
forget, because you normally never type the socket path at all. The instant you
start passing &lt;code dir=&quot;auto&quot;&gt;-S&lt;/code&gt; by hand — in a script, a test harness, a capture rig — you
have to reconstruct the exact path tmux would have derived, &lt;code dir=&quot;auto&quot;&gt;tmux-&amp;#x3C;uid&gt;&lt;/code&gt; and
all. Miss it and you’ve created a &lt;em&gt;second&lt;/em&gt; empty server next to the one you
meant to reach, and now nothing lines up.&lt;/p&gt;
&lt;p&gt;The cleaner move is usually to &lt;strong&gt;not&lt;/strong&gt; pass &lt;code dir=&quot;auto&quot;&gt;-S&lt;/code&gt; at all: set &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt;,
scrub &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt;, and let tmux compute its own default socket underneath your
private tmpdir:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TMUX&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TMUX_TMPDIR=/tmp/demo-tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new-session&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;agent-demo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Let the tool derive the path. The moment you hard-code it, you’ve taken on a
piece of tmux’s internal layout as your own responsibility.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;footgun-3-precedence--tmux-beats-tmux_tmpdir&quot;&gt;Footgun 3: precedence — &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; beats &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt;&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Footguns 1 and 2 combine into a nastier third one, and it’s pure precedence.&lt;/p&gt;
&lt;p&gt;Inside an existing tmux session, &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; is set. If you then set &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt;
and run a bare &lt;code dir=&quot;auto&quot;&gt;tmux&lt;/code&gt;, you might expect the fresh &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt; to win — you
just set it, after all. It doesn’t. For a bare &lt;code dir=&quot;auto&quot;&gt;tmux&lt;/code&gt; client, &lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; wins&lt;/strong&gt;:
the command follows the inherited session to the &lt;em&gt;old&lt;/em&gt; server and blithely
ignores the tmpdir you carefully pointed somewhere isolated.&lt;/p&gt;
&lt;p&gt;So “I set &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt;, therefore I’m isolated” is false while &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; is still
in the environment. Isolation requires &lt;em&gt;both&lt;/em&gt;: scrub &lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; set
&lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt;. One without the other is a quiet no-op that looks like it worked
right up until two servers’ worth of state bleed into each other.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# WRONG — $TMUX still set, so this targets the outer server despite TMUX_TMPDIR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TMUX_TMPDIR&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/tmp/demo-tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new-session&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;agent-demo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# RIGHT — scrub the inherited session first, then point at the private tmpdir&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TMUX&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TMUX_TMPDIR=/tmp/demo-tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tmux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new-session&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;agent-demo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;the-pattern-behind-all-three&quot;&gt;The pattern behind all three&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every one of these is the same shape: &lt;strong&gt;tmux resolves “which server” from
ambient state — an inherited variable, an implicit socket path — and the
resolution is invisible until it’s wrong.&lt;/strong&gt; The commands never error in a way
that says “you’re on the wrong server.” &lt;code dir=&quot;auto&quot;&gt;has-session&lt;/code&gt; just says &lt;em&gt;no&lt;/em&gt;.
&lt;code dir=&quot;auto&quot;&gt;new-session&lt;/code&gt; just quietly makes a second one. The state diverges silently and
you debug it as a &lt;em&gt;warden&lt;/em&gt; problem (why is the fleet orphaned?) when it’s really
a &lt;em&gt;tmux addressing&lt;/em&gt; problem one layer down.&lt;/p&gt;
&lt;p&gt;For anything that supervises tmux from code, three rules pay for themselves:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pin the environment.&lt;/strong&gt; A supervisor’s launch environment is config. Scrub
&lt;code dir=&quot;auto&quot;&gt;$TMUX&lt;/code&gt; for any command that must target a specific server, and don’t let the
daemon inherit an interactive shell’s tmux context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don’t hand-author the socket path.&lt;/strong&gt; Set &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt; and let tmux derive
the &lt;code dir=&quot;auto&quot;&gt;tmux-&amp;#x3C;uid&gt;/default&lt;/code&gt; socket itself. Reconstructing it by hand means you
now own a piece of tmux’s internal layout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isolation is &lt;code dir=&quot;auto&quot;&gt;env -u TMUX&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code dir=&quot;auto&quot;&gt;TMUX_TMPDIR&lt;/code&gt;, never one alone.&lt;/strong&gt;
Precedence will punish the half-measure.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In warden these live at the boundary where the daemon shells out to &lt;code dir=&quot;auto&quot;&gt;tmux&lt;/code&gt;, and
they’re the difference between “the cockpit shows six live agents” and “the
cockpit shows six tombstones while the agents keep happily working in the dark.”&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;warden is an open-source Go binary for running a fleet of coding agents from
one cockpit. If you want to see the tmux-per-agent model in action, the
&lt;a href=&quot;https://srjn45.github.io/warden/concepts/architecture/&quot;&gt;architecture docs&lt;/a&gt; start here, or grab it from
&lt;a href=&quot;https://github.com/srjn45/warden&quot;&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>go</category><category>tmux</category><category>internals</category><category>debugging</category></item><item><title>The database said zero. The agents were still running.</title><link>https://srjn45.github.io/warden/blog/scrivadb-rotation-zero-records/</link><guid isPermaLink="true">https://srjn45.github.io/warden/blog/scrivadb-rotation-zero-records/</guid><description>I restarted the daemon and the cockpit came up with an empty fleet. tmux still had every agent, alive and mid-task. The append-only store had lost nothing — but it loaded zero records, because a segment rotation had corrupted the index over data that was sitting right there intact.

</description><pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I restarted the warden daemon. The cockpit came up with an &lt;strong&gt;empty fleet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Except it wasn’t empty. &lt;code dir=&quot;auto&quot;&gt;tmux ls&lt;/code&gt; listed every agent session, alive, each one
mid-task. The processes were fine. The work was fine. The daemon had simply
loaded &lt;strong&gt;zero records&lt;/strong&gt; from its session store on boot and concluded it was
supervising nobody.&lt;/p&gt;
&lt;p&gt;If you read &lt;a href=&quot;https://srjn45.github.io/warden/blog/tmux-isolation-footguns/&quot;&gt;the last post&lt;/a&gt;, this
symptom will feel familiar — &lt;em&gt;the supervisor insists the fleet is gone while
every agent is demonstrably alive.&lt;/em&gt; Last time the lie came from tmux server
addressing. This time it came from one layer deeper: the &lt;strong&gt;storage engine&lt;/strong&gt;.
And the twist is that the data was never lost. The &lt;em&gt;index over it&lt;/em&gt; was.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-store-append-only-segments-keyed-by-id&quot;&gt;The store: append-only segments, keyed by id&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;warden keeps its state in an embedded store — &lt;a href=&quot;https://github.com/srjn45/scriva&quot;&gt;ScrivaDB&lt;/a&gt;,
a small Go engine that every warden store embeds (sessions, pipelines,
snapshots, schedules, shared context). The session store is the one that bit me.
Its shape, straight from the code:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// FileStore persists sessions in an embedded ScrivaDB rooted at&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// &amp;#x3C;dir&gt;/sessions-db/: live sessions in an &quot;active&quot; collection and archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// ones in a &quot;closed&quot; collection, each record keyed by the session id.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// A write appends one record instead of rewriting a whole per-session JSON&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// file (the write-amplification the previous FileStore carried).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The important word is &lt;strong&gt;appends&lt;/strong&gt;. Each write is a new record appended to a
segment file; the engine keeps an &lt;strong&gt;index&lt;/strong&gt; mapping each key to the latest
record for that key. Reads consult the index, not the raw segments. This is a
completely standard log-structured design, and it has a completely standard
sharp edge.&lt;/p&gt;
&lt;p&gt;Append-only earns you one guarantee for free: you never tear a record
mid-write, because you never overwrite. warden’s own comment leans on exactly
that —&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// …the last write surviving a power-loss is not a requirement&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// (append-only segments rule out torn reads regardless).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;But notice what that guarantee covers: &lt;strong&gt;the data&lt;/strong&gt;. It says nothing about the
&lt;strong&gt;index&lt;/strong&gt; — the derived structure that has to be maintained &lt;em&gt;alongside&lt;/em&gt; the
append, and rebuilt or rotated as segments grow. That’s the part that broke.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;rotation-the-rare-path-that-only-fires-in-production&quot;&gt;Rotation: the rare path that only fires in production&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Append-only logs can’t grow forever, so the engine &lt;strong&gt;rotates&lt;/strong&gt;: when the active
segment hits a size threshold, it’s sealed and a new one starts, and the index
is updated to span the new layout. Rotation is the classic place storage bugs
hide, for a mean reason — &lt;strong&gt;it only triggers after the store has grown past a
threshold.&lt;/strong&gt; In dev, with a handful of records, you never rotate. You ship. The
store fills up in production over days of real use, crosses the threshold, and
&lt;em&gt;then&lt;/em&gt; the rare path runs for the first time, on your real data.&lt;/p&gt;
&lt;p&gt;That’s precisely what happened. The root cause was a &lt;strong&gt;name collision during
segment rotation&lt;/strong&gt;: the rotation logic could reuse a segment name in a way that
left the index pointing at the wrong bytes. The append had succeeded — every
session record was physically there, on disk, intact. But after the rotation,
the index that was supposed to &lt;em&gt;find&lt;/em&gt; those records was corrupt. On the next
open, the engine rebuilt its view from that index, found nothing coherent, and
handed warden an empty collection.&lt;/p&gt;
&lt;p&gt;Zero records. Not an error — an &lt;em&gt;answer.&lt;/em&gt; And that’s the crux of what made it
nasty.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;zero-records-is-indistinguishable-from-empty&quot;&gt;”Zero records” is indistinguishable from “empty”&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s the failure mode that turned a storage bug into a &lt;em&gt;warden&lt;/em&gt; mystery: a
store that returns zero records looks exactly like a store that is legitimately
empty. There’s no exception, no missing file, no torn read to catch. The daemon
asked “how many sessions?”, got &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;, and did the only reasonable thing with that
answer — showed an empty cockpit.&lt;/p&gt;
&lt;p&gt;The supervisor trusted a number it had no way to distinguish from a lie. Which
is the same shape as the tmux bug: the failing call didn’t say &lt;em&gt;“you’re asking
the wrong place”&lt;/em&gt; — &lt;code dir=&quot;auto&quot;&gt;has-session&lt;/code&gt; just said &lt;em&gt;no&lt;/em&gt;, and here the store just said
&lt;em&gt;zero&lt;/em&gt;. Silent-wrong is so much worse than loud-wrong.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-nothing-was-actually-lost&quot;&gt;Why nothing was actually lost&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The reason this is a war story and not a data-loss postmortem is a design
decision that predates the bug: &lt;strong&gt;warden treats &lt;code dir=&quot;auto&quot;&gt;sessions-db/&lt;/code&gt; as a derived
cache, not the source of truth.&lt;/strong&gt; The store’s own boot path spells it out:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// …if the sentinel is absent (never imported, or a prior attempt died&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// partway) the derived sessions-db is wiped and rebuilt from the read-only&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// legacy JSON, then the sentinel is written LAST — so a crash mid-import&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// loses nothing.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The store keeps a sentinel file that means &lt;em&gt;“the database is authoritative.”&lt;/em&gt;
The database is populated (once) from plain JSON, and the sentinel is written
&lt;strong&gt;last&lt;/strong&gt;. The invariant: nothing lives in the fast store that can’t be
reconstructed from something more durable and dumber.&lt;/p&gt;
&lt;p&gt;So the recovery from a corrupt index was, bluntly, to &lt;strong&gt;throw the index away and
rebuild&lt;/strong&gt; — the exact same wipe-and-reimport path the store already runs when a
first-boot import is interrupted. The corruption surface and the crash-recovery
surface turned out to be the same surface, and the mitigation was already built.
That’s not luck; it’s the payoff of never letting derived state pretend to be
primary.&lt;/p&gt;
&lt;p&gt;The real fix, of course, was upstream: the rotation name-collision was
root-caused and patched in ScrivaDB, and warden pinned the fixed version. But
the reason a rotation bug in the storage engine cost a restart and not a fleet
is the cache/source-of-truth split.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-lessons&quot;&gt;The lessons&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Two of them, and the first is the one I’d tattoo on a storage layer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Append-only protects the data, not the index.&lt;/strong&gt; “We never overwrite, so
we can’t corrupt” is only true of the records. Every derived structure laid
over them — the index, the rotation bookkeeping, the compaction state — is a
fresh corruption surface with none of the append guarantee. Audit those
paths as if they were mutable, because they are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rotation and compaction are where the bugs wait.&lt;/strong&gt; They’re the code that
only runs once the store is big &lt;em&gt;and&lt;/em&gt; old — invisible in every test that
starts from empty. If you write a log-structured store, force a rotation in
your tests, then reopen and assert every key still resolves. The bug that
ate my fleet would have surfaced the instant a test crossed the threshold and
reopened.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And one structural rule that turned a scary bug into a boring one:&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Keep a dumb, durable source of truth under your fast store.&lt;/strong&gt; If your
performant on-disk format can be wiped and rebuilt from something simpler,
corruption is a rebuild, not a catastrophe. warden’s session store can always
fall back to plain JSON; that single decision is why “the database said zero”
ended in a restart instead of a support thread.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A store returning zero is a valid answer to a question you didn’t mean to ask.
Build the layer underneath so that when it does, you can just rebuild and move
on.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;warden is an open-source Go binary for running a fleet of coding agents from
one cockpit. The storage model — an embedded append-only store over a plain-JSON
source of truth — is described in the
&lt;a href=&quot;https://srjn45.github.io/warden/concepts/architecture/&quot;&gt;architecture docs&lt;/a&gt;, or grab it from
&lt;a href=&quot;https://github.com/srjn45/warden&quot;&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>go</category><category>storage</category><category>internals</category><category>debugging</category></item><item><title>Running a fleet of coding agents without losing your mind</title><link>https://srjn45.github.io/warden/blog/running-a-fleet-of-claude-code-agents/</link><guid isPermaLink="true">https://srjn45.github.io/warden/blog/running-a-fleet-of-claude-code-agents/</guid><description>I kept spawning more coding agents than I could track. So I built warden — a single Go binary that spawns, watches, and prices a whole fleet from one cockpit, whatever agent backend you run.

</description><pubDate>Sat, 27 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago I hit a wall that I suspect a lot of people are about to hit.&lt;/p&gt;
&lt;p&gt;I’d gotten good at running coding agents (Claude Code, in my case). So good that
one agent was no longer enough. I’d kick off a refactor in one terminal, a test-writing pass in another,
a PR review in a third. Then a fourth. Then I’d &lt;code dir=&quot;auto&quot;&gt;cmd+tab&lt;/code&gt; into a window and have
no idea which task it was, whether it was working or quietly waiting for me to
approve a &lt;code dir=&quot;auto&quot;&gt;db:migrate&lt;/code&gt;, how much context it had burned, or what it had cost me.&lt;/p&gt;
&lt;p&gt;I was spending more energy &lt;em&gt;managing the agents&lt;/em&gt; than the agents were saving me.
The thing that was supposed to give me leverage had turned into a second job:
human tmux scheduler.&lt;/p&gt;
&lt;p&gt;So I built &lt;strong&gt;warden&lt;/strong&gt; — a single Go binary that runs a fleet of coding
agents and gives you exactly one place to see all of them. It started Claude-only;
it now drives &lt;a href=&quot;https://srjn45.github.io/warden/concepts/agent-backends/&quot;&gt;multiple agent backends&lt;/a&gt;, Claude
Code being the default.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://srjn45.github.io/warden/media/hero.gif&quot; alt=&quot;warden in action: spawning and monitoring a fleet of coding agents from one cockpit&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-core-idea-agents-are-cattle-not-pets&quot;&gt;The core idea: agents are cattle, not pets&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The mental shift that made everything click was treating agent sessions as
disposable, observable units of work — not precious terminal windows I had to
babysit one at a time.&lt;/p&gt;
&lt;p&gt;In warden, you &lt;em&gt;spawn&lt;/em&gt; an agent against a task and walk away:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;warden&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;review the auth module for security issues&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Every write-type agent gets &lt;strong&gt;its own isolated git worktree&lt;/strong&gt; by default, so I
can have three agents editing the same repo in parallel and they never collide
on the same tree. That one decision removed a whole category of “wait, who
touched this file?” panic.&lt;/p&gt;
&lt;p&gt;Then I watch the whole fleet from a cockpit instead of from eight terminals.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-cockpit-eight-agents-one-screen&quot;&gt;The cockpit: eight agents, one screen&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;warden tui&lt;/code&gt; is a tmux-composited cockpit — the agents list grouped by
directory, an Approvals row for anything blocked on me, a shell, and the
selected agent’s &lt;em&gt;live&lt;/em&gt; session in a full-height pane. I drive the entire fleet
without leaving it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://srjn45.github.io/warden/media/tui-cockpit.gif&quot; alt=&quot;The warden TUI cockpit: a live agents list grouped by directory, an Approvals row, a shell, and the selected agent&amp;#x27;s session in the detail pane&quot;&gt;&lt;/p&gt;
&lt;p&gt;If I’m not at my terminal, the same fleet is a self-hosted web dashboard —
Fleet summary, grouped agents, and a Metrics tab with per-agent and fleet-total
CPU/memory:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://srjn45.github.io/warden/media/web-monitor.gif&quot; alt=&quot;The warden web dashboard: the Cockpit home, then the Metrics tab with per-agent and fleet-total CPU/memory charts&quot;&gt;&lt;/p&gt;
&lt;p&gt;No SaaS, no login, no telemetry. It’s a loopback REST API and an embedded
file-backed store on disk. Runs on my laptop or a box I SSH into. By default nothing leaves the
machine — the only outbound traffic is what you opt into (webhook/Slack alerts,
remote access, or &lt;code dir=&quot;auto&quot;&gt;--calibrate&lt;/code&gt;).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-part-i-didnt-expect-to-care-about-knowing-what-it-costs&quot;&gt;The part I didn’t expect to care about: knowing what it costs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you run one agent, cost is abstract. When you run a fleet, it’s a line
item. &lt;code dir=&quot;auto&quot;&gt;warden spend&lt;/code&gt; prices each agent’s &lt;em&gt;real&lt;/em&gt; model usage into dollars and
gives me a budget gate, so a runaway agent doesn’t quietly run up a bill while
I’m in a meeting. (Dollar pricing covers the Claude backend today; bring-your-own-model
backends report tokens.)&lt;/p&gt;
&lt;p&gt;The flip side is &lt;code dir=&quot;auto&quot;&gt;warden savings&lt;/code&gt; — an append-only ledger of the tokens warden
keeps &lt;em&gt;out&lt;/em&gt; of agents’ context (the lifecycle plumbing that would otherwise
bloat every prompt), with a without-vs-with A/B benchmark. It turns “this feels
more efficient” into a number I can actually point at.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;an-agent-can-drive-the-whole-fleet&quot;&gt;An agent can drive the whole fleet&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the part that surprised people I showed it to. &lt;code dir=&quot;auto&quot;&gt;warden mcp&lt;/code&gt; exposes the
whole fleet as MCP tools. So an &lt;em&gt;orchestrator&lt;/em&gt; agent session can spawn agents,
query their status, and talk to a specific running agent — one agent managing a
team of agents (a Claude session managing Claudes, say), with warden as the
substrate. You’re not limited to driving it by hand.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-one-binary-no-database&quot;&gt;Why one binary, no database&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every design decision bent toward “you should be able to try this in 60 seconds
and trust it with your repos”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One Go binary&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;warden&lt;/code&gt;, aliased &lt;code dir=&quot;auto&quot;&gt;wd&lt;/code&gt;). No runtime, no containers required.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A local daemon&lt;/strong&gt; as the single writer to an embedded, file-backed store. No
Postgres, no Redis, no cloud account — no database server at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-hosted and free&lt;/strong&gt;, Apache-2.0. It’s your machine and your data.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/srjn45/warden/cmd/warden@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;warden&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--yes&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# install missing deps (tmux, claude, …)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;warden&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tutorial&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# guided tour: spawn → watch → commit → tear down&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;warden&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;your first task here&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;warden&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tui&lt;/span&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;# open the cockpit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;where-its-going&quot;&gt;Where it’s going&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;warden is open source and I’m building it in the open. If you’re running more
than one coding agent at a time — or you’re about to — I’d love for you to
try it and tell me where it falls short.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/srjn45/warden&quot;&gt;https://github.com/srjn45/warden&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs &amp;#x26; guide:&lt;/strong&gt; &lt;a href=&quot;https://srjn45.github.io/warden/start/what-is-warden/&quot;&gt;the rest of this site&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’ve felt that same “I have too many agents and no cockpit” feeling, this
was built for exactly that.&lt;/p&gt;</content:encoded><category>multi-agent</category><category>claude-code</category><category>orchestration</category><category>go</category></item></channel></rss>