agentop for VS Code
Watch Claude Code multi-agent workflows without leaving the editor.
Claude Code writes a journal and a transcript per subagent to disk while a
workflow runs, but the /workflows view that reads them only exists in the
official CLI. This extension surfaces the same information inside VS Code: a
status bar item that says whether anything needs you, a Runs panel in the
Activity Bar, and each agent's transcript as an ordinary read-only editor
document.
It talks to the agentop binary and never to the files that binary reads.
Nothing is sent anywhere. There is no API key, no account, and no service.
Requirements
The agentop binary has to be reachable from the machine the extension host
runs on. Install it one of three ways:
brew install pimlabs/tap/agentop
npm install -g @pimlabs/agentop
curl -fsSL https://raw.githubusercontent.com/pimlabs/agentop/main/install.sh | sh
If it lands somewhere that is not on PATH, point agentop.binaryPath at it.
Run agentop: Report Diagnostics from the Command Palette when in doubt; it
prints what the extension can and cannot see.
Installing the extension
It is not on the Marketplace or Open VSX yet. Download agentop-<version>.vsix
from the Releases page and
install it:
code --install-extension agentop-<version>.vsix
Extension releases are tagged vscode-v* and move independently of the
binary's v* tags. The two do not have to be on the same number.
What you get
A status bar item. It shows the newest run: how far along it is, how many
of its agents are actually running, and how many need attention. Once a run
goes quiet for longer than its own threshold it also says how long, because a
run hanging since yesterday and a run mid-sentence are otherwise the same three
numbers. It appears on its own, without you running a command first, because
that is the only thing a status bar is for.
A Runs panel in the Activity Bar. Runs sit under the session they came out
of, the way the terminal groups them: on one real machine 92 of 112 runs shared
a session with another run, so a flat list said nothing about which of them came
from the same conversation. A session that has nothing to call itself, or that
holds a single run already named after it, gets no row of its own and its runs
stand at the top level instead. Agents are listed under their run, and an agent is the last level: clicking one
opens its transcript, which is where its tool calls are. A row has one line, so it says whichever thing the agent's state
raises: an agent still working shows the tool call it is on and how long it has
been going, one that has stalled shows how long it has been idle and the call
it stuck on, and a finished one shows its duration, model and output tokens.
Hovering any agent gives you all of them at once, plus its full name when the
row was too narrow to hold it. Expanding a run is what fetches its agents, so a
window with fifty runs on disk does not read fifty transcripts.
Transcripts as editor documents. Open Transcript and Open Transcript to
the Side render the selected agent through the agentop: URI scheme as a
read-only document, so ordinary editor search, split views and copy all work on
it. The document is laid out in sections: what needs attention, the prompt, the
timeline, and the result. Failed and stalled states are highlighted in place.
Mission Control, a single page holding the run list and the agents inside
the selected run. It is a static page redrawn on each poll, with no scripts of
its own.
An output channel that is not empty. It says which binary was chosen and
how it was found, which home is being read, and what that binary answered, from
the moment the window opens. Open it from View → Output and pick agentop
when something looks wrong.
A notification when a run stops moving while its agents are still marked
running. Only that case: a stalled or overspending agent usually recovers on
its own. Runs already stuck when the window opened are never announced, a run
has to stay stuck for two minutes before you hear about it, and one message is
shown across all your windows rather than one per window. Turn it off with
agentop.notifications, or from the message itself.
A terminal profile. Open a terminal with the agentop profile and the full
TUI starts in that tab.
Commands
Every command is under the agentop category in the Command Palette.
| Command |
What it does |
| Mission Control |
Open the run and agent overview page |
| Open in Terminal |
Start the TUI in a new terminal tab |
| Open Transcript |
Open the selected agent's transcript as a document |
| Open Transcript to the Side |
The same, in a split editor |
| Copy Agent ID |
Copy the selected agent's id to the clipboard |
| Refresh |
Re-read now, without waiting for the interval |
| Report Diagnostics |
Print what the extension sees, and why it might not |
The transcript and copy commands act on the selection in the Runs panel, so
they are hidden from the Command Palette and appear in that panel's context
menu instead.
Settings
| Setting |
Default |
What it does |
agentop.binaryPath |
"" |
Full path to the agentop binary. Empty means: use one bundled with the extension, then whatever is on PATH. |
agentop.home |
"" |
Directory to search for .claude and .claude-work. Empty means the home directory of the machine the extension host runs on. |
agentop.refreshInterval |
5 |
Seconds between disk checks. |
agentop.notifications |
true |
Show a message when a run stops moving with agents still running. Never fires for runs that were already stuck when the window opened. |
Where it looks, and what to do when it looks in the wrong place
The extension is extensionKind: ["workspace"], never "ui". Under Remote
SSH, a dev container, or WSL, Claude Code writes its journal on the remote
machine, so a UI-kind extension would read the wrong disk and honestly report
nothing at all.
That covers the common cases and leaves one it cannot solve by itself: an
editor on Windows driving a workspace in WSL, where the workflow was started on
the Windows side. Point agentop.home at the Windows home directory, which WSL
mounts:
/mnt/c/Users/<you>
Report Diagnostics names the remote kind, the platform, the home directory
it resolved, which of the three routes found the binary, and the schema version
the binary answered with. Read it before filing anything.
How it reads the disk, and what that costs
The status bar and the Runs panel's top level are fed by one long-lived
child process, agentop watch --ndjson --interval <refreshInterval>s, whose
output is decoded line by line as it arrives. The extension used to start a
fresh agentop runs --json on every tick instead. Measured against one
developer's own home directory, that cost 21.94s of CPU over a 62s window; the
persistent stream cost 1.21s of CPU over the following 60s, roughly 18 times
less in the same wall clock.
The child is killed on deactivate. If it exits, errors, or goes silent
without exiting, a watchdog kills it and starts a new one with a doubling
backoff up to a minute. An idle window backs the poll off the same way, so a
window left open overnight is not paying a per-second price for a machine where
nothing is happening.
One thing is still on demand: expanding a run in the panel runs
agentop show <id> --json for that run. Streaming a single run's detail through
the same channel is a separate step, deliberately not folded into this one.
Rules this client lives under
- It never reads
journal.jsonl or a transcript. Everything arrives
through the agentop binary's JSON output. This is invariant 1 in
ARCHITECTURE.md; the moment a client parses upstream files for a shortcut,
a format change needs fixing in two places instead of one.
extensionKind is ["workspace"], for the reason above.
activationEvents is ["onStartupFinished"], against the ROADMAP's own
instruction to avoid startup activation. Leaving it out meant VS Code derived
activation from contributes alone, so the extension stayed asleep until a
command was run and the status bar, created during activate(), could never
appear on its own. The cost that instruction wanted to avoid is real and is
paid down in schedule.ts instead, by backing an idle window off to one
check a minute.
- Releases are tagged
vscode-v*, separate from the binary's v*.
For contributors
npm ci
npm run compile
npm test
npm run check type-checks without emitting.
src/contract.ts here is a generated copy and is not tracked:
scripts/vendor.mjs writes it from clients/contract/src/contract.ts on
prepare, precompile, precheck and pretest. Edit the source in
clients/contract; an edit to the copy is destroyed by the next build with no
warning. The tests that read testdata/contract/*.json live in that package
too, so a key renamed in internal/jsonout fails there first.
Nothing has to be installed in clients/contract first. vendor.mjs reads
that file off the disk and writes a copy, so no npm link and no node_modules
of that package are involved. clients/web is the one that consumes the
contract as a real dependency, and it does need it installed.
The copy exists rather than a link because .vscodeignore excludes
node_modules/** and main points into a flat out/: a linked package
resolves fine in the dev tree and throws MODULE_NOT_FOUND in a user's editor.