What is Runyard
Runyard is a self-hosted control plane for agent runs. It exposes a capability catalog over MCP, CLI, HTTP API, and a Web Hub; dispatches work to local or remote runners; and stores logs, events, artifacts, approvals, skills, agents, and knowledge centrally. The codebase ships as the smithers-hub package; Runyard is the public product name and the names compose without breakage.
Install
Requirements: Node.js 22.5+ (the server uses --experimental-sqlite), pnpm, and a writable data/ directory.
git clone https://github.com/yolo-maxi/runyard cd runyard pnpm install pnpm build:vendor # vendors highlight.js + react-flow for the workflow viewer
Public clients can also install the CLI from a running hub with bash <(curl -fsSL https://hub.example.com/install.sh). hub.example.com is just an example domain — use whatever you host on.
Run locally
PORT=43117 BASE_URL=http://127.0.0.1:43117 pnpm start # server writes data/bootstrap-token.txt on first boot open http://127.0.0.1:43117/app
Topology options
Runyard is built for one private Hub per company or org. The Hub owns durable state; runners are disposable workers that poll over HTTPS. You can adapt the deployment shape to your threat model and hardware.
- Small/local. Hub and one runner on the same machine. Best for development or a solo operator. Keep runner capacity low.
- Private org. Hub on a small VPS with TLS reverse proxy, SQLite/artifacts on local disk, runners on separate machines.
- Dedicated runner pool. One or more worker VPSes advertise tags and capacity. The Hub queue stays centralized; runners do not access SQLite directly.
- Public docs + private Hub. Serve setup docs as a static public site, while `/app`, API, MCP, runner tokens, SQLite, and artifacts stay on the private Hub.
- Our deployment.
runyard.repo.boxis static docs.hub.repo.boxis the live token-protected Hub. Build/agent work runs on a separate worker host, not on the serving box.
Deploy
- Node + SQLite single-process server. No external database.
- Artifacts on local disk under
data/artifacts/. Back up by copying thedata/directory. - Bearer-token authentication only — no SaaS user database.
- One private deployment per company/org. Each gets its own domain & bootstrap token.
- Reverse-proxy with TLS in front (Caddy, nginx, Traefik). The Hub does not terminate TLS itself.
- Runners may live on the same box or on remote VPSes, Linux workstations, or macOS laptops.
Auth tokens
Runyard uses long-lived access tokens. On first boot the server writes data/bootstrap-token.txt — that becomes the operator key for Web login, CLI, MCP, API, and runner registration. Issue additional tokens from the Connect tab inside the Hub.
CLI
smithers-hub login --url https://hub.example.com --token shub_...
smithers-hub capabilities
smithers-hub capability prepare-spec
smithers-hub run prepare-spec --input '{"goal":"Prepare a rollout spec"}'
smithers-hub runs
smithers-hub runners
smithers-hub approvals
smithers-hub approve appr_...
Multiple orgs work like git remotes:
smithers-hub login --remote acme --url https://acme-hub.example smithers-hub mcp install --all --remote acme # installs as smithers-hub-acme smithers-hub remote use acme smithers-hub remotes
MCP config
{
"command": "smithers-hub-mcp",
"env": {
"SMITHERS_HUB_URL": "https://hub.example.com",
"SMITHERS_HUB_TOKEN": "shub_..."
}
}
From a running Hub, smithers-hub mcp install --all writes this config into every detected agent — Claude Code/Desktop, Codex, Cursor, Windsurf, Gemini, VS Code.
Runner pool
Runners are stateless processes that poll the Hub for queued work. Each runner advertises a tag set and an optional capacity:
SMITHERS_HUB_URL=https://hub.example.com \ SMITHERS_HUB_TOKEN=shub_... \ SMITHERS_RUNNER_NAME=hetzner-vps-runner \ SMITHERS_RUNNER_LOCATION=vps \ SMITHERS_RUNNER_TAGS=linux,node,git,shell,web,smithers \ SMITHERS_RUNNER_CAPACITY=4 \ smithers-hub-runner
- Tag match. The Hub only schedules a run on a runner that advertises every required tag.
- Capacity. A pool runner with
capacity=4can hold four concurrent slots; the Web Hub shows the slot grid live. - No double-claim. Two runners polling for the same queued run can never both pick it up — the Hub atomically assigns one and rejects the other.
- Disposable. Restart freely; the Hub keeps the durable record.
Telegram approvals
Approval notifications are designed for a private operator DM. Configure TELEGRAM_BOT_TOKEN plus TELEGRAM_APPROVAL_CHAT_ID or SMITHERS_TELEGRAM_APPROVAL_CHAT_ID. Legacy TELEGRAM_CHAT_ID/TELEGRAM_THREAD_ID remains a fallback when no private approval chat is set. Approve/reject/request-changes from Web, API, CLI, MCP, or the Telegram buttons; the decision is recorded once in the Hub regardless of surface.
Environment variables
Required for production:
BASE_URL— the public origin (e.g.https://hub.example.com).PORT— port the server binds to.SMITHERS_HUB_SESSION_SECRET— long random string. Sessions and CSRF derive from it.SMITHERS_HUB_BOOTSTRAP_TOKENor read access todata/bootstrap-token.txt.
Optional:
TELEGRAM_BOT_TOKEN,TELEGRAM_APPROVAL_CHAT_ID(orSMITHERS_TELEGRAM_APPROVAL_CHAT_ID) for approval DMs.TELEGRAM_CHAT_ID/TELEGRAM_THREAD_IDas a backward-compatible fallback.SMITHERS_HUB_INSTANCE_NAMEto label a deployment in/api/version.
Runner-side variables (used by smithers-hub-runner):
SMITHERS_HUB_URL,SMITHERS_HUB_TOKEN— connection & auth.SMITHERS_RUNNER_NAME,SMITHERS_RUNNER_LOCATION,SMITHERS_RUNNER_TAGS,SMITHERS_RUNNER_CAPACITY.SMITHERS_WORKSPACE— directory the runner uses as scratch space for Smithers workflows.
Security model
- Single auth primitive. Every surface (Web, API, CLI, MCP, runner) authenticates with a Hub access token.
- Private deployment. Runyard is meant for one org per deployment behind a domain you control. There is no multi-tenant SaaS database.
- Static public docs. If you publish a marketing/setup site, make it static. Do not expose `/app`, access tokens, SQLite files, artifacts, or environment files.
- Bootstrap token. The first token is written to
data/bootstrap-token.txt; rotate it via the Connect tab once you are logged in. - Scopes. API tokens can be scoped (
api,mcp,approvals) so approvers can be issued narrower keys. - Permissive runners. Runners execute work on behalf of authenticated requests. Treat the runner host as if it has the privileges of any operator who can submit a capability run; isolate sensitive runners with restricted tag sets.
- Headers. The server sets strict
content-security-policy,x-content-type-options,referrer-policy, andstrict-transport-security(when served over HTTPS). - Rate limiting. Login and general API buckets are enforced to discourage credential stuffing and scraping.
Verification
pnpm test # full Node test runner curl -s http://127.0.0.1:43117/healthz # liveness curl -s http://127.0.0.1:43117/readyz # DB warm curl -s http://127.0.0.1:43117/llms.txt # agent discovery copy curl -s http://127.0.0.1:43117/openapi.json | head
Manual checks after install:
- Log in to
/appwith the bootstrap token. - Run Prepare Spec.
- Start a runner and confirm the run succeeds.
- Open the run detail page and download the generated artifact.
- Run Implement; approve it via Web or MCP; confirm it queues and executes.
- Configure a local MCP client with
smithers-hub-mcpand calllist_capabilities.
Open source & contributions
- The repo is structured for open-source release:
bin/,src/,public/,specs/,workflow-templates/,tests/. - Specs live under
specs/. Read them before changing product behaviour — they describe intent, expectations, decisions, and acceptance checks. - The gated pipeline runs
pnpm test,git diff --check, and smoke-checks/healthz,/app,/docs,/llms.txtbefore deploy. - File issues and PRs against the public Runyard repo. Keep private hostnames out of code and use
hub.example.comin docs.
Deep links
Every console page, run, workflow, agent, skill, knowledge entry, run artifact, and approval has a stable URL — paste it into chat or docs and it survives reload. Each card and detail view shows a 🔗 button to copy that link to your clipboard. Examples:
/app#runs # all runs (home) /app#runs/<id> # one run's detail /app#runs/<id>/logs # jump to the log timeline /app#runs/<id>/artifacts # jump to artifacts /app#runs/<id>/artifacts/<artifact-id> # one artifact inside its run /app#workflows/<slug> # workflow detail (description, agents, runs) /app#workflows/<slug>/run # workflow detail with the Run form open /app#workflows/<slug>/runs # latest runs for this workflow /app#workflows/<slug>/edit # workflow detail with the Edit modal open /app#agents/agents/<slug> # a specific agent /app#agents/skills/<slug> # a specific skill /app#agents/knowledge/<slug> # a knowledge entry /app#approvals/<id> # an approval /app#tokens | #runners | #audit | #connect | #settings
The same URLs come back as deepLink fields on most API responses (runs, capabilities, artifacts, approvals) so MCP/CLI clients can hand operators a clickable link with zero extra work.