Prototype sibling-folder fork for testing a tree-first workbench: a compact hierarchy sidebar with role badges, summaries, and an inline inspector.
π€ $\color{red}{Agent}$ .new( π₯οΈ $\color{green}{Terminal}$ .new( π€ $\color{blue}{Agent}$ ) )
An agent opens a terminal. Inside, another agent wakes up. It opens more terminals. More agents wake up. All the way down.
while (true) {
agent.createTerminal().launchAgent(); // you are here
}
The missing agent.fork() for Claude Code, Codex CLI, Gemini CLI, and Copilot CLI.
You (watching)
β
ββββββ΄βββββ
β Agent 0 β β your Copilot CLI session
ββββββ¬βββββ
ββββββββββββΌβββββββββββ
ββββββ΄βββββ ββββ΄ββββ ββββββ΄βββββ
β Agent 1 β β ... β β Agent N β β each in a visible terminal
ββββββ¬βββββ ββββββββ ββββββ¬βββββ
βββββββββΌββββββββ ββββ΄ββββ
ββββββ΄βββ ββββ΄ββββ ββ΄ββββββ β ... β
β 1.1 β β 1.2 β β 1.3 β ββββββββ β agents spawning agents
βββββββββ ββββββββ ββββββββ
Each agent can see, type, read, and spawn more of itself. You just watch.
- π₯οΈ Create visible terminals β agent opens new terminal tabs you can see
- β¨οΈ Send commands β agent types commands, you watch in real-time
- π Read output β agent reads terminal output with cursor-based incremental reads
- π Send keystrokes β Ctrl+C, arrow keys, function keys, etc.
- πΈ Screenshot β capture current terminal screen content
- π Multiple sessions β manage any number of terminals simultaneously
- π Localhost only β server binds to
127.0.0.1, no remote access - π² Hierarchical workbench β sidebar webview renders a tree-first agent hierarchy with window/group/manager/worker/subagent semantics
- π§ Compact hierarchy navigation β collapse, pin, filter, subtree rollups, right-aligned role badges, and inline summaries for large orchestration trees
- π Inline inspector β inspect the currently selected node's status, children, terminal binding, and subtree health below the tree
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VS Code β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Agent Fleet Extension β β
β β β β
β β ββββββββββββββββ ββββββββββββββββββββββββ β β
β β β Terminal β β MCP Server (SDK) β β β
β β β Manager ββββββ + HTTP transport β β β
β β β (VS Code β β 127.0.0.1:17580 β β β
β β β API) β ββββββββββββ¬ββββββββββββ β β
β β ββββββββββββββββ β β β
β β β on activate: β β
β β ββββββββββββββββββββββββββββΌβββββββββββ β β
β β β 1. Write mcp-config.json β β β
β β β 2. Write ~/.agent-fleet-port β β β
β β β 3. Register VS Code MCP API β β β
β β ββββββββββββββββββββββββββββΌβββββββββββ β β
β βββββββββββββββββββββββββββββββββββΌβββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββΌβββββββββββββββ β
β β Terminal: copilot β β β
β β ββββββββββββββββββ β β β
β β β Copilot CLI βββββββββββββββ β β
β β β reads mcp- β POST /mcp (JSON-RPC) β β
β β β config.json β β β
β β ββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Terminal: agent-controlled (visible to you) β β
β β Terminal: agent-controlled (visible to you) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β VS Code Copilot Chat (sidebar / agent mode) β β
β β Discovers via registerMcpServerDef... API β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Build
npm install && npm run build
# Package
npm run package
# Bump patch version, package, and install into regular VS Code
npm run install:localOr press F5 in VS Code to launch the Extension Development Host for development.
The extension auto-registers as an MCP server on activation. No manual setup needed.
It writes to ~/.copilot/mcp-config.json:
{
"mcpServers": {
"agent-fleet": {
"type": "http",
"url": "http://127.0.0.1:17580/mcp",
"tools": ["*"]
}
}
}It also writes ~/.agent-fleet-port with the port number for non-MCP discovery.
| Setting | Default | Description |
|---|---|---|
agentFleet.port |
17580 |
Preferred HTTP port (falls back to random if busy) |
agentFleet.maxBufferSize |
1048576 |
Max output buffer per terminal (bytes) |
agentFleet.runnerAutoLaunch |
true |
Auto-launch and reconnect the local companion runner process on activation |
agentFleet.logLevel |
info |
Log level: debug, info, warn, error |
The extension exposes three tool families:
- Terminal tools (8):
terminal_create,terminal_send,terminal_send_keys,terminal_type,terminal_read,terminal_list,terminal_close,terminal_screenshot - Runtime tools (3):
runtime_type,runtime_send_keys,runtime_read - Graph tools (10):
graph_create_group,graph_create_agent,graph_bind_agent,graph_import_terminal,graph_remove,graph_update,graph_move,graph_list,graph_stop_subtree,graph_retry
| Tool | What it does | Notes |
|---|---|---|
terminal_create |
Create a visible VS Code terminal | Returns both ephemeral terminalId and durable runtimeId metadata |
terminal_send |
Write text directly to stdin | Uses addNewline (default true) |
terminal_send_keys |
Send control/navigation keys | Good for interrupts and shell history |
terminal_type |
Type through the xterm keyboard path | Prefer for busy TUIs that need real typing semantics |
terminal_read |
Read buffered output incrementally | Supports since, waitMs, waitForOutput, waitForIdle, waitForString, raw, maxLines |
terminal_list |
List tracked terminals | Includes runtimeId, ownership, provenance, import policy, mode, and status |
terminal_close |
Close a tracked terminal | Clears the live binding |
terminal_screenshot |
Return a text snapshot of recent output | Uses maxLines |
| Tool | What it does | Notes |
|---|---|---|
graph_create_group |
Create an organizational node | Groups do not bind to terminals |
graph_create_agent |
Create an agent node | runtimeId is the canonical binding key |
graph_bind_agent |
Bind/rebind an existing agent node | terminalId is only an ephemeral fallback |
graph_import_terminal |
Explicitly import a tracked terminal into the graph | Promotes external/manual terminals to workbench/auto metadata before binding |
graph_remove |
Remove a node/subtree | Removes descendants too |
graph_update |
Update label/role/status/summary | Lightweight graph metadata edit |
graph_move |
Reparent a node | Supports sibling index |
graph_list |
Read the hierarchy tree | Returns terminal binding metadata on agent nodes |
graph_stop_subtree |
Stop all bound terminals in a subtree | For graph-managed terminals |
graph_retry |
Recreate a stopped agent runtime, or launch an idle runner-backed agent | Reuses the node's retained runtimeId when present |
These tools target the extension-owned runner/PTy execution plane rather than VS Code's built-in terminal tabs.
| Tool | What it does | Notes |
|---|---|---|
runtime_type |
Type text into a runner-backed runtime by runtimeId |
TUI-safe path for Copilot/Claude-style workers; optional submit presses Enter |
runtime_send_keys |
Send control/navigation keys to a runner-backed runtime | Use for Enter, Escape, Ctrl+C, arrows, etc. |
runtime_read |
Read buffered output from a runner-backed runtime | Supports since, waitFor*, maxLines, and optional raw ANSI output |
- Tracked terminals and graph nodes are different things. The terminal tracker can know about a terminal even when no graph node exists for it yet.
- Graph nodes are not terminal nodes. Agent nodes may bind to terminals, but the graph itself models agents/groups, not raw terminals.
runtimeIdis the durable identity.terminalIdis only the live VS Code handle for the current session/window.- External terminals are not auto-imported by default. They remain tracked-but-outside-the-graph until explicitly imported with
graph_import_terminal.
- VS Code 1.93+ (Shell Integration API)
- PowerShell or Bash with shell integration enabled
- Node.js 18+ (bundled with VS Code)
- On activation, the extension starts an HTTP server on
127.0.0.1:17580 - It registers itself in
~/.copilot/mcp-config.jsonso Copilot CLI discovers it - The terminal manager adopts existing VS Code terminals into the tracked-terminal registry
- Persisted workbench graph/view state is rehydrated
- Startup backfill only auto-imports tracked terminals whose metadata says they belong in the graph
- Copilot CLI (or any MCP client) sends JSON-RPC requests to
/mcp - The extension creates/controls real VS Code terminal tabs via the VS Code API
- All terminals are visible β you see exactly what the agent is doing
- On deactivation, the extension unregisters and cleans up
- The extension persists graph state + workbench UI state, not live PTY sessions.
- Persisted agent nodes keep their
runtimeId, but persistedterminalIdbindings are cleared. - Nodes that were previously bound to running terminals come back as
disconnecteduntil they are rebound. - Startup reconciliation collapses duplicate
runtimeIdbindings and clears any stale live-terminal claims before the tree rehydrates against the current terminal registry. - Existing VS Code terminals are tracked again on startup, but adopted external terminals stay outside the graph unless they match auto-import policy or you explicitly import them.
- Closing a bound terminal clears the live
terminalIdwhile keeping the node'sruntimeId, which is what later retry/rebind flows use.
Use this flow to verify the current implementation without assuming more than the code does today.
- Install / reload
- Run
npm install - Run
npm run build - Press
F5to launch the Extension Development Host, or runnpm run install:localto bump the patch version and install the VSIX into regular VS Code - In the host window, run Developer: Reload Window once to verify activation + rehydration
- Run
- Confirm MCP registration
- Check
~/.copilot/mcp-config.jsonforagent-fleet - Check
~/.agent-fleet-portor the Agent Fleet output channel if the preferred port was busy
- Check
- Create a workbench-owned runtime
- Call
terminal_createwithout overriding ownership metadata - Confirm
terminal_listshowsruntimeKind: "vscode-terminal",ownership: "workbench",provenance: "created"(or"recovered"when reusing aruntimeId), andgraphImportPolicy: "auto"
- Call
- Bind by
runtimeId- Call
graph_create_agentorgraph_bind_agentwith the terminal'sruntimeId - Confirm the node reflects
runtimeId,runtimeKind: "vscode-terminal", and the currentterminalId - If you later close/retry the terminal, confirm the node keeps the same
runtimeId
- Call
- Verify restart / restore behavior
- Reload the window
- Confirm the graph reappears, but previously bound nodes restore as
disconnecteduntil rebound - Confirm tracked terminal count can differ from graph node count after reload
- Verify auto-import policy
- Workbench-created or recovered terminals with
graphImportPolicy: "auto"are eligible for startup backfill into the graph - Adopted/external terminals default to manual import and should remain tracked-but-outside-the-graph after reload
- Workbench-created or recovered terminals with
- Verify manual import / promote path
- Open or keep an external terminal that the extension tracks
- Call
terminal_listand identify itsterminalId - Call
graph_import_terminalwith thatterminalId - Confirm the terminal is now represented by an agent node and the bound metadata is promoted into the workbench/auto path
- Verify focus behavior
terminal_send/terminal_send_keyspreserve editor focus while sending inputterminal_typeintentionally focuses the terminal because it uses the active-terminal typing path
- Auto-import on startup: only terminals that are already workbench-owned/recovered and marked
graphImportPolicy: "auto" - Tracked but not in graph: adopted external terminals
- Explicit import path:
terminal_listβgraph_import_terminal - Canonical rebind concept:
runtimeId - Current VS Code terminal backend:
runtimeKind: "vscode-terminal" - Phase 3 runner backend:
runtimeKind: "runner-process"is now part of graph binding and the custom runtime panel flow - Phase 3 host bridge: the extension auto-discovers / auto-launches the local
runner-processcompanion and keeps a reconnectable host-side bridge ready
Use this flow to verify the custom execution-plane slice introduced in Phase 3.
- Build the extension + smoke targets
npm run build
- Run the backend/runtime smoke
npm run smoke:phase3- This validates:
graph_create_agent-style idle runner intentgraph_retrylaunching a realrunner-processshell- input/output round-trips against the live shell
- runtime-level TUI-safe typing, keypress, and read flows against a second runner runtime
- resize through the runner bridge
- reconnect by rebuilding the host-side service against the same runner process
- stop + retry while retaining the same
runtimeId
- Manual panel sanity check in VS Code
- Launch the Extension Development Host (
F5) - Create or recover a runner-backed graph node
- Use the sidebar's focus/open action on that node
- Confirm the dedicated runtime terminal panel hydrates output, accepts line input, and reflects stop state changes
- Launch the Extension Development Host (
# Install dependencies
npm install
# Build (one-time)
npm run build
# Watch mode (rebuild on change)
npm run watch
# Launch Extension Development Host
# Press F5 in VS Code
# Package for distribution
npm run package
# Local install into regular VS Code
# Automatically bumps 0.3.0 -> 0.3.1 -> 0.3.2 ...
npm run install:localsrc/
βββ extension.ts # Extension entry point, lifecycle management
βββ runner/
β βββ client.ts # Host-side JSON-RPC client for the companion runner
β βββ launcher.ts # Discovery + auto-launch/bootstrap for the local runner process
β βββ service.ts # Lightweight lifecycle/cache bridge owned by the extension host
β βββ main.ts # Companion runner entry point
β βββ hostBridgeSmoke.ts # Narrow dev smoke for hostβrunner round-trips
βββ config/
β βββ autoRegister.ts # MCP server registration (config file + VS Code API)
βββ graph/
β βββ graphManager.ts # Persisted graph state + binding sanitization on save
β βββ mcpTools.ts # graph_* MCP tool registrations
β βββ orchestrator.ts # Agent graph lifecycle, import, bind, retry, startup backfill
β βββ workbenchStateStore.ts # Workspace-state persistence/rehydration
βββ server/
β βββ httpServer.ts # HTTP server with auto-restart + socket tracking
β βββ mcpServer.ts # MCP tool definitions (via @modelcontextprotocol/sdk)
βββ terminal/
β βββ manager.ts # Terminal lifecycle, tracking, adoption, import metadata
β βββ outputBuffer.ts # Ring buffer with cursor-based reads + waitFor*
β βββ shellIntegration.ts # VS Code Shell Integration API wrapper
β βββ pseudoTerminal.ts # PTY mode for advanced terminal control
β βββ runtimeMetadata.ts # ownership/provenance/graph import policy defaults
βββ webview/
β βββ agentTreeProtocol.ts # Host/webview message contract
β βββ agentTreeViewProvider.ts # Thin host bridge: lifecycle, sync, actions, terminal focus
β βββ agentTreeWebviewHtml.ts # Bundled frontend payload (HTML/CSS/JS)
βββ utils/
βββ logger.ts # Structured leveled logging to OutputChannel
βββ ansiStrip.ts # Comprehensive ANSI escape sequence removal
Check the output channel: View β Output β Agent Fleet
Verify the config: cat ~/.copilot/mcp-config.json
Expected:
{
"mcpServers": {
"agent-fleet": {
"type": "http",
"url": "http://127.0.0.1:17580/mcp",
"tools": ["*"]
}
}
}If port 17580 is busy, the extension auto-selects a random port. Check ~/.agent-fleet-port for the actual port, or look in the Output channel.
MIT