πΌ Conductor
Ten terminals running Claude Code, no idea which one is stuck or waiting on you. Conductor reads all of them β and lets you reply β from one window.


You've got 10 terminals running Claude Code. You lose track of what each one is doing. Open a fresh window and ask Conductor to sort them out:
πΌ Conductor β 3 windows Β· last 1h
cockpit: conductor up Β· control: conductor run <label> / conductor say <label> yes
WORKING NOW βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββ 1d177c35 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Build SOAG Agent trading grid with character art conv-fix Β· 4s ago β
β SOAG Β· Grid β
β βΊ Read: src/characters.js β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
OPEN ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββ ede6faa0 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Tech week NYC schedule planning main Β· 17h ago β
β Good Rooms β
β βΊ Done. Here's what I builtβ¦ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Each window is a box that leads with what it's actually about (the session's own
summary), grouped into Working now / Open / Recently active / Idle. Open the visual
version with conductor up.
No new infrastructure. Conductor reads the trail each worker already writes (for Claude
Code, the transcript under ~/.claude/projects/). Read-only observation, opt-in control:
watching is always-on and free; control (replies, launch, flatten) is opt-in and only where a
real command channel exists. Zero dependencies. The server binds to 127.0.0.1 only, and
state-changing requests require a local origin + an X-Conductor header (CSRF / DNS-rebinding
guard); destructive control (flatten / broadcast) additionally requires a confirm token. The
header stops hostile webpages, not local processes β anything already running code on your
machine can call the API, same as it could call tmux directly. Run the cockpit for yourself,
not as a service.
Under the hood, Conductor is a source-agnostic supervisory core with a pluggable adapter interface: the engine owns grouping, status ranking, sectioning, and the three surfaces (CLI table, web cockpit, MCP server); an adapter owns where the trails live and how to read them. Claude Code is one adapter; a trading-bot fleet, MEV searchers and Solana validators are others β see The abstraction.
Conductor watches windows you already opened. Its sibling Conductor V2 flips the order: design the fleet first (topology + preset + mission), press FIRE, and steer the swarm from V2's bundled board. V2 is self-contained on its own tmux/registry namespace β fleets you fire live there; V1 stays the watcher for windows you open by hand.
How it works
Every Claude Code window logs a live .jsonl transcript under
~/.claude/projects/<dir>/<session-id>.jsonl. Conductor:
- Lists those transcripts and filters by modification time (folders hold thousands of historical sessions β only recently-touched ones are candidates).
- Excludes subagent threads (
/subagents/, sidechains) so each window counts once. - Groups by session id and streams each file (never loads 8MB into memory),
pulling each session's
ai-title, latest prompt, recent tool calls, and last action. - Reports one row per window.
πΊ The whole thing on one page

Install
git clone <repo> ~/conductor && cd ~/conductor && npm link
npm link puts a global conductor command on your PATH. No build, no dependencies.
Usage
One command, three modes:
conductor # glance: table of your live windows
conductor up # launch the visual web cockpit (opens your browser)
conductor mcp # run the MCP server (for agent integration)
conductor help # all options
Table options
conductor --minutes 60 # widen the time window
conductor ls --all # every session, ignore the filter
conductor ls --json # structured JSON
Web cockpit (the visual)
A live, glanceable dashboard. Big friendly label per window, color-coded status (π’ working now Β· π‘ idle), click a card for full detail (goal, last action, recent timeline). Auto-refreshes every 4s (read-only observation).
conductor up # starts on :7591 and opens your browser
conductor up --port 8080 # custom port
conductor up --no-open # don't auto-open
Tap π in the header for a one-page quick manual (also at /manual, or as a
printable PDF).
Custom labels (the "key")
The big label on each card comes from the working directory, auto-prettified. To give a
project a human name, edit ~/.conductor/labels.json β a flat map of
<dir-basename> β <friendly name>:
{
"agentsoag": "SOAG Β· Website",
"inmusic-pitch": "inMusic Β· Pitch",
"survivors": "DegenScreener"
}
Changes are picked up live (no restart). Unmapped projects fall back to a prettified directory name.
As a Claude Code skill (recommended)
Install the skill so any window can summarize the others in natural language:
mkdir -p ~/.claude/skills/conductor
cp skill/SKILL.md ~/.claude/skills/conductor/SKILL.md
Then in any Claude Code session: "sort out my windows" / /conductor. Claude
runs the scanner and renders a doing-now / done / what's-left summary per window.
As an MCP server (use it inside any agent)
Conductor speaks the Model Context Protocol over stdio, so any MCP-aware agent can call it natively β to watch its windows and to drive them.
Read tools: list_sessions, summarize_session, whats_left, pending_questions
(only the windows blocked waiting on a human, with the question text β the triage feed), and
risk_snapshot (fleet PnL / drawdown / wedged units, for the trading-bot adapter). The read
tools take an optional adapter ("claude-code" default, or "fleet").
Control tools (write, via the same tmux channel as the cockpit): reply_to_session
(reply to a window, adopting a read-only one first β ungated, for a human-authorized reply),
auto_continue (advance a window under the gate β see below), send_key (Escape / C-c /
Enter to a managed window), and run_window (launch a new managed window with an optional
first prompt).
The driving loop, and the gate that makes it safe. An orchestrator runs windows
end-to-end like this: poll pending_questions β each waiting window comes back flagged
irreversible? β auto_continue the safe ones (it sends "continue" to keep ordinary work
moving) and hand the flagged ones to the human. auto_continue consults the irreversibility
gate (policy.js): if the window's question β or the reply you propose β touches deploy,
send, delete, or spend, it refuses to send and returns the question + reason so you escalate.
Continuing ordinary work is automatic; approving an irreversible action is always a human
decision. The bias is to stop when unsure: a false gate costs one manual reply, a false pass
can ship a bad deploy or move real money. reply_to_session stays the raw, ungated channel
for relaying a decision the human has already made.

Add it to Claude Code (user scope = available everywhere):
claude mcp add conductor --scope user -- node ~/conductor/mcp.js
Or add it by hand to a client config (e.g. Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"conductor": { "command": "node", "args": ["/Users/you/conductor/mcp.js"] }
}
}
Then in any session: "use conductor to list my sessions" / "what's left across my windows?"
To run an agent that drives your windows end-to-end (triage β continue the safe stuff β
stop and ask you for irreversible steps), paste the orchestrator prompt in
docs/orchestrator-prompt.md into a window that has this MCP.
Control β reply to managed windows
Conductor can also steer windows, not just watch them. Because a plain-terminal Claude TUI can't have input injected, control works on managed windows β ones you launch through Conductor into a tmux session:
conductor run soag # launch a managed Claude window labelled "soag" (in tmux)
conductor adopt <id> soag # take an EXISTING session under management (see below)
conductor say soag yes # send a quick reply
conductor say soag "review and test it before deploying"
conductor attach soag # drop into the window to type longer commands
conductor managed # list managed windows
conductor stop soag # close it
In the cockpit, managed windows get a MANAGED badge and a reply bar β one-tap
Yes / No / Continue / Review / Re-iterate / Test+deploy plus a free-text box. Clicks
send keystrokes straight into the live window.
Quick replies are short by design. For long, complex instructions, conductor attach and
type in the window directly. Requires tmux (brew install tmux).
Adopting an existing window
A Claude window you opened yourself (in a plain terminal) can't be controlled β the OS
won't let anything inject input into it. conductor adopt works around this by forking
the session into a managed tmux window, keeping the full history:
conductor ls # find the session (note its 8-char id or label)
conductor adopt 1a2b3c4d work # re-open it (forked) as managed window "work"
# ...then close the original tab; control "work" from CLI + cockpit
It runs `claude --resume --fork-session` in the session's own project directory. Forking means no collision with the still-open original β but you should close that tab and continue in the managed window.
Honest limits (v1)
- Control is managed-only. Conductor can reply to windows you launched via
conductor run(tmux). Plain terminal windows you opened yourself stay read-only β there's no reliable way to inject input into them. - "What's left" is inferred from the transcript, not a real todo list. Treat it as best-effort.
- "Live" = recently touched. A window that's been open but idle for hours may not
appear (widen with
--minutes). Per-row time shows true last activity. - Claude Code only, local machine only.
- It only reads your own
~/.claudeβ never another user's transcripts. - The "open" signal needs
lsof(and aclaude-named process) β primarily macOS. Iflsofis missing/unavailable, liveness falls back to recent file writes (nothing shows as "open", just "recent"/"idle"). - Control = a control plane, not read-only. The cockpit's POST endpoints inject
keystrokes into managed windows. They're guarded (localhost bind + origin +
X-Conductorheader) so a stray web page can't reach them, but treat the cockpit as something you run for yourself, not a public service.
The abstraction
Conductor is supervisory awareness over a fleet of semi-autonomous workers that already emit an append-only activity trail, where the operator's scarce resource is attention. Observation is always-on and free; control is a separate, opt-in plane added only where a real command channel exists.
A worker fits Conductor when four things hold:
- Many autonomous-ish units β you're outnumbered.
- Each already emits a readable trail β no instrumentation to add.
- Each is pursuing a task with intent β so "doing now / done / what's left" is meaningful.
- You supervise by exception β surface the one that's stuck, ignore the 19 that are fine.
Claude Code windows fit (transcripts + intent + you-have-10-open). Trading bots fit (event logs + a mandate + you-can't-watch-all-day). So do CI runners, scrapers, data pipelines β anything that narrates itself to disk.
The engine / adapter split
βββββββββββββββββββββββ engine.js βββββββββββββββββββββββ
adapter ββββΆ β discover β liveness β parse β group β status β sort β ββββΆ rows
(the trail) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
owns: grouping, ranking, sectioning βββββββββ΄ββββββββ
CLI Β· cockpit Β· MCP
The engine (engine.js) is domain-blind: loadAdapter(name) resolves adapters/<name>.js,
collect(adapter, opts) runs the pipeline and returns sorted public rows. The three surfaces
(scan.js, server.js, mcp.js) render those rows and are adapter-selectable with --adapter.
Adapters
| Adapter | Reads | Liveness | Control |
|---|---|---|---|
claude-code (default) |
~/.claude/projects/**/*.jsonl transcripts |
a live claude process (lsof) |
tmux send-keys (managed windows) |
fleet |
~/.fleet/bots/*/events.jsonl event logs |
newest heartbeat freshness | append commands to control.jsonl |
mev-searcher |
~/.fleet/searchers/*/events.jsonl event logs |
fresh heartbeat/opportunity (feed alive) | append commands to control.jsonl |
validator-fleet |
the chain (getVoteAccounts/getEpochInfo/β¦ per rpcUrl) |
currently in the cluster vote set | opt-in, gated: control file or SSH exec |
conductor # claude-code (default)
conductor --adapter fleet # the trading-bot fleet
conductor --adapter mev-searcher # the MEV / liquidation searcher fleet
conductor --adapter validator-fleet # Solana validator ops (chain-side)
conductor up --adapter fleet # the cockpit, fleet mode
The fleet adapter
A convention-over-config trading desk. Each bot appends to ~/.fleet/bots/<bot>/events.jsonl,
one JSON record per line β { ts, type, ... } where type β signal | order | fill | pnl | heartbeat | error β and an optional ~/.fleet/bots/<bot>/meta.json gives
{ strategy, mandate, venue, symbol }. The adapter derives:
- liveness from heartbeat freshness;
- a
wedgedsignal β an order/signal stuck with no fill past a threshold; - a
drawdownsignal β equity off its running peak; - position, session PnL, and venue as context chips.
Units are sectioned WEDGED β DRAWDOWN β TRADING β IDLE (problems first β supervise by
exception). Control appends pause | resume | flatten | set-param to the bot's control.jsonl,
which the bot polls; the cockpit's broadcast-flatten is the desk-wide panic button (confirm
token + double-confirm). Try it without a venue:
node tools/fakebot.js alpha --scenario healthy
node tools/fakebot.js beta --scenario wedged
node tools/fakebot.js gamma --scenario drawdown
conductor --adapter fleet
The MCP server adds risk_snapshot (total PnL + worst drawdowns + wedged units) for an
orchestrator agent driving the desk.
The mev-searcher adapter
A sibling of the fleet adapter β same ~/.fleet file-trail plumbing (factored into
adapters/_filetrail.js), different domain. Each searcher appends to
~/.fleet/searchers/<bot>/events.jsonl β { ts, type, ... } where type β opportunity | bundle | submit | land | revert | pnl | gas | heartbeat | error β and an optional meta.json gives
{ strategy, mandate, chain, venue }. The adapter derives, over a recent window:
feed-dead(critical) β no heartbeat/opportunity in the window: disconnected from the mempool / Geyser / orderflow;wedgedβ submitting a flurry of bundles but ~nothing landing (losing every race);bleedingβ net flow negative after tips + gas;racing(active) β landing bundles, positive flow;idleβ connected, quiet market.
Sectioned FEED-DEAD β WEDGED β BLEEDING β RACING β IDLE. Control appends pause | resume | set-param | kill | unwind to the searcher's control.jsonl. unwind (flatten seized collateral)
is destructive β it carries an adapter-layer confirm-token gate on top of the cockpit guard, and
can never be broadcast β broadcast('pause') is the desk-wide stop (gas spike / reorg / bad
oracle), nothing destructive. Try it without a live chain:
node tools/fakesearcher.js arb-1 --scenario racing
node tools/fakesearcher.js arb-2 --scenario wedged
node tools/fakesearcher.js liq-1 --scenario bleeding
conductor --adapter mev-searcher
The validator-fleet adapter
The novel one: zero-instrumentation, chain-side observation. It queries the cluster, not the
box β one batched getVoteAccounts + getEpochInfo (+ optional getBlockProduction /
getClusterNodes / getBalance) poll per rpcUrl, never per-node β so delinquency, catchup,
skip-rate, balance, and version-drift are learned without touching the validator. Config at
<CONDUCTOR_DIR>/validators.json (default ~/.conductor): an array of
{ name, identityPubkey, votePubkey, cluster, rpcUrl, host?, control? }.
Signals: delinquent (critical) β behind (failing catchup) β degraded (skip rate) β
low-balance (identity near the vote-fee floor) β version-drift (info) β healthy
(voting, caught up, producing). Sectioned in that order.
Safety is non-negotiable here. Control is observe-only by default β every capability
(restart | catchup | topup-identity | drain) requires both a per-validator enable flag in
control and a confirm token, and reaches the node via a control file it polls (default) or an
arg-structured SSH exec. There is deliberately no hot identity-swap: swapping a validator
identity while another copy may still be voting is the classic double-sign β slashing / fund-loss
footgun, so it is left out entirely (a future version would need a hard interlock proving the old
identity is provably stopped first). broadcast is read-only β at most a non-mutating report,
never a desk-wide restart.
Writing a new adapter
Drop a file at adapters/<name>.js exporting the contract below, and every surface works with
--adapter <name> β no engine changes.
module.exports = {
// REQUIRED
discover(opts) { /* β array of trail handles (file paths, dirs, cursors) */ },
parse(handle, opts) { /* β a normalized record (below), or null to drop it */ },
// OPTIONAL
liveness(handles, opts) { /* β Set of handles live right now; else engine falls back to recency */ },
status(record, { live, now }) { /* β a status key string from record.statusInputs */ },
project(baseRow) { /* β the public row shape for this domain (add domain fields) */ },
statuses: [ { key, title, word, color } ], // ordered vocabulary β sections + sort order
control: { // opt-in command plane
capabilities: ['pause', 'flatten', /* β¦ */],
send(target, command) { /* one unit */ },
broadcast(command) { /* all units */ },
},
};
The normalized record (the stable contract the engine depends on):
{
id, // stable unique id for the unit
shortId, // display id
label, // friendly name (project / bot / agent)
title, // plain-language "what this unit is about"
intent, // its goal / mandate
context, // array of chips, e.g. ["feat-branch"] or ["Hyperliquid", "+2.3%"]
recent: [ { actor, kind, summary, ts } ], // ring-buffered recent events
lastAction, // one-line "doing now"
lastActivityTs, // ms epoch of true last activity
statusInputs: { β¦ } // adapter-specific signals the engine maps to a status
}
Notes: the engine groups records by id (freshest wins), applies liveness, computes status,
sorts by your statuses order then recency, and runs project to produce the public row. The four
worked examples are adapters/claude-code.js, adapters/fleet.js, adapters/mev-searcher.js, and
adapters/validator-fleet.js. The two file-trail adapters share adapters/_filetrail.js (discover
glob, streamed jsonl parse, tail-read liveness, control-file append); the validator adapter is
chain-side and doesn't use it. Stream large trails (don't load them whole); bound any wide scan's
concurrency. Zero runtime dependencies, Node β₯18.
The four-ingredient fit test
A domain is a Conductor adapter β rather than a rewrite β when it has all four. Naming them up front tells you what each hook becomes:
- A fleet of units with intent. Many semi-autonomous workers, each with a goal you can state in
one line (a bot's mandate, a validator's "stay in consensus"). β
parse().title/intent. - A trail that already exists. The unit emits its own activity β an append-only log, or a
third party (the chain) reports on it β so observation is read-only and zero-instrumentation. β
discover()+parse(). - A liveness signal. Something that says "alive right now" distinct from "healthy": a heartbeat,
a fresh feed event, presence in a consensus set. β
liveness(). - Supervise-by-exception status. A small ordered vocabulary that floats the one unit needing
attention to the top (wedged, delinquent, feed-dead). β
statuses+status().
Control is the optional fifth ingredient β opt-in, structured (never shell-interpolated), with a confirm token on anything destructive and no destructive broadcast. Candidates that fit the recipe: DER/VPP dispatch, data-pipeline / ETL runs, CI fleets, crawler swarms, GPU training jobs.
License
MIT
No comments yet
Be the first to share your take.