sidebar: remote agents over ssh (state mirror + jump routing) - #233
Open
sophronesis wants to merge 1 commit into
Open
sidebar: remote agents over ssh (state mirror + jump routing)#233sophronesis wants to merge 1 commit into
sophronesis wants to merge 1 commit into
Conversation
sophronesis
force-pushed
the
remote-agents-pr
branch
from
August 14, 2026 08:41
82916a0 to
39f7070
Compare
Show agents running on other machines in the local sidebar/dashboard
and route jumps to them over ssh:
- reconcile: state files with instance "ssh:<host>" (mirrored from a
remote machine by an external sync) are exposed with namespaced pane
ids "ssh:<host>/%N", skipping local liveness checks
- switch_to_pane routes ssh:* pane ids through remote_pane_jump:
local-first for perceived latency - focus the most recently used
local ssh/autossh attach pane for that host, record the highlight in
@workmux_remote_active, then flip the remote group's window pointers
via a detached ssh (every attached session; grouped sessions keep
per-clone active-window pointers, so a bare select-window is not
enough)
- the sidebar daemon validates/clears the remote-active override
against the recorded window and ships it in snapshots; clients
prefer it when picking the active row
- new {remote} template token renders a dim "@<host>" tag next to the
agent name, wired into all default templates
- remote agents are skipped by pane capture (no local pane to read)
The state mirror itself lives outside workmux: any 1s-ish sync that
copies ~/.local/state/workmux/agents/*.json from the remote host and
rewrites pane_key.instance to "ssh:<host>" makes the remote agents
appear.
Because the local reconcile pass has nothing to verify them against,
that sync owns liveness. Remote state files outlive their panes -
nothing prunes them unless a workmux daemon happens to run over there -
so a sync that copies the directory wholesale pins dead agents to the
sidebar forever. The contract is to drop an agent whose pane is gone
(id absent from list-panes, or boot_id / pane_pid / command disagreeing
with the live pane) and to clear the mirror when the host is
unreachable; the guide page spells it out with a working example.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sophronesis
force-pushed
the
remote-agents-pr
branch
from
August 14, 2026 08:44
39f7070 to
e2f952d
Compare
Owner
|
The idea is fun but seems a bit hacky |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
AI disclosure, upfront: this feature was implemented with Claude Code (model
claude-fable-5; the 2026-08-14 revision below usedclaude-opus-5) - designed, driven, reviewed and live-tested by me, but the code was largely machine-written. The commit carriesCo-Authored-Bytrailers for both. If AI-assisted contributions aren't welcome here, feel free to close - no hard feelings.I know CONTRIBUTING prefers an issue first for large changes - happy to convert this into an issue/discussion instead. Opening as a PR because the implementation exists and has been running on my machines all day (laptop + a work server, 5 agents of which 2 remote), so it felt more concrete to discuss with code attached.
Important
Updated 2026-08-14 (force-push, the single commit amended - no review had happened yet, so I kept the history clean rather than stacking a fixup).
Running this for real for two days turned up the one way the feature goes bad. Remote state files outlive their panes - nothing prunes them unless a workmux daemon happens to be running on that machine - so a sync that copies the agents dir wholesale pins dead agents to the sidebar forever. I had two ghosts from a rebooted remote tmux server sitting in my sidebar for a day before I noticed.
No behaviour change in the Rust code (the reconcile comment is the only source edit): the fix belongs in the sync contract, which is the part this PR documents rather than implements. So:
pane_idpresent inlist-panes -a,boot_idvs#{start_time},pane_pid,commandvs#{pane_current_command}) - the same ones the local reconcile pass applies to local panesgrep -H . *.json, which never worked, becauseupsert_agentwrites withto_string_prettyand the files are multi-lineThe reconcile comment now says outright that these files are a mirror, not the state of record, so a future reader knows why no liveness check happens there.
What this adds
I keep claudes running inside tmux on other machines (attached via
autossh ... 'tmux attach'panes) and wanted them in the same sidebar as local agents. The kitty/wezterm/zellij backends and pure-local setups are untouched.pane_key.instancerewritten tossh:<host>) are reconciled without local liveness checks and exposed under namespaced pane idsssh:<host>/%Nswitch_to_panerecognizes the namespace; jumping switches the agent's window in every attached session of the remote server (grouped sessions keep per-clone active-window pointers, so a bareselect-windowisn't enough), focuses the most recently used local ssh/autossh attach pane for that host, and runs the remote flip as a detached ssh so the UI never blocks (local switch lands in ~30ms)@workmux_remote_activetmux option - recorded on jump, validated by the daemon, cleared as soon as the user moves to a different local window (remote agents have no local window for the normal host-window match){remote}template token: dimmed@<host>tag next to the agent name, wired into the default compact/tile/horizontal templates{remote}in the token table, nav entryDesign notes
agents/*.json, keep only agents whose pane is still live on the remote, rewrite instance, delete mirror when unreachable, optionallySIGUSR1the daemon). This keeps auth/transport policy out of the tool - at the cost of making liveness the syncer's responsibility, which the guide is now explicit about.switch_to_paneare untouched, and the sync contract only mirrorsbackend == "tmux"states.Testing
cargo fmt --checkclean; clippy warnings in the diff: none (remaining ones pre-exist on main)sandbox::rpc::tests::test_exec_sandbox_blocks_ssh_readfails on my box also on a clean checkout of main (environment-dependent), passes in a sandboxed build🤖 Generated with Claude Code