Control plane for coding agents

Queue tasks. Dispatch to the machine that owns the code.

Nothing lands until you review the diff. Switchyard stores boards and tasks, claims them with a single dispatcher, runs them on the machine that owns your code, and holds every result in review until you inspect and approve it.

GoSQLiteReact + VitegRPC with HTTP fallback
Engineering board · Task flow preview
one dispatcher

Ready

Add rate-limit tests

cover validation edge cases

dshMac
+42 −7

Running

Blocked

Update node service

executor unavailable

shellWindows

Review

Done

Agents run where the code lives.

Your repo is on a Mac or Windows box, not on the server running the scheduler.

Agents shouldn’t commit on their own.

A passing run still needs a human look at the diff.

One dispatcher, one queue.

Two schedulers claiming the same task is a bug, not a feature.

Two planes, one queue

The VPS runs the control plane. Node-agent runs the execution plane on every host that owns source code. Remote workspaces are never used as cwd by local VPS processes.

Switchyard control and execution planesKanban boards feed a single dispatcher, which posts tasks to the node-agent server on the control plane. The server dispatches over gRPC, with HTTP long-poll as the fallback, to the Mac and Windows agents that own the source code. Results return to the review gate, then through the board and back to the boards.CONTROL PLANE · VPSEXECUTION PLANE · WORKERSgRPC preferred · HTTP fallbackPOST /api/dispatchresultPOST /api/nodes/:id/result
  1. Control plane: boards store tasks in SQLite per board.
  2. The single dispatcher polls every 30 seconds and sends POST /api/dispatch to the node-agent server.
  3. Execution plane: Mac and Windows agents run on the hosts that own the source code. gRPC is preferred; HTTP long-poll is the fallback.
  4. Workers return results through POST /api/nodes/:id/result.
  5. The review gate fetches the diff. A person approves before the task moves to done.

Paths like /Users/… and C:\… are routed to their registered host, never executed on the VPS.

Success lands in review, never in done

Every executor goes through the same gate.

Triage
Todo
Scheduled
Ready
Running
Blocked
Review
Done
Archived
1

Agent mutates the working tree but does not commit or push.

2

Board fetches the diff from the workspace host.

3

You pick Commit or Commit & Push.

4

Board runs approval over SSH.

5

Status moves from review to done.

Successful results become review, not done.
A plain status PATCH cannot move a task from review to done.
Failures retry up to 3 times, then become blocked.
Shell tasks with an empty command are rejected up front.
Review gate API
GET  /api/boards/{slug}/tasks/{id}/diff
POST /api/boards/{slug}/tasks/{id}/approve
{"action": "commit", "message": "optional commit message"}

Pick a runtime only when you need to

Tasks store human intent as a title and description. Choose an executor only to force a specific runtime. Every executor below runs through node-agent on the workspace host.

hermes

Hermes on the workspace host.

codex

Codex on the workspace host.

claude

Claude CLI on the workspace host.

commandcode

CommandCode on the workspace host.

dsh

DSH CLI on the workspace host.

shell

Direct remote commands; command is the only executed input.

auto

legacy · SSH from the VPS

Hermes on the VPS, file access over SSH. Kept for backward compatibility.

CommandCode runs with --yolo, which lets the worker edit files and run shell commands. Use it only on trusted nodes.

Node capabilities

hermes · codex · claude · commandcode · dsh · shell

Request

dshThe server picks a node by workspace prefix plus executor capability. A node without that executor is rejected with executor unavailable.

gRPC when it’s up, HTTP when it isn’t

Node-agent prefers gRPC and falls back to HTTP long-poll when the stream drops. Operators can see which path a task took.

Worker connectionIllustration · grpc
gRPC streamactive
HTTP long-pollfallback

Client-side illustration only. No network calls.

SettingMeaning
NODE_AGENT_TRANSPORT=autogRPC preferred, HTTP fallback
NODE_AGENT_TRANSPORT=grpcFail-closed when gRPC is unavailable
NODE_AGENT_TRANSPORT=httpForces the compatibility lane
Keep gRPC port 8789 private on the tailnet. Tailscale connects VPS and workers.

Less context in, less noise out

Three layers keep agent prompts and shell output small.

codegraph

Structural index of the codebase on the workspace host.

Used for hermes, codex and commandcode.

rtk

Shortens verbose shell commands and output within bounded timeouts.

800 ms hook check/rewrite · 2 s --ultra-compact cap

caveman

Optional compact output for shell over 8 KiB, with fail-open behavior.

NODE_AGENT_SHELL_CAVEMAN=1

Shell tasks skip AGENTS/README/codegraph prompt injection by default.

A small, boring API

A compact surface for boards, tasks, workspaces, flow and remote dispatch.

MethodPathPurpose
GET / POST/api/boardsBoards and tasks
PATCH/api/boards/{slug}/tasks/{id}/statusStatus transitions
PATCH/api/boards/{slug}/tasks/{id}/assigneeChange assignee
GET/api/boards/{slug}/tasks/{id}/diffWorkspace diff
POST/api/boards/{slug}/tasks/{id}/approveCommit or push
GET/POST/PUT/DELETE/api/workspaces*Workspaces and health
GET/api/flow/activeActive flow tasks
POST/api/remote/dispatchManual dispatch
GET/api/nodesNode status

All /api/* routes require the kanban_session HttpOnly cookie except the four /api/auth/* routes.

{
  "task_id": "t1",
  "board": "saas",
  "message": "Fix login validation",
  "workspace": "/Users/<user>/Development/saas",
  "executor": "dsh"
}

Roll out the VPS first

Mac and Windows agents keep running with their previous capabilities until you upgrade them.

  1. Build and restart node-agent server on the VPS (HTTP :8788, gRPC :8789).

  2. Build and restart kanban-board (Switchyard).

  3. Cross-build the worker binary (GOOS=darwin GOARCH=arm64 for Apple Silicon).

  4. Reinstall the agent on Mac or Windows and restart the LaunchAgent or service.

  5. Confirm node is idle and capability and transports show at /api/nodes.

  6. Run a dispatch canary: expect success=true, a delivery_id, and transport grpc (or fallback http).

Build and deploy commands
go vet ./...
go test ./...
go build -o bin/kanban-board ./cmd/server
cd web && pnpm build
pm2 restart kanban-board
Production serves static web/dist from the Go binary; no Node or Bun runtime stays alive.
Frontend builds are RAM-heavy on a 2 GB VPS.

Questions about the review gate

Switchyard mascot

Put a gate in front of your agents

Control plane in Go, execution plane in node-agent.