Herd your AI coding-agent sessions: multi-host tmux session dashboard with hook-fed state, embedded terminal, and unattended-worker supervision
Overview
ShiftLead is under active development and is not yet meant for anyone outside the project to install or run: interfaces, names and on-disk state still change without notice, and there is no support. Please hold off on sharing it until a release is announced. *The project is (shiftlead.dev), decided 2026-09-21; the command is ao. The packages, the state directory, the units and the config file carry the name since 2026-10-10 (TD-060; ao migrate moves a machine from the old names once), and the repo is ShiftLead/shiftlead since 2026-10-11. Was sessionherd for the first day of design, and agentorc from 2026-09-04 to 2026-10-10, to sit beside cmdorc.* A self-hosted web dashboard that orchestrates interactive AI coding-agent sessions (Claude Code first; Gemini CLI, Codex CLI, and on-prem harnesses via adapters) — and plain shells — running in tmux across one or more hosts.
README
ShiftLead
Not ready for use yet. ShiftLead is under active development and is not yet meant for anyone outside the project to install or run: interfaces, names and on-disk state still change without notice, and there is no support. Please hold off on sharing it until a release is announced.
The project is ShiftLead (shiftlead.dev), decided 2026-09-21; the command is ao.
The packages, the state directory, the units and the config file carry the name since 2026-10-10 (TD-060;
ao migrate moves a machine from the old names once), and the repo is ShiftLead/shiftlead since 2026-10-11.
Was sessionherd for the first day of design, and agentorc from 2026-09-04 to 2026-10-10, to sit beside cmdorc.
A self-hosted web dashboard that orchestrates interactive AI coding-agent sessions (Claude Code first; Gemini CLI, Codex CLI, and on-prem harnesses via adapters) — and plain shells — running in tmux across one or more hosts. One view of every session and whether it is working, needs you, idle, or exited; read and drive any of them in an embedded terminal; start, resume, and close them; see git status per checkout; press configured command buttons; jump to VS Code; and supervise unattended workers with run windows and usage caps. Sessions live on the host, so closing the laptop changes nothing.
Status: phase 1 in progress. The host agent, the Claude Code adapter, the Org, Focus and
Inbox pages (mail and the sessions’ states; due board items are still to come — TD-069), New session and the CLI are built and
running; phase 2 (a second host over ssh) is not.
Read docs/design.md — §7 has the phase plan, and what is deferred is in
docs/technical_debt.md.
Grew out of samscrape’s tdgrind worker supervisor
and the dev-cadence working-cadence system, whose
repo registry it depends on.
Start here (handoff, updated 2026-09-06 evening; the next-steps list below is from that
date — the live lists are docs/user_attention.md and
docs/technical_debt.md)
Phase 1 is built (PRs #1–#4; each was reviewed by a Sonnet subagent in the build session and the findings folded into the PR before merge — see the “review fixes” commits): host agent, Claude Code adapter, Org and Focus pages, New session, CLI. Run it per “Run it” below. What to do next, in order:
- Use it against a real repo and note what the flows get wrong (board item, due 2026-09-13).
From the laptop, reach the UI with
ssh -L 8765:127.0.0.1:8765 kmaster; from the phone, set up the Tailscale route (Reach it from your phone, below). - Decisions waiting on Paul: see
docs/user_attention.md(dev-cadence PRs #82/#83, GitHub merge settings, the two permission-dialog questions). - Deferred work is in
docs/technical_debt.md(TD-001limitedstate, TD-002 attachments, TD-003 phone, TD-004hosts.yml, TD-010 adopting hand-started sessions), then phase 2 ofdocs/design.md§7: second host, ssh transport, agent install script. - Known gap in the phase 1 success test: only sessions ShiftLead launched (or hand-started
ao-*tmux sessions) appear in the Org. A Claude Code session opened in a VS Code terminal is not a tmux session and is invisible until Adopt (TD-010) exists.
Mockups: https://claude.ai/code/artifact/0e14af3a-5e5a-4d9c-88b2-74205c394c04 (sources and
regeneration notes in docs/mockups/). The CSS block at the top of
docs/mockups/gen.py is the seed for the app’s stylesheet tokens; the dark artboard is a token
swap of the light one, which is how the real dark mode should work too.
Layout
One repo, one distribution (shiftlead, with an [ui] extra for the host that serves the web
UI), two import packages: sessionorc (tmux, hosts, ssh, the pty terminal bridge, run logs, the
shell adapter) and shiftlead (hook adapters, profiles, policies, Ready to close, the board) on
top of it. sessionorc never imports shiftlead; it becomes its own repo only when a second
consumer appears (docs/design.md §10).
Requirements
Python 3.12+, tmux 3.2+ (new-session -e, bracketed paste-buffer -p; kmaster has 3.5a), and
on each session host a user systemd session with loginctl enable-linger so the agent and its
tmux server survive logout and reboot.
Run it (phase 1, one host)
pdm install -G ui # or: pipx install 'shiftlead[ui]' once published
pdm run shiftlead-agent serve # the host agent: tmux, sessions, hooks, run logs
pdm run ao ui # the web UI on http://127.0.0.1:8765 (localhost by default; see below)
pdm run ao new td-302 -d ~/samscrape # or the New session form; `ao shell` for a plain shell
pdm run ao service install # instead of the two `serve`/`ui` terminals: systemd user units
# (survive logout and reboot with linger; `ao service status`)
To work on ShiftLead with ShiftLead, keep the running copy separate from the checkout: install a stable, non-editable copy and point the units at it, then promote a merge deliberately.
python3 -m virtualenv ~/.local/share/shiftlead-venv # (pipx works the same way, if you have it)
~/.local/share/shiftlead-venv/bin/pip install '/path/to/shiftlead[ui]'
~/.local/share/shiftlead-venv/bin/ao service install # units now run the stable copy; restarts them
# after a merge you want live:
~/.local/share/shiftlead-venv/bin/pip install --upgrade '/path/to/shiftlead[ui]' && ~/.local/share/shiftlead-venv/bin/ao service install
Reach it from your phone
Never a bare public port (design §4.5). The UI listens on two loopback ports: the plain port
8765, for a browser on this machine and an ssh -L, and the route port 8766, for a proxy that
proves who you are on every request. Tailscale is that proxy here. Nothing below hands shiftlead a
key, and ao connect never runs sudo (design §4.5b, §4.7).
-
A Tailscale account (sign in with Google, GitHub or Microsoft) with a second factor on that login: whoever holds it reaches your sessions.
-
The client on this machine (), then
tailscale upand open the link it prints. -
On Linux, let your user run
tailscale servewithout root:sudo tailscale set --operator=$USER. -
In the admin console’s DNS page (), turn on MagicDNS and HTTPS Certificates;
tailscale serveneeds them for its certificate. -
Turn on Tailnet Lock, so a device nobody you trust signed cannot join the tailnet.
-
In the tailnet’s access rules, grant your login this machine’s ports 443 (the UI) and 22 (an ssh from the phone, for the case below).
-
ao connect tailscale. It checks each step above and names the first one missing. It writesreach:insettings.yml, serves 443 at the route port, and probes the result; a probe that fails is undone. On the first run the probe can wait a minute or two while Tailscale fetches the machine’s first HTTPS certificate.--allownames the login to let in (a tagged machine needs it).ao connectalone shows the route;ao doctor routechecks it. -
On the phone: the Tailscale app, signed in as the same login, then open the URL
ao connectprinted. -
On each computer you code from: the client, signed in as the same login. Point the ssh alias your editor uses (VS Code Remote-SSH, and the UI’s VS Code links through
vscode_host) at this machine’s tailnet name or address, not its LAN address:Host kmaster HostName 100.x.y.z # `tailscale ip -4` on this machine, or kmaster..ts.net User youThe alias goes in the ssh config of the program that runs ssh: on Windows, VS Code reads
C:\Users\\.ssh\configby default, not WSL’s~/.ssh/config; and whenHostNameis a name, a line for it inC:\Windows\System32\drivers\etc\hostsdecides where it goes. The first connect asks you to accept the host key again, because ssh files it under the new address. Port 22 is already granted (step 6).
If 443 already serves the plain port (the interim of design §4.5b, before the route port
existed), the UI refuses what that proxy sends, and the phone shows a page saying so. Run
tailscale serve --https=443 off, then ao connect tailscale. From the phone, do it over an ssh
to this machine’s tailnet name, so keep an ssh app on the phone while that serve is in use.
A lost phone: remove the device from the tailnet in the admin console’s Machines page; it
reaches nothing from then on. ao connect off removes the route and turns off only the serve it
added.
ssh -L 8765:127.0.0.1:8765 always works as the fallback. A script that POSTs to
the UI over HTTP sends an Origin naming the UI’s own address, as a browser does; one without it is
refused (design §4.5b). A private network you run yourself (WireGuard) can reach the plain port bound
to its address with ao ui --bind (and --allow-host for a name it is reached by).
Do not start the agent or the UI from a VS Code Remote-SSH terminal. VS Code auto-forwards
any port a process in its terminal starts listening on, so 127.0.0.1:8765 on your laptop ends
up served by VS Code’s forwarder instead of your ssh -L, and the terminal websockets never
arrive (pages still load, which makes it hard to spot; found 2026-09-06). Start both from a plain
ssh session, or set remote.autoForwardPorts to false for that remote.
State lives under ~/.shiftlead/ (SHIFTLEAD_HOME overrides it — every session the agent creates
carries it, so hooks inside find the right agent). Profiles: ~/.shiftlead/profiles.yml
(design §4.2a). Host identity: ~/.shiftlead/hosts.yml, e.g. local: {name: kmaster, vscode_host: kmaster} —
vscode_host is the ssh alias VS Code links use and must be in your own ~/.ssh/config; add
local: true when the UI runs on the machine you sit at (vscode://file/… links). Without the
file the machine’s hostname is used, which is rarely what VS Code can resolve. The same entry
takes volatile: true (a laptop: an unreachable agent is asleep, not an alert), repos_registry
(one file in place of the default: ~/.shiftlead/repos.txt, then dev-cadence’s
~/.config/dev-cadence/repos.txt when it exists; their repos head the New session directory list) and
runs_keep_days (default 30; run logs of exited/closed sessions older than that are deleted on
the agent’s tick, 0 keeps all) — the full shape is in sessionorc/hosts.py. A top-level home: makes this
host agent a node of that home (design §4.4a); leave it out on a single machine.
Linking a second machine to the home (TD-057 step 3a — the link comes up and stays up; nothing is forwarded over it yet, so a node still serves only its own host’s sessions):
# on the node (say `laptop`): a key used for nothing else
ssh-keygen -t ed25519 -N '' -f ~/.ssh/shiftlead-link
# on the home (kmaster), one line in ~/.ssh/authorized_keys — the name after --host is the node's
# name as far as the home is concerned, and it is set HERE, never by the node:
command="shiftlead-agent link --host laptop",restrict ssh-ed25519 AAAA… laptop-link
# on the home, ~/.shiftlead/hosts.yml: nodes: [laptop]
# on the node, ~/.shiftlead/hosts.yml: home: kmaster
# local: {name: laptop}
# link: {ssh: kmaster-link} # an alias in ~/.ssh/config using that key
ao’s host RPC (shiftlead-agent rpc → {"method": "host"}) shows the link’s state at both ends,
with the reason when it is down: ssh failed, agent down on the home, or refused: ….
CLI
The package installs shiftlead and an ao alias. Reading: ao status [-v], ao tail,
ao explain. Acting on a session: ao new, ao shell, ao send, ao keys, ao allow /
ao deny, ao mode, ao kill, ao close, ao forget. Capabilities and reports (design §4.8):
ao grant / ao revoke, ao progress claim|done|drop, ao finding, ao doing "" (what
this session is doing now — its card shows it with its age; --clear empties it). Who is calling
(design §4.8a): ao whoami (what the host agent takes this process to be, from its connection),
ao identity (this host’s mode — local: {identity: off | observe | enforce} in hosts.yml,
observe by default — its tally of connections, and its identity alarms, which the Inbox also lists with
Acknowledge; ao status -v prints the mode and whether the detached-process check is on). Serving: ao ui,
ao service. A container node (design §4.4a): ao host up|rebuild|forget|status ; ao new --host starts a session on it, every act on the @ it prints is gated here
and run there, and ao focus @ attaches through docker exec. ao new --attach (or ao shell --attach) starts the session and attaches your terminal to it — type it
where you would have typed claude, and the session is a first-class card; ao focus
attaches to an existing one. Every
subcommand takes --json (before or after the subcommand) and then prints only the RPC result —
the session dict, the tail lines, {"ok": true, "id": …} for calls that return nothing — or
{"error": …} with the same exit codes, so a script or an agent driving ao parses ids instead
of prose (design §4.7). ao --skill prints the rules an agent driving ao from inside a session must
follow; ao --skill > .claude/skills/ao/SKILL.md installs them in a repo (TD-019).
Stand up a team
One command is the whole of it, and it is the same text for a person and for an agent:
ao team --skill
It prints the recipe — the node (if the team runs on one), the project and the team definition in
~/.shiftlead/org.yml or a repo’s own .shiftlead.yml, the roles and their briefs, and
ao team list / start / status / stop — each step a command and what it does. It needs no host
agent and no checkout of this repo, so it works inside a container node too. That is the source;
this section is the pointer (TD-067). ao --skill is the other document and answers the other
question: how to behave inside a session, once one is running.
License
MIT — see LICENSE.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add shiftlead/shiftlead