A macOS bootstrap that turns one Mac into a always-reachable host for
running named AI agent sessions with GitHub Copilot
CLI, Claude Code
CLI, or Codex CLI. Choose a
backend explicitly (agent-stack copilot alpha, agent-stack claude alpha,
or agent-stack codex alpha) and reach it from any device (phone, tablet, another
laptop) over Tailscale SSH
and tmux, with Termius as the terminal
client.
The end state: tap a host in Termius on your phone, land in the same agent CLI conversation your laptop left mid-thought.
This README is the full setup walkthrough — fresh Mac to phone to desktop. If you already have parts of this stack working, jump to the section you need from the table of contents.
- What you get
- How many agents?
- Prerequisites
- Part 1 — Server Mac setup
- Part 2 — iPhone (Termius iOS)
- Part 3 — Mac desktop (Termius)
- Daily use
- Configuration
- Uninstall
- Status & roadmap
-
A single multi-call wrapper (
bin/agent) installed asagent-stackplus the descriptive aliasescopilot-agent,claude-agent, andcodex-agent. It launches or reattaches to a named agent session. Keychain unlock,tmuxsession management, and best-effort resumption of the previous CLI session for that name (Copilot: stable UUID via--session-id; Claude:--continue; Codex:resume --lastwhen the workspace has history) are all handled.Each backend has its own workspace root and its own tmux session namespace, so the three never collide:
agent-stack copilot alpha→$COPILOT_WORKSPACE_BASE/agent-alpha, tmuxalphaagent-stack claude alpha→$CLAUDE_WORKSPACE_BASE/agent-alpha, tmuxclaude-alphaagent-stack codex alpha→$CODEX_WORKSPACE_BASE/agent-alpha, tmuxcodex-alpha
Copilot keeps its established unprefixed tmux names. Claude and Codex use backend prefixes, so the three namespaces do not collide. Matching pre-release
copilot-<name>sessions remain attachable but are not created. -
A one-shot installer for OS-level prerequisites: Homebrew,
tmux, Tailscale (CLI build), and the MagicDNS resolver fix that Homebrew's Tailscale formula leaves out. -
An optional
request_helpMCP tool for Copilot CLI, Claude Code, and Codex CLI. A blocked agent can send one bounded iMessage identified by its NATO-alphabet tmux session. The tool opens temporary Screen Sharing and includes a clickablescreens://link using the Mac's actual Tailscale name. -
Optional installation of
dfrysinger/dreamingas a headless Copilot learning and skill-curation service, isolated from normal interactive agent context. -
A symmetric uninstaller.
| Task | Command |
|---|---|
| Launch or reattach Copilot | agent-stack copilot <name> or copilot-agent <name> |
| Launch or reattach Claude | agent-stack claude <name> or claude-agent <name> |
| Launch or reattach Codex | agent-stack codex <name> or codex-agent <name> |
| Enable bounded Screen Sharing | agent-screen on [1-8] |
| Disable Screen Sharing | agent-screen off |
| Inspect Screen Sharing | agent-screen status |
Pick whatever count fits your workflow. The author runs about a dozen
in practice — named after the NATO phonetic
alphabet
(alpha, bravo, charlie, delta, echo, foxtrot, golf,
hotel, india, juliet, kilo, lima). The wrapper accepts any
non-option name without / and prepends agent- to derive a workspace directory, so
agent-stack copilot coordinator or claude-agent planner Just Work.
Names are case-sensitive end-to-end (tmux session, workspace
directory, Copilot CLI session name all match exactly what you type).
Pick a casing convention and stick with it. Lowercase is what we use.
- A Mac (Apple silicon, macOS Tahoe or newer; older versions probably work but aren't tested).
- A Tailscale account (the free tier covers everything here).
- Termius on every device you want to
drive agents from. Termius Pro is recommended for the snippet
features used in Part 2, but the
whole stack also works on Termius Free if you're willing to type
agent-stack copilot alphaafter each connect. - The direct-download Termius build on macOS — not the Mac App Store version, which is sandboxed and has no local-terminal feature. Download from https://termius.com/download.
This is the Mac that will host the agent sessions. Everything in this part runs once, on the Mac itself.
Auth model heads-up: this stack uses Tailscale SSH, which authenticates by tailnet identity — server-side, the SSH server uses auth method
noneand trusts the tailnet for identity. The username field on the host has to match a real macOS user that the ACL allows (autogroup:nonrootin the rule below means "any non-root user"). The password field can be left blank; if you do save a value, it's sent on the wire but ignored by the Tailscale SSH server.
If you already have a tailnet, skip ahead. Otherwise: sign up at
https://login.tailscale.com/start (free personal plan is enough),
then confirm MagicDNS is on at
https://login.tailscale.com/admin/dns (it's on by default for new
tailnets — just verify). The full Tailscale walkthrough is
docs/tailscale.md.
Amphetamine (free, Mac App Store) keeps the Mac from sleeping while still letting the display sleep, the screensaver lock the screen per MDM policy, etc.
Recommended config (Amphetamine menu → Quick Settings):
- Allow display sleep — ON
- Allow system sleep when display is closed — OFF (keeps the Mac awake even with the lid down on a clamshell setup)
- Allow screen saver after 45m of inactivity — ON (so MDM lock policy still fires)
- Start session at app launch — ON, set to Indefinitely
- Launch Amphetamine at login — ON (System Settings → General → Login Items)
The result: rebooting the Mac auto-resumes an indefinite caffeine
session, the display still sleeps and locks, but the system never
suspends — so the tailnet stays online and tmux sessions stay alive.
git clone https://github.com/dfrysinger/agent-stack \
~/code/agent-stack
cd ~/code/agent-stack
./install.shThe installer is idempotent — safe to re-run. It:
- Installs Homebrew (if missing),
tmux, and the Tailscale CLI build. - Writes
/etc/resolver/ts.net(the MagicDNS resolver fix). - Detects Copilot CLI, Claude Code, and Codex CLI, then asks which
should receive the optional
request_helptool. - Installs a root-owned Screen Sharing helper whose passwordless
commands are limited to
onfor one through eight hours andoff. A launchd watchdog enforces the deadline after logout, sleep, or reboot. - Symlinks
agent-stack,copilot-agent,claude-agent, andcodex-agentinto/usr/local/bin, all pointing at the same multi-call wrapper (bin/agent). During upgrade it removesca,cc,co, and transitionalremote-agentlinks only when they are owned by this checkout; foreign commands are preserved. - Symlinks
agent-screenfor bounded Screen Sharing. During upgrade it removes ownedssand transitionalremote-screenlinks; foreign commands are preserved. - Optionally installs
dfrysinger/skillsfor any selected backend anddfrysinger/dreamingas a dedicated headless Copilot service. - Stamps a managed block into
~/.tmux.confthat hides tmux's status bar (the agents are full-screen TUIs and your Termius tabs already label each session). Toggle it back per-session withprefix + b. Any hand-written~/.tmux.confyou already have is preserved. - Prints a checklist of the GUI / interactive steps it can't do for
you — Full Disk Access grants (next step),
tailscale up --ssh, first Copilot CLI auth prompt.
Upgrading from the old short commands: the installer intentionally retires owned
ca,cc,co, andsslinks. Update saved Termius startup snippets toagent-stack <backend> <name>. For an existing Mac Termius workspace, run the new command once in every pane and save the workspace again so autocomplete replays the replacement command. Useagent-screeninstead ofss. Any foreign command using those names is left untouched.
If you're driving the install over VNC / Screens / Termius —
anywhere the sudo prompt could be hard to see — use the GUI launcher
instead, which opens a fresh Terminal.app window where the password
prompt is obvious:
open ~/code/agent-stack/install-gui.command(Or double-click install-gui.command in Finder.)
tmux and tailscaled both need Full Disk Access on macOS Ventura+.
Grant FDA before running tailscale up --ssh — tailscaled
without FDA can flap offline and DNS goes intermittent.
The installer printed the exact Cellar paths to add. See
docs/fda-grants.md for the why and the
step-by-step. (After every brew upgrade tmux or brew upgrade tailscale, the version-numbered Cellar path changes and you'll need
to re-add — annoying but unavoidable.)
sudo tailscale up --sshOpen the auth URL it prints, sign into your Tailscale account, and
the Mac joins the tailnet. The --ssh flag tells tailscaled to bind
a Tailscale SSH server on :22 (replacing macOS's built-in Remote
Login, which on a managed Mac MDM keeps disabling anyway).
Gotcha — sandboxed GUI Tailscale.app: if you already have
/Applications/Tailscale.apprunning its owntailscaled(both the Mac App Store build and, since ~1.98, the standalone download from tailscale.com are sandboxed),tailscale up --sshrefuses with:The Tailscale SSH server does not run in sandboxed Tailscale GUI builds.
The installer detects this and prints the switchover steps. In short: quit the GUI (
osascript -e 'quit app "Tailscale"'), uninstall its system daemon (sudo /usr/local/bin/tailscaled uninstall-system-daemon), start Homebrew's version (sudo brew services start tailscale), then re-runsudo tailscale up --ssh --accept-routes(re-auth once because brew's tailscaled has its own state dir).
Tailscale SSH only binds :22 if your tailnet ACL contains at
least one SSH rule that could apply to the node. Out of the box, new
tailnets have zero SSH rules — :22 will refuse connections even
though tailscale debug prefs shows RunSSH: true. Edit your ACL at
https://login.tailscale.com/admin/acls/file and add:
{
"ssh": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot"]
}
]
}tailscaled picks this up within seconds. Verify:
sudo tailscale debug netmap | python3 -c \
'import json,sys; d=json.load(sys.stdin); p=d.get("SSHPolicy") or {}; \
print("rules:", len(p.get("Rules",[])))'rules: 1 (or higher) means you're good. If it's still 0, you saved
to the wrong tailnet or the ACL has a syntax error — see
docs/tailscale.md.
Install whichever backend CLI(s) you plan to use:
-
Copilot CLI — per its own instructions (
brew install copilot-cliworks once it's published; otherwise follow the README there). The first launch asks:System vault not available — store token in plain text config file?
Answer Yes. The macOS keychain prompts the GUI for unlock, which there's nobody to dismiss when you're SSH'd in from a phone — the keychain stays locked and Copilot CLI stalls. The plaintext fallback lives at
~/.copilot/config.jsonwith mode0600, inside the FileVault-encrypted home volume. The risk delta over the keychain is small; the operational benefit (Copilot CLI just works over SSH) is large. -
Claude Code CLI — per the Claude Code quickstart. The first launch opens an in-terminal login flow — follow the prompts to authenticate; the token lands under
~/.claude/. -
Codex CLI — install and authenticate Codex normally. The wrapper resumes the most recent session for the selected workspace when matching history exists.
Install any combination. The wrapper checks for a backend binary only when that backend is launched.
The help-tool selection can also be supplied noninteractively:
AGENT_HELP_RECIPIENT='you@example.com' ./install.sh \
--agent-help-clis copilot,claude,codex \
--skills-clis copilot,claude,codex \
--dreaming \
--screen-sharing-port 15900 \
--screen-sharing-hours 1The recipient is stored only in
~/.config/remote-agent-stack/agent-help/config.json with mode
0600. Prefer the hidden interactive prompt or the temporary
AGENT_HELP_RECIPIENT environment variable over a command-line flag
so the value does not enter shell history.
The installer also offers to install
dfrysinger/skills through each
CLI's native user-scoped plugin manager. Supply the complete desired set
noninteractively with:
./install.sh --skills-clis copilot,claude,codexUse --skills-clis none or a smaller list to remove only plugins and
marketplaces created by this installer. Pre-existing installations are
preserved. Ownership state lives under
${XDG_STATE_HOME:-~/.local/state}/remote-agent-stack/, outside the
purgeable recipient and wrapper configuration.
The installer can also install
dfrysinger/dreaming as a
Copilot-only, headless learning and skill-curation service:
./install.sh --dreamingDreaming is independent of --skills-clis. Its dedicated runtime checkout
defaults to ~/.local/share/remote-agent-stack/dreaming, so a separate
development checkout can remain on another branch or contain local edits.
Installation delegates to Dreaming's own install, self-test, and enable
lifecycle. The stack sets
DREAMING_SKIP_PLUGIN_SYNC=1, so Dreaming's five private orchestration skills
do not appear in normal interactive CLI context. --no-dreaming removes only a
runtime adopted by this installer and preserves the checkout and recovery
state.
Each backend has its own root directory; inside each, agents are
agent-<name>/ subdirs (e.g., agent-alpha/, agent-bravo/). The
installer asks for all roots and auto-picks smart defaults:
- With Dropbox installed (recommended — workspaces sync across
Macs):
- Copilot:
~/Library/CloudStorage/Dropbox/copilot-workspace - Claude:
~/Library/CloudStorage/Dropbox/claude-workspace - Codex:
~/Library/CloudStorage/Dropbox/codex-workspace
- Copilot:
- Without Dropbox:
- Copilot:
~/copilot-workspace - Claude:
~/claude-workspace - Codex:
~/codex-workspace
- Copilot:
Press Enter to accept a default, or type any path (~, $HOME, and
shell expansions all work). Re-running install.sh later keeps
existing choices and skips the prompts.
To pick paths non-interactively (or from a script):
./install.sh \
--copilot-workspace-base ~/code/copilot-workspace \
--claude-workspace-base ~/code/claude-workspace \
--codex-workspace-base ~/code/codex-workspaceThis writes COPILOT_WORKSPACE_BASE, CLAUDE_WORKSPACE_BASE, and
CODEX_WORKSPACE_BASE into
~/.config/remote-agent-stack/config. Edit that file directly any
time to move a workspace base later (you'll have to move existing
<name>/ directories under it by hand).
Copilot backend:
agent-stack copilot alphaClaude Code backend:
agent-stack claude alphaCodex backend:
agent-stack codex alphaYou should land inside the matching tmux session with the backend
CLI running. Detach with Ctrl-b d (you're back at the Mac shell).
Reattach with the same command again — should be instant. Now the Mac
side is done.
Copilot CLI sessions write a lot of Markdown to disk —
~/.copilot/session-state/<uuid>/plan.md, per-session checkpoints,
files in files/. Open
Markdown is a free native
macOS viewer that renders GitHub-flavored Markdown with live reload,
Mermaid diagrams, syntax highlighting, and inline editing. It's the
nicest way to skim what an agent has been writing without leaving the
Mac.
Install from https://ptheofan.github.io/open-markdown/, then in
Finder right-click any .md file → Open With → Open Markdown
→ Always Open With to make it the default.
The phone is your "land in any agent in one tap from anywhere" client.
Install Tailscale from the App Store, sign in with the same account as the Mac, and toggle it on. That's it — no config.
Termius from the App Store. Sign in with the same account you'll use on the Mac desktop client (this enables Termius Sync, optional but convenient).
In the iOS Tailscale app, tap your Mac in the device list. Its
MagicDNS name appears at the top of the detail screen with a copy
button next to it — tap it. You now have something like
macbook-air.tail-xxxx.ts.net on your clipboard, ready to paste into
Termius next.
A host group lets every Agent host inherit shared SSH credentials (username, optional password) so you only have one place to fix things. Per Termius's docs on groups, groups inherit credentials, snippets, protocols, environment, and themes — but not the address, which is always set per host.
- Termius → Hosts → + → New Group.
- Name it
Mac. - Toggle SSH on at the group level so the credential fields
appear, then set:
- Username: your macOS username (whatever
whoamiprints on the Mac). This must be a real user — Tailscale SSH checks it against the ACL, and a wrong/blank username is what causes the "connection failed" prompt loop. - Password: Tailscale SSH doesn't use this for auth. Two
reasonable choices:
- Save your macOS login password. Termius will autofill it
at
sudoprompts on iOS, which is the only reason to bother. The value is sent on the wire (encrypted under SSH + WireGuard) and discarded by the Tailscale SSH server. - Leave it blank. You'll type your password by hand whenever
sudoasks.
- Save your macOS login password. Termius will autofill it
at
- Username: your macOS username (whatever
- Save.
For each agent name (alpha, bravo, charlie, …):
-
Termius → Hosts → + → New Host.
-
Address: the FQDN from 2.3 (
macbook-air.tail-xxxx.ts.net). This is per-host on iOS — the group can't set it for you. -
Parent Group:
Mac. The host will show "Inherited from group" under the credential fields; leave those blank to use the group's username and password. -
Label:
Agent alpha(or whatever you want to see in the host list). -
Startup snippet: tap Startup Snippet, then Add new snippet. Enter:
agent-stack copilot alphaName the snippet
Agent alpha(or whatever you want), save, and it'll be selected as the host's startup snippet. Termius docs on snippets: https://docs.termius.com/termius-handbook/snippets. -
Save the host.
Repeat for each agent. The host list now has Agent alpha, Agent bravo, etc., all inheriting credentials from the Mac group, each with
the same address and a different one-line startup snippet.
Tap Agent alpha. Termius connects over Tailscale SSH (the iPhone's
tailnet identity is what authenticates server-side — no password or
key needed), lands in the Mac shell, runs agent-stack copilot alpha, and you're in
the agent's tmux session. Detach with Ctrl-b d, kill the
Termius tab, and reattach later from the same host (or from a
different device entirely) — the tmux session keeps running on the
Mac.
On the Mac itself, Termius's local-shell + workspaces feature is more
ergonomic than snippets — you get a saved grid of named local terminals
that auto-reconnect to their tmux sessions when you reopen the
workspace. This is the trickiest part of the whole stack to set up
because the Termius UI for it isn't obvious.
Mac App Store Termius is sandboxed and has no local terminal at all. Download the unsandboxed build from https://termius.com/download.
Termius → Settings → enable Autocomplete. This is what makes a
saved local shell replay its last-entered command on reopen — the
mechanism we'll use to auto-reconnect to tmux. Verify it works
before you invest in building the full workspace:
- Open one local terminal (
⌘L). - Run
echo replay-test. - Close the tab.
- Reopen a local terminal in the same window.
- Termius should suggest
echo replay-test(or replay it automatically depending on your version) — confirming autocomplete is on.
If nothing replays, recheck the setting before continuing. The whole auto-reconnect flow in 3.5 depends on it.
Termius doesn't have a "create workspace" button. You build one by dragging a tab into the body of another tab's window:
-
Open the first local terminal: click Vaults → Terminal, or press
⌘L. A local shell tab opens. -
Open a second local terminal in a new tab in the same window:
⌘Lagain. -
Drag-drop to form the workspace: select one of the tabs, then drag the unselected tab into the body of the window. A drop-zone box appears; release. You now have a tiled workspace with two panes.
-
Save the workspace: tap the small dot in the workspace tab's header. Right-click the tab to rename it (e.g.,
Agents). -
Add more panes: open another
⌘Ltab, drag-drop into the workspace body to add a third pane. Repeat until you have one pane per agent (the author runs ~12).
By default the panes have generic names. To rename:
-
Click the focus mode button in the top-right of any terminal card (next to the X). This expands the card and reveals a left panel listing every named local-shell session in the workspace.
-
Right-click a session name in the side panel → Rename → give it the agent name (
alpha,bravo, …). -
Click the split-view button at the top-right of the side panel to return to the grid view of all panes.
-
Save again (the tiny dot on the workspace tab).
In each pane, run the matching command:
agent-stack copilot alpha # in the pane named "alpha"
agent-stack copilot bravo # in the pane named "bravo"
# …This serves two purposes:
- Bootstraps the actual
tmux/Copilot CLI session for that agent. - Becomes the last entered command in that pane, which is what Termius's autocomplete replays on reopen.
Save the workspace one more time (tiny dot). Close the workspace window. Reopen it from the Workspaces sidebar.
Each pane reopens, replays agent-stack copilot <name>, and reconnects to the
already-running tmux session on the Mac — you're back in the same
grid of agent conversations you left.
Save the workspace every time you change anything — adding a pane, renaming a session, resizing splits. The tiny dot is easy to miss. If you close the window without saving, the change is lost.
Once everything is set up:
- From the phone: tap an Agent host in Termius. You're in.
- From the Mac desktop: open the saved Termius workspace. Every pane reconnects.
- Detach (leave session running, drop back to shell):
Ctrl-b dinsidetmux. - Kill an agent session entirely: exit Copilot CLI, then
exitthe shelltmuxgave you. Or from outside any device:tmux kill-session -t alpha. - Reattach from anywhere:
agent-stack copilot alphaon the Mac, or tapAgent alphain iOS Termius. Same session. - GUI access (graphical desktop, not just the terminal):
agent-screen onenables Screen Sharing for an hour and forwards it over the tailnet. It no longer asks for a password because the installer grants only the exact bounded helper commands. Connect using the printedscreens://<tailscale-name>:<port>link. Seedocs/screen-sharing.md. - Agent needs you: the selected CLIs expose
request_help. Its tool description tells agents to call it once when blocked on your login, permission, or decision. The request opens Screen Sharing forSCREEN_SHARING_HOURSand identifies the originating NATO-alphabet tmux session.
tmux sessions live in memory — they don't survive a Mac reboot. The
wrapper relaunches Copilot CLI with the same --session-id=<uuid> on
next launch, so the Copilot CLI conversation comes back; only the
tmux scrollback is lost.
Defaults live in ~/.config/remote-agent-stack/config, which the
installer writes for you (see §1.8). The
file looks roughly like:
COPILOT_WORKSPACE_BASE="/Users/you/.../Dropbox/copilot-workspace" # set during install
CLAUDE_WORKSPACE_BASE="/Users/you/.../Dropbox/claude-workspace" # set during install
CODEX_WORKSPACE_BASE="/Users/you/.../Dropbox/codex-workspace" # set during install
MAILBOX_INTEGRATION="false" # off by default
ALLOW_ALL="false" # off by default
AGENT_HELP_CLIS="copilot,claude,codex" # complete desired set
SCREEN_SHARING_PORT="15900" # Tailscale TCP forward
DESK_DISPLAY_COUNT="3" # legacy compatibility setting
SCREEN_SHARING_HOURS="1" # automatic bounded lease
# COPILOT_BIN="copilot"
# CLAUDE_BIN="claude"
# CODEX_BIN="codex"
# AGENT_DIR_PREFIX="agent-"
# SESSION_WARN_MB="300"The three *_WORKSPACE_BASE values are whatever you
picked at install time (Dropbox defaults if Dropbox is installed,
otherwise backend-specific directories directly under $HOME).
Inside each root, agents live in agent-<name>/ subdirs (change the
prefix with AGENT_DIR_PREFIX if you want a different convention).
Re-run ./install.sh with the matching --<backend>-workspace-base PATH
option or edit the file directly to move a
root.
MAILBOX_INTEGRATION enables the cross-session
mailbox skill — the wrapper pokes the
recipient's pane on attach + cold-start when pending mail exists. Off
unless you have the skill installed.
ALLOW_ALL makes the wrapper skip permission prompts on new-session
launch — Copilot: --allow-all (auto-approves tools, paths, URLs);
Claude Code: --dangerously-skip-permissions; Codex:
--dangerously-bypass-approvals-and-sandbox. Personal-machine
convenience; do not enable in shared environments.
SESSION_WARN_MB (Copilot only) is the size at which the wrapper offers to
retire an agent's session and start a fresh one. Copilot replays a
session's whole event log on resume, so a session carried for weeks
grows into the gigabytes and becomes slow to start. Accept the offer and
the wrapper records the old session as retired, launches a new one, and seeds
it with a prompt that rebuilds context from the old session's
plan.md, checkpoints, todo database, and conversation tail. Nothing is
deleted — the retired session stays on disk, resumable and minable. Set
to 0 to disable the prompt.
The first carry-over asks Copilot to approve reading
~/.copilot/session-state, which sits outside the agent workspace.
Choose "add these directories to the allowed list" and later rotations
run without prompting.
COPILOT_BIN, CLAUDE_BIN, and CODEX_BIN let you point the wrapper at a specific
backend binary (e.g., /opt/homebrew/bin/copilot or a nightly build).
AGENT_HELP_CLIS is the complete managed set. Re-run the installer
with --agent-help-clis none or a smaller list to remove only this
repository's MCP entries and instruction blocks from deselected CLIs.
Existing unrelated CLI configuration and instruction text are
preserved.
SKILLS_CLIS is the independently managed set for the
dfrysinger-skills plugin. Reruns update installations created by this
installer. Deselecting a CLI removes only owned plugin and marketplace
registrations; same-named user-managed installations are left untouched.
DREAMING_ENABLED independently controls the headless Dreaming service.
DREAMING_REPO_ROOT records its source checkout. Reruns fast-forward only an
exact, clean dfrysinger/dreaming checkout on main; a dirty checkout,
different branch, or foreign origin fails closed. The lifecycle journal lives
under ${XDG_STATE_HOME:-~/.local/state}/remote-agent-stack/.
The help guidance lives in the request_help tool description, which is in
context for any CLI where the tool is registered. The installer no longer adds
a managed instruction block, and removes any block it previously wrote from:
- Copilot CLI:
~/.copilot/copilot-instructions.md - Claude Code:
~/.claude/CLAUDE.md - Codex CLI:
~/.codex/AGENTS.md
Active CLI sessions must be restarted after changing MCP selection.
The installer offers to install a tiny LaunchAgent
(~/Library/LaunchAgents/com.dfrysinger.tmux-keychain-bootstrap.plist)
that pre-warms the tmux server in your GUI (Aqua) login session at
every login and re-fires automatically whenever the tmux socket
disappears.
Without it, the first agent-stack <backend> <name> call after a Mac reboot
bootstraps the tmux server from your SSH login shell. macOS gives that
shell a restricted keychain search list (System keychain only, no
login keychain), and every subsequent shell inside that tmux server
inherits the restriction — including the ones the wrapper opens later when
you re-attach. The visible symptom is that gh, the osxkeychain
git credential helper, and anything else that reaches into the login
keychain silently fail inside agent shells, even though they work in
Terminal.app on the same Mac.
The LaunchAgent fixes that by starting the tmux server itself under
the GUI session, so the security context is correct from the first
session onward. It runs once at login, starts a hidden anchor session
called _keychain-anchor (so the server stays alive), and exits.
Subsequent wrapper calls just attach to or create sessions on
that already-running, properly-contextualized server.
It also installs a PathState watchdog on the tmux socket. If you
ever run tmux kill-server — intentionally or by accident — launchd
notices the socket disappear within a few seconds and re-fires the
bootstrap script, which spawns a fresh GUI-context server. So
recovery is hands-off: kill the server, wait a moment, run the same
agent-stack <backend> <name> command
again, you're back.
If you skip it: keep using gh-auth-macos from the
dfrysinger/skills plugin as a
per-shell fallback that reads the keychain via the security CLI and
exports GH_TOKEN. That works but is per-shell setup, not a one-time
fix.
To install later, re-run ./install.sh and answer Y at the prompt.
To uninstall:
launchctl bootout "gui/$(id -u)/com.dfrysinger.tmux-keychain-bootstrap"
rm ~/Library/LaunchAgents/com.dfrysinger.tmux-keychain-bootstrap.plist~/code/agent-stack/uninstall.sh # wrappers, MCP entries, instructions, remote access
~/code/agent-stack/uninstall.sh --purge # also drops private recipient/state config
~/code/agent-stack/uninstall.sh --commands-only # only owned agent command linksHomebrew packages, FDA grants, and Tailscale network state are left in place — see the uninstaller output for the manual cleanup commands.
Single-machine, single-user, macOS arm64. Tested on macOS Tahoe. Copilot CLI, Claude Code CLI, and Codex CLI are first-class named-session backends. Linux support remains an open issue.
docs/tailscale.md— Tailscale account → MagicDNS → ACL → CLI install → resolver fix.docs/fda-grants.md— exactly which binaries need Full Disk Access and why.docs/troubleshooting.md— every macOS / Tailscale / Copilot CLI / Termius gotcha we hit.docs/screen-sharing.md— GUI access over Tailscale: theagent-screen/vncfixscripts, black-screen recovery, the IPv4-blackhole workaround, and going dark without locking.




