<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aviv Laufer</title>
    <description>The latest articles on DEV Community by Aviv Laufer (@avivl).</description>
    <link>https://dev.to/avivl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F34647%2F9e6962c5-8187-44f5-a7da-a1bd4447cced.png</url>
      <title>DEV Community: Aviv Laufer</title>
      <link>https://dev.to/avivl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avivl"/>
    <language>en</language>
    <item>
      <title>Close the laptop, the agent keeps working</title>
      <dc:creator>Aviv Laufer</dc:creator>
      <pubDate>Thu, 15 Oct 2026 13:01:37 +0000</pubDate>
      <link>https://dev.to/avivl/close-the-laptop-the-agent-keeps-working-34pg</link>
      <guid>https://dev.to/avivl/close-the-laptop-the-agent-keeps-working-34pg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part 2 of My Agentic Setup. &lt;a href="https://dev.to/avivl/i-talk-to-one-agent-it-runs-the-others-26k4"&gt;Start from part 1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;When an agent runs on my laptop, it stops when I close the laptop. That is fine for a five minute task. It is not fine for an audit that takes an hour, or a crew of agents working through a backlog.&lt;/p&gt;

&lt;p&gt;So my long agent work runs on a cloud VM, inside tmux. The laptop is only a window into it.&lt;/p&gt;

&lt;p&gt;The whole thing is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agent-session.sh my-vm &lt;span class="nt"&gt;--gcloud&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm on the VM. I start &lt;code&gt;omp&lt;/code&gt;, give it work, and close the laptop. Tomorrow I run the same command, from the same laptop or the other one, and I'm back in the same place. The agent kept going while I was away.&lt;/p&gt;

&lt;p&gt;The idea is simple: the agent runs on the VM inside tmux, and the laptop is only a window, so one command starts the work or brings me back to it from any machine.&lt;/p&gt;

&lt;p&gt;Getting there was less simple. Here is what broke first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The VM was too small
&lt;/h3&gt;

&lt;p&gt;My first VM was a 1 GB e2-micro. omp got killed by the OOM killer while it was generating its shell completions. Not while running an agent, just while setting up the shell.&lt;/p&gt;

&lt;p&gt;Small doesn't mean slow here. It means broken. I moved to a larger VM, and now the troubleshooting table in my docs has this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a tool is killed with exit 137    out of memory; check free -h, resize the VM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The login never finished
&lt;/h3&gt;

&lt;p&gt;Every agent CLI logs in on each machine separately. My laptop's logins don't carry over to the VM, and I don't want them to.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;claude login&lt;/code&gt; opens a browser flow and then waits for the callback on &lt;code&gt;http://localhost:54545/callback&lt;/code&gt;. On the VM, that is the VM's localhost. The browser is on my laptop. It authorizes, redirects to port 54545 on the laptop, finds nothing there, and the login never completes. The browser just spins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv6umv92b6edyum9ag7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv6umv92b6edyum9ag7n.png" alt="Why the login never finished: without --forward-auth the browser redirects to the laptop's localhost, where nothing listens. With --forward-auth an ssh tunnel carries the callback to the VM, where claude login is waiting." width="800" height="1481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is to forward the callback ports back over ssh. The script has a flag for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ports the agent CLIs listen on for their OAuth callback. Forwarded so the&lt;/span&gt;
&lt;span class="c"&gt;# browser on this laptop can reach a login running on the VM.&lt;/span&gt;
&lt;span class="nv"&gt;AUTH_PORTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"54545 54546 8976"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FORWARD_AUTH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  for &lt;/span&gt;port &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$AUTH_PORTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;forward_args+&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:localhost:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;done
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the first time on a new VM I connect with &lt;code&gt;--forward-auth&lt;/code&gt;, then run &lt;code&gt;claude login&lt;/code&gt; and &lt;code&gt;cursor-agent login&lt;/code&gt; inside the session. When a tool has a device code flow, like &lt;code&gt;gh auth login --web&lt;/code&gt;, I use that instead. It needs no tunnel at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Unknown scope: use&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Even with the tunnel, a login can fail with &lt;code&gt;Unknown scope: use&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The cause: the login URL was in a boxed TUI panel. The panel wraps the long URL, and copying the visible lines cuts off the end of the scope list. The browser gets a broken URL.&lt;/p&gt;

&lt;p&gt;The fix: copy the link from a plain, wide terminal. Or send the output to a file and take the URL from there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude login 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;tee&lt;/span&gt; /tmp/login.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quoting in &lt;code&gt;--run&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I wanted to start a job without attaching first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agent-session.sh my-vm &lt;span class="nt"&gt;--gcloud&lt;/span&gt; &lt;span class="nt"&gt;--run&lt;/span&gt; &lt;span class="s1"&gt;'omp "audit the auth module and report back"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command goes through three layers: ssh, the shell on the VM, and tmux. Each one has its own quoting rules. A command with quotes inside quotes doesn't survive all three.&lt;/p&gt;

&lt;p&gt;The fix is to not quote it at all. The script base64-encodes the command on the laptop and decodes it on the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The command crosses ssh, the remote shell and tmux, each with its own&lt;/span&gt;
&lt;span class="c"&gt;# quoting. base64 is the one encoding that survives all three unchanged.&lt;/span&gt;
&lt;span class="nv"&gt;encoded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tmux new-session -A -d -s &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;session_q&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
        tmux new-window -t &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;session_q&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sh -lc 'eval &lt;/span&gt;&lt;span class="se"&gt;\\\"\\\$&lt;/span&gt;&lt;span class="s2"&gt;(printf %s &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;encoded&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | base64 -d)&lt;/span&gt;&lt;span class="se"&gt;\\\"&lt;/span&gt;&lt;span class="s2"&gt;; exec &lt;/span&gt;&lt;span class="se"&gt;\\\$&lt;/span&gt;&lt;span class="s2"&gt;SHELL -l'&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
        tmux attach -t &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;session_q&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not pretty, but base64 has only letters, digits, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;=&lt;/code&gt;, so none of the three layers has anything to escape.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;exec $SHELL -l&lt;/code&gt; at the end matters too. When the agent finishes, the window stays open with a shell in it, so the final answer is still on screen when I come back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the script on the VM itself
&lt;/h3&gt;

&lt;p&gt;The script is meant to run on the laptop. Run it on the VM by mistake, and &lt;code&gt;gcloud compute ssh&lt;/code&gt; tries to ssh from the VM to itself. It generates a new SSH key there and fails on permissions the VM's own service account doesn't have.&lt;/p&gt;

&lt;p&gt;The fix is a hostname check. If I'm already on the target machine, the script skips ssh and just attaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;%%.*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;tmux new-session &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same command does the right thing from both places.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;command not found: omp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This one happens on a fresh VM. You connect, tmux starts, then you install the tools. Inside the session, &lt;code&gt;omp&lt;/code&gt; is not found.&lt;/p&gt;

&lt;p&gt;The tmux server started before the tools were installed, so it still has the old PATH, and every new window gets that old PATH too. Reconnecting doesn't help, because the server is still running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux kill-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smaller ones
&lt;/h3&gt;

&lt;p&gt;Some issues came from setting up the VM, not from running agents on it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimal Debian image has no &lt;code&gt;make&lt;/code&gt;, and my setup runs &lt;code&gt;make&lt;/code&gt;. It is in the first install step now.&lt;/li&gt;
&lt;li&gt;Debian ships Node 18. Some of my npm tools need 22. Linux takes Node from mise instead of apt.&lt;/li&gt;
&lt;li&gt;The ssh client forwards a locale the VM didn't generate, which prints a &lt;code&gt;setlocale&lt;/code&gt; warning on every login. It is harmless. Installing the locale on the VM, or not forwarding it, makes it go away.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The VM comes from the same dotfiles
&lt;/h3&gt;

&lt;p&gt;The VM is not special. It gets the same dotfiles as my laptops, with a Linux layer on top. This is the whole OS profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# profiles/os/linux.toml&lt;/span&gt;
&lt;span class="c"&gt;# Linux defaults, targeting headless Debian and Ubuntu LTS hosts.&lt;/span&gt;

&lt;span class="py"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"linux"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[shell]&lt;/span&gt;
&lt;span class="py"&gt;ssh_use_keychain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[packages]&lt;/span&gt;
&lt;span class="py"&gt;packs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"agents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"formal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"linux"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cli&lt;/code&gt; without &lt;code&gt;desktop&lt;/code&gt; keeps desktop applications off a headless machine. &lt;code&gt;ssh_use_keychain = false&lt;/code&gt; matters because &lt;code&gt;UseKeychain&lt;/code&gt; is a macOS-only SSH option, and on Linux it makes ssh reject the whole config file.&lt;/p&gt;

&lt;h3&gt;
  
  
  The agents pack
&lt;/h3&gt;

&lt;p&gt;The agent tools come from a package pack. On macOS most of them are Homebrew formulas. On Linux most don't have an apt package, so each row has an upstream installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[package]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"can1357/tap/omp"&lt;/span&gt;
&lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;macos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"brew"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;linux&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"upstream"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;binary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"omp"&lt;/span&gt;
&lt;span class="py"&gt;install&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;linux&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"...curl the release binary into ~/.local/bin..."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nn"&gt;[[package]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-code"&lt;/span&gt;
&lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;macos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"upstream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;linux&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"upstream"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;install&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"curl -fsSL https://claude.ai/install.sh | bash"&lt;/span&gt;
&lt;span class="py"&gt;binary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude"&lt;/span&gt;

&lt;span class="nn"&gt;[[package]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"node"&lt;/span&gt;
&lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;macos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"brew"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;linux&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"upstream"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;min_major&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
&lt;span class="py"&gt;install&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;linux&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mise use -g node@lts"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bootstrap script shows each upstream command and asks before running it. It also exits non-zero until everything is installed, so a half-installed VM doesn't look finished.&lt;/p&gt;

&lt;p&gt;What runs on the VM today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does there&lt;/th&gt;
&lt;th&gt;Installed on Linux by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tmux&lt;/td&gt;
&lt;td&gt;keeps sessions alive&lt;/td&gt;
&lt;td&gt;apt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;omp&lt;/td&gt;
&lt;td&gt;my main agent&lt;/td&gt;
&lt;td&gt;release binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;crew agent&lt;/td&gt;
&lt;td&gt;its install script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor agent&lt;/td&gt;
&lt;td&gt;crew agent&lt;/td&gt;
&lt;td&gt;its install script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firstmate&lt;/td&gt;
&lt;td&gt;runs the crew&lt;/td&gt;
&lt;td&gt;git clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worktrunk&lt;/td&gt;
&lt;td&gt;worktrees&lt;/td&gt;
&lt;td&gt;release tarball&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;for the npm tools&lt;/td&gt;
&lt;td&gt;mise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  New machine, in order
&lt;/h3&gt;

&lt;p&gt;Setting up a VM follows the same eight steps as a laptop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Base tools: git, ssh, make, Python, chezmoi&lt;/li&gt;
&lt;li&gt;A new SSH key for this machine, added to GitHub&lt;/li&gt;
&lt;li&gt;Clone the dotfiles&lt;/li&gt;
&lt;li&gt;A dry run into a fake home&lt;/li&gt;
&lt;li&gt;Declare the machine in a profile&lt;/li&gt;
&lt;li&gt;Install the software&lt;/li&gt;
&lt;li&gt;Install the configuration&lt;/li&gt;
&lt;li&gt;Log in to each tool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Configuration and software never mix. Step 7 installs no packages, and step 6 changes no configuration. Logins are always last, and always per machine. Nothing is copied from another machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the script works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One tmux command does most of the work
&lt;/h3&gt;

&lt;p&gt;The whole idea is one tmux flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tmux new-session -A -s &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; %q &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;ssh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;new-session -A&lt;/code&gt; attaches to the session if it exists and creates it if it doesn't. So the first connection and every connection after it are the same command.&lt;/p&gt;

&lt;p&gt;ssh is only the transport. &lt;code&gt;-t&lt;/code&gt; gives it a terminal, because tmux refuses to attach without one.&lt;/p&gt;

&lt;p&gt;On GCP the script uses &lt;code&gt;gcloud compute ssh&lt;/code&gt; with &lt;code&gt;--tunnel-through-iap&lt;/code&gt;. My VM has no external IP, so IAP is how I reach it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fikayok6sixdyfwqdnoih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fikayok6sixdyfwqdnoih.png" alt="One session, any laptop: the work laptop and the personal laptop both connect over ssh with IAP to my-vm, which is always on. Its tmux server has a session called agents with omp in window 1 and fm payments in window 2, and a session called review with claude." width="800" height="1259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Less typing
&lt;/h3&gt;

&lt;p&gt;The machine, project and zone are the same every time, so I set them once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc.local&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AGENT_SESSION_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-vm
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AGENT_SESSION_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-project
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AGENT_SESSION_ZONE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-zone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agent-session.sh &lt;span class="nt"&gt;--gcloud&lt;/span&gt;            &lt;span class="c"&gt;# connect&lt;/span&gt;
agent-session.sh &lt;span class="nt"&gt;--gcloud&lt;/span&gt; &lt;span class="nt"&gt;--list&lt;/span&gt;     &lt;span class="c"&gt;# what is running&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--list&lt;/code&gt; is my dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;billing: 1 windows (created Mon Sep 14 09:57:17 2026)
payments: 1 windows (created Mon Sep 14 09:56:16 2026)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sessions, windows and tabs
&lt;/h3&gt;

&lt;p&gt;These three are easy to mix up, so here is how I use each one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A tmux window&lt;/strong&gt; (&lt;code&gt;Ctrl-b c&lt;/code&gt;, or &lt;code&gt;fm &amp;lt;project&amp;gt;&lt;/code&gt;): jobs I want to watch together. &lt;code&gt;Ctrl-b w&lt;/code&gt; picks one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A tmux session&lt;/strong&gt; (&lt;code&gt;--session &amp;lt;name&amp;gt;&lt;/code&gt;): jobs I want kept apart, each one reattachable on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Ghostty tab&lt;/strong&gt;: only a way to see two sessions at the same time. Closing it changes nothing on the VM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing surprises people. Two Ghostty tabs attached to the same session show the same screen, live, with both cursors. That is nice for watching a long run on a second monitor, and useless for doing two things at once. Different work needs &lt;code&gt;--session&lt;/code&gt;, not another tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  What survives
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Closing the laptop, losing wifi, disconnecting: keeps running.&lt;/li&gt;
&lt;li&gt;Connecting from the other laptop: same sessions, same screens.&lt;/li&gt;
&lt;li&gt;Two laptops connected at once: both see the same screen.&lt;/li&gt;
&lt;li&gt;Rebooting the VM: the agents stop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The VM is always on, so the last one almost never happens. I have tmux-resurrect and tmux-continuum installed, so if the VM does reboot, the sessions and windows come back. An agent that was in the middle of its work doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I am now
&lt;/h2&gt;

&lt;p&gt;These days I mostly use herdr for this. It does the same job as tmux on the VM, with its own way of saving machines and reconnecting. Use one or the other, not both: herdr inside tmux makes their keyboard shortcuts fight.&lt;/p&gt;

&lt;p&gt;The pattern is the same either way. The agent runs on a machine that stays on, and the laptop is only a window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The companion repo has a sanitized copy of &lt;code&gt;agent-session.sh&lt;/code&gt; and a test suite. The tests run tmux on a private socket with fake &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;gcloud&lt;/code&gt;, so they don't need a VM and don't touch your own tmux server. They check the &lt;code&gt;--run&lt;/code&gt; quoting with quotes, &lt;code&gt;$&lt;/code&gt; and backticks, the local attach, &lt;code&gt;--forward-auth&lt;/code&gt; and the IAP flags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;02-remote-agents
make &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/avivl/golem-notes-examples/tree/main/02-remote-agents" rel="noopener noreferrer"&gt;golem-notes-examples/02-remote-agents&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where do your long agent runs live: your laptop, a VM, something else? Leave a comment.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part 2 of my series on running a real agentic dev setup. I publish each part first on Golem Notes, with the full write-up and what's coming next. Subscribe there to get the next one in your inbox: &lt;a href="https://golemnotes.substack.com/p/close-the-laptop-the-agent-keeps?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=agentic-setup-02" rel="noopener noreferrer"&gt;Golem Notes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>productivity</category>
      <category>tmux</category>
    </item>
    <item>
      <title>I talk to one agent. It runs the others.</title>
      <dc:creator>Aviv Laufer</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:49:31 +0000</pubDate>
      <link>https://dev.to/avivl/i-talk-to-one-agent-it-runs-the-others-26k4</link>
      <guid>https://dev.to/avivl/i-talk-to-one-agent-it-runs-the-others-26k4</guid>
      <description>&lt;p&gt;&lt;em&gt;The map of my setup: OMP in the middle, the tools it dispatches, and the 13 parts coming next.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most of my coding day happens in one terminal pane. I type into OMP, and OMP does the rest.&lt;/p&gt;

&lt;p&gt;It picks a model for each job. Planning goes to Opus. Normal turns go to Sonnet. Reviews go to a GPT model, so the reviewer is never the same model that wrote the code. When the work is big, OMP hands it to a fleet: Firstmate spawns crew agents, each one in its own worktree, and most of the time the crew is working and I'm not.&lt;/p&gt;

&lt;p&gt;I use several coding agents, but I don't configure them separately. One repo decides which rules, skills, identities and models every agent gets, on every machine.&lt;/p&gt;

&lt;p&gt;That repo is my dotfiles. It started in December 2016 with a tmux and vim config. In the last three months, since agents took over my workflow, it got almost as many commits as in the nine years before.&lt;/p&gt;

&lt;p&gt;This post is the map: every tool, how they connect, what broke when I first wired them together, and the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The map
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         me
                          |
                          v
   +---------------------------------------------+
   |  OMP  (my main tool, one model per role)    |
   +---------------------------------------------+
            |                        |
            | talks to models        | fm &amp;lt;project&amp;gt;
            | directly               v
            |              +----------------------+
            |              |  Firstmate           |
            |              |  (first mate on OMP) |
            |              +----------------------+
            |                 |   dispatch rules
            v                 v
   Anthropic, Cursor,     crew: OMP, Claude Code,
   OpenAI, local model    Codex, Grok
                              |
                              v
                     treehouse worktrees
                     (one per task, pooled)

   herdr on the laptop, tmux on the VM: every agent gets a pane
   dotfiles: renders the config for all of the above
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs on two laptops and a cloud VM. My work laptop gets the company rules: our skills, our ticket conventions, our compliance rules. My personal laptop should not see any of that, and the two use different model accounts.&lt;/p&gt;

&lt;p&gt;I'll go through each box below. First, the part that went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;OMP is where I type, but it is not the only agent that runs. Firstmate starts Claude Code, Codex or Grok as crew, and Cursor agent is there too. Each of these tools reads its instructions from a different place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/CLAUDE.md
~/.codex/AGENTS.md
~/.pi/agent/AGENTS.md
~/.agents/AGENTS.md
~/.gemini/GEMINI.md
~/.config/opencode/AGENTS.md
~/.copilot/copilot-instructions.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven files that should say the same thing. Skills have the same problem: each tool has its own skills folder.&lt;/p&gt;

&lt;p&gt;Agents read their instructions in every session, so when those instructions drift, the agents drift with them. Nothing warns you.&lt;/p&gt;

&lt;p&gt;Two things were wrong.&lt;/p&gt;

&lt;p&gt;First, the files had drifted. My &lt;code&gt;~/.codex/AGENTS.md&lt;/code&gt; was a copy I'd made by hand in May, and it was out of date. Pi, another agent I had installed, saw only 3 of my 23 skills. Each tool was working with a different set of rules, and I had assumed they were all the same.&lt;/p&gt;

&lt;p&gt;Second, the instructions were too long. The global file had grown to 465 lines, about 21 KB or 5,300 tokens. It loads on every turn, in Claude Code and each of its subagents, in OMP, Codex, Pi, Gemini, OpenCode and Copilot. So every one of those paid for all 465 lines, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One source, compiled per machine
&lt;/h3&gt;

&lt;p&gt;I split the instructions into small fragments, one topic per file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agents/fragments/
  00-header.md
  05-no-emojis.md
  10-voice.md
  20-workflow.md
  30-git-conventions.md
  32-worktrees-stacking.md
  35-pull-requests.md
  45-core-principles.md
  ...
  50-work-skills.md     # work only
  55-compliance.md      # work only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each machine profile is a plain list of the fragments it gets. The personal profile leaves out the work-only ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# agents/machines/personal.txt
00-header.md
05-no-emojis.md
20-workflow.md
35-pull-requests.md
32-worktrees-stacking.md
45-core-principles.md
30-git-conventions.md
40-languages.md
10-voice.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;make build&lt;/code&gt; puts them together into one flat &lt;code&gt;AGENTS.md&lt;/code&gt;, with a marker before each fragment so I can see where a rule came from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;while&lt;/span&gt; &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; name&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="err"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"$name"&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="s1"&gt;'#'&lt;/span&gt;&lt;span class="err"&gt;*)&lt;/span&gt; &lt;span class="err"&gt;continue&lt;/span&gt; &lt;span class="err"&gt;;;&lt;/span&gt; &lt;span class="err"&gt;esac&lt;/span&gt;
  &lt;span class="nl"&gt;printf '&amp;lt;!-- Source&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fragments/%s --&amp;gt;&lt;/span&gt;\n&lt;span class="nf"&gt;' "$name"&lt;/span&gt;
  &lt;span class="err"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"fragments/$name"&lt;/span&gt;
&lt;span class="err"&gt;done&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt; &lt;span class="err"&gt;machines/$(PROFILE).txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why flat and not includes? Codex, Pi and Cursor don't expand any include syntax. So the modular part lives in the source, and every tool gets one plain file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;make deploy&lt;/code&gt; then symlinks that one file into all seven paths. There are no copies left to go stale. When I edit a fragment and run &lt;code&gt;make build&lt;/code&gt;, every tool picks it up in its next session.&lt;/p&gt;

&lt;h3&gt;
  
  
  A check that blocks drift
&lt;/h3&gt;

&lt;p&gt;A pre-commit hook runs &lt;code&gt;make check&lt;/code&gt;. It builds again into a temp folder and diffs the result against the built file. If I edit a fragment and forget to build, the commit fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check: build/personal/AGENTS.md is out of date. Run: make -C agents build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One skills folder
&lt;/h3&gt;

&lt;p&gt;Skills go in one place, &lt;code&gt;~/.agents/skills/&lt;/code&gt;. When I checked in July, Codex, Cursor, OpenCode, Gemini and Copilot all read that folder directly. Claude Code doesn't, so &lt;code&gt;make skills-sync&lt;/code&gt; adds a symlink per skill into &lt;code&gt;~/.claude/skills/&lt;/code&gt;. It does the same for Pi's own folder, which is where the 3-of-23 problem was.&lt;/p&gt;

&lt;p&gt;It never overwrites a real file. A skill installed some other way gets skipped with a warning, not deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I could cut it in half
&lt;/h2&gt;

&lt;p&gt;With one file, I could finally see everything in one place. So I cut it. 465 lines down to 215. About 5,300 tokens down to 2,500.&lt;/p&gt;

&lt;p&gt;The biggest cut was the pull request procedure. I moved it, as is, into a skill. It loads only when an agent works on a PR. The fragment kept only the hard rules: draft only, no merge and no deploy without my approval. The rest of the fragments I trimmed down to the rules, without the explanations.&lt;/p&gt;

&lt;p&gt;With seven copies, I never cut anything. I didn't know which copy was the real one, so I only added. With one source, cutting is safe.&lt;/p&gt;

&lt;p&gt;Now I split instructions into two kinds. Rules every agent needs on every turn go in the global file, and I keep it short. Procedures for one kind of task go in a skill. An agent renaming a variable doesn't need my PR procedure, and it shouldn't pay for it in tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the map
&lt;/h2&gt;

&lt;p&gt;With that in place, here is what each box in the diagram does.&lt;/p&gt;

&lt;h3&gt;
  
  
  OMP
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/can1357/oh-my-pi" rel="noopener noreferrer"&gt;OMP&lt;/a&gt; (Oh My Pi) is a coding agent that talks to the model APIs directly. I log in to Anthropic, Cursor and OpenAI once, and OMP routes each kind of work to a model through roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;modelRoles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cursor/claude-sonnet-5-5:medium&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;cursor/claude-opus-5-5:medium&lt;/span&gt;
  &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;cursor/claude-sonnet-5-5:medium&lt;/span&gt;
  &lt;span class="na"&gt;slow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;cursor/composer-2.5:high&lt;/span&gt;
  &lt;span class="na"&gt;smol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;cursor/gpt-5.6-luna:medium&lt;/span&gt;
  &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;cursor/gpt-5.6-luna:low&lt;/span&gt;
  &lt;span class="na"&gt;tiny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;local/lfm2.5-230m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reviewer is set in a separate block of the same config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;agentModelOverrides&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reviewer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cursor/gpt-5.6-terra:high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's my personal laptop. It only has Claude and Cursor accounts, so almost everything goes through Cursor. The work laptop uses the same roles with different providers, and dotfiles picks the right ones per machine.&lt;/p&gt;

&lt;p&gt;Model names change every few months, so here is the current table. I'll keep it up to date. The pattern is what matters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Current model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;normal turns&lt;/td&gt;
&lt;td&gt;Claude Sonnet 5.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan&lt;/td&gt;
&lt;td&gt;planning&lt;/td&gt;
&lt;td&gt;Claude Opus 5.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task&lt;/td&gt;
&lt;td&gt;subagent work&lt;/td&gt;
&lt;td&gt;Claude Sonnet 5.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;slow&lt;/td&gt;
&lt;td&gt;hard fixes&lt;/td&gt;
&lt;td&gt;Composer 2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reviewer&lt;/td&gt;
&lt;td&gt;code review, different vendor than the author&lt;/td&gt;
&lt;td&gt;GPT-5.6 Terra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smol&lt;/td&gt;
&lt;td&gt;cheap fan-out&lt;/td&gt;
&lt;td&gt;GPT-5.6 Luna&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;commit&lt;/td&gt;
&lt;td&gt;commit messages&lt;/td&gt;
&lt;td&gt;GPT-5.6 Luna, low effort&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tiny&lt;/td&gt;
&lt;td&gt;throwaway calls&lt;/td&gt;
&lt;td&gt;a 230M local model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;judge&lt;/td&gt;
&lt;td&gt;"is this actually done?"&lt;/td&gt;
&lt;td&gt;a small model on Cloudflare Workers AI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Firstmate
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/kunchenguid/firstmate" rel="noopener noreferrer"&gt;Firstmate&lt;/a&gt; runs a fleet of agents. I'm the captain. The first mate is an OMP session that takes my ask, writes a brief and starts crew agents to do the work. &lt;code&gt;fm &amp;lt;project&amp;gt;&lt;/code&gt; opens it for one project, and each project gets its own home, so backlogs and running tasks never mix.&lt;/p&gt;

&lt;p&gt;The crew is OMP by default, but a few dispatch rules send work to other tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The task is a trivial mechanical edit: rote rename, formatting sweep, targeted typo fix, dependency bump, or gathering files."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"harness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"effort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The task is a scout: investigate, audit, trace a bug, or produce a standalone report without changing code."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"harness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"omp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"effort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My preferences for the first mate live in a markdown file it reads at the start of every session. A few lines from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; Never use em dashes in PR titles, PR bodies, commit messages, or comments.
&lt;span class="p"&gt;-&lt;/span&gt; Every PR any worker opens is created in draft mode, no exceptions.
&lt;span class="p"&gt;-&lt;/span&gt; Escalate to me on any change touching auth, tenancy boundaries, data
  retention, or migration ordering, even when the tests are green.
&lt;span class="p"&gt;-&lt;/span&gt; Scout tasks end in a report, never a code change.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Worktrees: two tools on purpose
&lt;/h3&gt;

&lt;p&gt;When I start an agent myself, I use &lt;a href="https://github.com/max-sixty/worktrunk" rel="noopener noreferrer"&gt;worktrunk&lt;/a&gt; (&lt;code&gt;wt&lt;/code&gt;). One alias creates a branch, a worktree and an agent session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;worktree-path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"~/worktrees/{{ repo }}/{{ branch | sanitize }}"&lt;/span&gt;

&lt;span class="nn"&gt;[aliases]&lt;/span&gt;
&lt;span class="py"&gt;cc&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"wt switch --create {{ branch }} -x claude"&lt;/span&gt;
&lt;span class="py"&gt;cur&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"wt switch --create {{ branch }} -x wt-agent -- cursor-agent"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Firstmate starts a crew agent, it uses treehouse. Treehouse keeps a pool of worktrees per repo and leases one out per task (&lt;code&gt;treehouse get --lease&lt;/code&gt;, then &lt;code&gt;treehouse return&lt;/code&gt;). Two crew agents on the same repo get slot 1 and slot 2 and never collide. Most of the worktrees on my machine come from treehouse, because most of the time the crew is the one working.&lt;/p&gt;

&lt;h3&gt;
  
  
  herdr and tmux
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://herdr.dev" rel="noopener noreferrer"&gt;herdr&lt;/a&gt; is the terminal multiplexer on my laptops. Unlike tmux, it knows which pane is an agent and whether that agent is working, blocked, done or idle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[ui]&lt;/span&gt;
&lt;span class="py"&gt;agent_panel_sort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"spaces"&lt;/span&gt;     &lt;span class="c"&gt;# one space per repo/worktree&lt;/span&gt;
&lt;span class="py"&gt;status_indicators&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"symbols"&lt;/span&gt;   &lt;span class="c"&gt;# blocked/working/done/idle&lt;/span&gt;
&lt;span class="py"&gt;show_agent_labels_on_pane_borders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With several crew agents running, &lt;code&gt;ctrl+alt+up&lt;/code&gt; and &lt;code&gt;ctrl+alt+down&lt;/code&gt; jump between them. On the cloud VM I use plain tmux, so agents keep running after I close the laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dotfiles
&lt;/h3&gt;

&lt;p&gt;Everything above is rendered from the dotfiles repo, in layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base &amp;lt; os &amp;lt; organization &amp;lt; machine &amp;lt; local.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer only overrides the values it names. The work laptop declares the work organization and the personal one. The personal laptop declares only personal. A repo outside every declared root has no git identity at all, so a commit fails instead of going out under the wrong name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The companion repo has a small working version of the fragments build: four fragments (one of them work-only), two profiles, a pre-commit hook, and the Makefile with &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;check&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt;. By default it deploys into a fake home folder, so it never touches your real config. When you point it at your real home, it skips any real file in the way instead of overwriting it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make deploy
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; fake-home/.codex   &lt;span class="c"&gt;# AGENTS.md -&amp;gt; .../build/personal/AGENTS.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/avivl/golem-notes-examples/tree/main/01-the-map" rel="noopener noreferrer"&gt;golem-notes-examples/01-the-map&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's coming
&lt;/h2&gt;

&lt;p&gt;One part every two weeks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The map&lt;/strong&gt; (this post)&lt;/li&gt;
&lt;li&gt;Close the laptop, the agent keeps working: agents on a cloud VM with tmux&lt;/li&gt;
&lt;li&gt;My agents on two laptops, controlled from one phone app&lt;/li&gt;
&lt;li&gt;Every PR gets an agent review before a human sees it&lt;/li&gt;
&lt;li&gt;Captain, first mate, crew: orchestrating a fleet of agents&lt;/li&gt;
&lt;li&gt;Nine roles, not one model&lt;/li&gt;
&lt;li&gt;Right model for the task: dispatch rules and cheaper subagents&lt;/li&gt;
&lt;li&gt;When the credits run out: fallback chains&lt;/li&gt;
&lt;li&gt;A cheap judge for "is this actually done?"&lt;/li&gt;
&lt;li&gt;Teaching agents how I work: my skills library&lt;/li&gt;
&lt;li&gt;Dotfiles an agent can't break&lt;/li&gt;
&lt;li&gt;One laptop, many companies: identities and secrets&lt;/li&gt;
&lt;li&gt;Testing your dotfiles like production code&lt;/li&gt;
&lt;li&gt;What I'd do differently&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you run more than one agent tool, I'd like to know how you keep their rules in sync. Leave a comment.&lt;/p&gt;




&lt;p&gt;This is part 1 of my series on running a real agentic dev setup. I publish each part first on Golem Notes, with the full write-up and what's coming next. Subscribe there to get the next one in your inbox: &lt;a href="https://golemnotes.substack.com/p/i-talk-to-one-agent-it-runs-the-others?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=agentic-setup-01" rel="noopener noreferrer"&gt;https://golemnotes.substack.com/p/i-talk-to-one-agent-it-runs-the-others?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=agentic-setup-01&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>dotfiles</category>
    </item>
  </channel>
</rss>
