# Runyard (codebase: runyard) Self-hosted control plane for agent runs. Agents discover workflows over MCP/CLI/HTTP, runners execute them, and the Hub stores the durable record of logs, events, artifacts, approvals, skills, agents, and knowledge. One private deployment per company/org. Primary agent interface: - MCP server: runyard-mcp - Stable HTTP API: http://runyard.synclave.net/api/v1 - Compatibility HTTP API: http://runyard.synclave.net/api - OpenAPI: http://runyard.synclave.net/openapi.json - Menu (authenticated): http://runyard.synclave.net/api/menu - Workflow catalog (authenticated): http://runyard.synclave.net/api/workflows - Setup docs: http://runyard.synclave.net/docs/quickstart Tools (mirrors get_menu; the MCP server advertises the full set over tools/list): - get_menu - list_workflows - describe_workflow - create_workflow - update_workflow - delete_workflow - plan_workflow_run - preflight_workflow - run_workflow - get_run_status - get_run_logs - get_run_artifacts - get_run_usage - get_usage_summary - list_copilot_conversations - create_copilot_conversation - get_copilot_conversation - send_copilot_message - confirm_copilot_action - archive_copilot_conversation - get_assistant_status - ask_assistant - get_run_flow - get_run_attempts - retry_run - list_attention_runs - list_factory_items - get_factory_item - create_factory_item - update_factory_item - delete_factory_item - link_factory_item_run - unlink_factory_item_run - list_boards - get_board - create_board - update_board - resume_run - list_runners - list_pending_approvals - list_approvals - list_hooks - list_schedules - get_schedule - preview_schedule - create_schedule - update_schedule - enable_schedule - disable_schedule - delete_schedule - run_schedule_now - download_artifact - get_dashboard - list_workflow_bundles - get_workflow_bundle - list_ci_repositories - get_ci_pipeline - dispatch_ci_run - enable_ci_repository - disable_ci_repository - set_ci_repository_trust API-first guarantee: the web app is an ordinary client of the same HTTP API. Every operation is documented in http://runyard.synclave.net/openapi.json, and everything the app can show or do is available over the API and MCP — a client built on those surfaces loses nothing. Workflows: - This deployment's catalog is private. Authenticate, then call get_menu / list_workflows (MCP), `runyard menu` (CLI), or GET http://runyard.synclave.net/api/menu (HTTP) for the live list. - Discovery defaults to product workflows. Admin callers may explicitly include operations/internal workflows with includeAudience; audience is catalog visibility only, not a replacement for scopes or adminOnly. Execution modes: - local -> runners tagged local - remote -> runners tagged vps or remote Run input recommendation: - For agent-created runs, include input.title: a short human-readable title that explains the specific job. - Keep it concise and specific, e.g. "Audit checkout flow mobile states". - The title is advisory, not required; it helps approval cards, run lists, and human handoff stay decipherable. Authenticate with a Hub access token using Bearer auth. Tokens carry scopes (api, mcp, approvals, read, runner, admin); a token with only the read scope is read-only — it can inspect workflows, runs, logs, approvals, and schedules but cannot change anything. Ask this Hub's administrator for a token; admins issue them from the Tokens page in the web app (GET /api/tokens/scopes lists scopes and presets). API groups: - The API is organized into groups, exposed as OpenAPI tags: workflows, runs, work (work items / tickets), approvals, automation (schedules + endpoints), library (agents, skills, knowledge, hooks), distribution (bundles, packages), admin (tokens, secrets, audit, alerts, updates), and system (health, version, menu, dashboard, runners). - Stable contract: /api/v1. Build new HTTP agents against /api/v1. - Compatibility aliases: canonical /api paths remain supported for existing clients, but /api/v1 is the versioned migration target. - Response headers on /api routes identify lifecycle state: X-Runyard-API-Version, X-Runyard-API-Lifecycle, and, for compatibility aliases, X-Runyard-API-Stable-Path. - Deprecation policy: breaking changes require a new major prefix, a written migration guide, at least 180 days of Deprecation notice, and Sunset headers at least 90 days before removal. Supported /api compatibility aliases are not deprecated and have no sunset date. - Grouped operations answer at /api/v1 paths, e.g. http://runyard.synclave.net/api/v1/automation/schedules or http://runyard.synclave.net/api/v1/runs. /api/v1 paths share the compatibility route's auth and scopes. - Normalized failures use { error: { code, message, status, details?, requestId? } }. Older endpoints may still return legacy string errors until migrated; idempotency-key failures keep code=missing_idempotency_key. Run path: 1. Discover with get_menu / list_workflows. 2. Choose local or remote execution. 3. Ask for a launch plan with plan_workflow_run or `runyard plan `. 4. Start with run_workflow or `runyard run --where local|remote`. For agent-created runs, set input.title when practical. Rough or unverified input? Preflight first: preflight_workflow, `runyard preflight `, or POST /api/workflows/{id}/preflight. 5. Fetch status, logs, outputs, artifacts, and the unified timeline from the Hub. 6. Operators can run `runyard tail ` for an NDJSON timeline stream. Operator assistant: - Use typed copilot conversations for headless assistance: create_copilot_conversation, send_copilot_message, then confirm_copilot_action for any returned stored actionId. - Copilot actions are server-validated, persisted, audited, and confirmation-gated; mutation actions execute exactly once through Runyard's canonical action executors. - ask_assistant is deprecated compatibility. It wraps the typed copilot message endpoint and returns typed actions; it is not the legacy browser button action surface. Creating and editing workflows: - Workflows are stored in the Hub database as immutable, versioned source bundles. Create or update them by sending workflow source bytes through MCP (create_workflow / update_workflow), HTTP (POST /api/workflows, PATCH /api/workflows/{id}), or by importing a portable package file (see below). - Do NOT write workflow files to disk: custom workflows that reference bare workflow.entry file paths without source bytes are rejected. Repository-authored workflow files are source templates only; installed workflows run from durable DB definitions pointing at workflow.bundleId. - Set audience to product for normal team abilities, operations for admin/operator runbooks, and internal for Hub plumbing that should not appear in default API/MCP/CLI/Web discovery. Schedules (cron and one-shot): - Schedules fire workflows on a recurring 5-field cron cadence (with IANA timezone support) or once at an ISO runAt timestamp. - HTTP lifecycle: GET/POST http://runyard.synclave.net/api/schedules, GET /api/schedules/preview?cron=...&timezone=..., and per-schedule GET/PATCH/DELETE /api/schedules/{id} plus POST /api/schedules/{id}/enable | /disable | /run-now (run-now requires Idempotency-Key). - Equivalent MCP tools: list_schedules, get_schedule, preview_schedule, create_schedule, update_schedule, enable_schedule, disable_schedule, delete_schedule, run_schedule_now. - Each fire creates a normal run (origin.type "schedule", a run.scheduled event, and last run status recorded on the schedule), so scheduled runs are traced like any other run. Response endpoints (optional): - POST /api/workflows/:id/run requires Idempotency-Key and accepts an optional responseEndpoint: { type: "http"|"telegram", config: { ... } } - Polling /api/runs/:id is always available and stays canonical. - Endpoint config is validated server-side; secrets are not echoed back in API responses, events, or audit log entries. - When the run reaches a terminal state (succeeded/failed/cancelled) the Hub posts a sanitized payload to http endpoints and a concise message to telegram endpoints; telegram delivery requires the Hub's Telegram integration to be configured. Delivery state (status / attempts / last_error / delivered_at) is visible on GET /api/runs/:id under responseEndpoints[]. Usage metering & budgets: - Every run records the model calls it consumed. Per-call usage records stream as run.usage events and aggregate onto the run as run.usage: { totalTokens, promptTokens, completionTokens, costMicros, calls, byModel, byProvider } — visible on GET /api/runs, GET /api/runs/{id}, GET /api/runs/{id}/usage (per-call records + budget), the get_run_usage MCP tool, and terminal response-endpoint payloads (usage / budget / budgetStop fields). - Run creation accepts an optional budget: { maxTokens?, maxCostMicros? } (top-level body field or input.budget; micro-USD, 1000000 = $1). The budget is a hard ceiling: when metered usage reaches it the Hub emits run.budget.exceeded and terminates the run with the distinct terminal status budget_exceeded (not a generic failure), refusing further metered provider calls. - Capture is at the inference boundary: gateway-metered runs route the child agent's model calls through the Hub's metering gateway with a run-scoped token (the provider key stays on the Hub), and all engine-observed model calls are recorded from structured usage telemetry — never scraped from logs. - Budgeted runs carry a computed budgetStatus (spent vs limit, remaining, percentUsed, nearLimit at 80%) in list/detail payloads. - GET /api/usage/summary (get_usage_summary) rolls up metered usage for a window (?days=, default 30): fleet totals, a per-workflow breakdown sorted by spend, and how many runs stopped at budget. Rate limits: - Every /api response includes RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset for the active bucket. Values are non-sensitive counts only: limit, remaining requests after this response, and whole seconds until reset. Bucket names, bucket keys, and client IPs are never exposed. - 429 responses keep Retry-After with the same whole-second retry delay as RateLimit-Reset. Runner protocol traffic uses an isolated high-capacity bucket so machine heartbeats/events cannot exhaust the general API bucket, and the general API cannot starve runners. Paused runs (recoverable interruptions): - A run interrupted by a recoverable external condition — provider credits or quota exhausted, an operator pause — parks with the non-terminal status paused instead of failing. run.pause records the reason (e.g. credits_exhausted), message, pausedBy, whether it is resumable, the engine checkpoint for resume, and the required action (e.g. "Add credits, then resume"). - Paused runs keep their engine checkpoint, release their runner slot, and are never failed by liveness/stall/deadline reaping. - POST /api/runs/{id}/pause parks a run; POST /api/runs/{id}/resume (resume_run) re-queues the same run and continues from the recorded checkpoint when one exists — otherwise it re-runs from scratch and says so. Cancel still works from paused. budget_exceeded remains a distinct terminal hard stop and is never converted to a pause. - A checkpointed resume runs on the runner holding the checkpoint; the resume response warns when that runner is offline. Resume with body {"strategy":"rerun_from_scratch"} to discard the checkpoint and runner pin so any live runner takes the run. If a recorded checkpoint turns out to be missing on the runner, the run re-parks as paused (reason resume_failed) with the stale checkpoint dropped — never a silent hang or a bogus timeout failure. - GET /api/runs/attention (list_attention_runs) is the triage queue: every run whose next step is a human action — paused, waiting for a decision, or stopped at its budget in the last 7 days — with counts including pending decision cards. Work items (tickets and the kanban board): - A work item is the durable unit of company work — distinct from workflows (reusable recipes) and runs (single execution attempts). The web app's Work board shows them as kanban lanes. - Lifecycle statuses: intake, triaged, ready, running, waiting, blocked, review, shipped, accepted, archived. There is no failed ticket state: a failed linked run parks its ticket in blocked with an explicit reason; a succeeded run moves it to review unless the board opts into autonomous shipped success; a held run (waiting_approval/paused/budget_exceeded) parks it in waiting. - Boards are durable configured views over work items (lanes, project scope, default workflows, server auto triggers): GET/POST http://runyard.synclave.net/api/boards, GET/PATCH /api/boards/{slug}. MCP: list_boards, get_board, create_board, update_board. One board is the instance default. - Moving into trigger.mode=auto launches from the server for Web, CLI, API, and MCP. PATCH responses include laneTriggers metadata; launch failures park the ticket in waiting instead of rolling back the successful status mutation. - Preview Ready approvals with GET /api/factory-items/{id}/status-preview before PATCHing. Auto lanes return workflow, input, duplicate/live-run suppression, and deterministic preflight; PATCH with negotiate:true saves a non-ready auto-trigger preflight as a run draft. - HTTP lifecycle: GET/POST http://runyard.synclave.net/api/factory-items, per-item GET/PATCH/DELETE /api/factory-items/{id}, GET /api/factory-items/{id}/status-preview, and POST /api/factory-items/{id}/link-run | /unlink-run ({ runId }); link/unlink requires Idempotency-Key. - Equivalent MCP tools: list_factory_items, get_factory_item, preview_factory_item_status_move, create_factory_item, update_factory_item, claim_next_factory_item, renew_factory_item_claim, release_factory_item_claim, delete_factory_item, link_factory_item_run, unlink_factory_item_run. - Attach a run at creation time by passing workItemId to run_workflow / POST /api/workflows/{id}/run; runs linked to a ticket appear on its board card and detail with a status rollup. - GET /api/runs/{id}/flow (get_run_flow) returns the run's execution flow: the workflow's step graph with per-step states (pending/active/done/failed/waiting/cancelled/skipped) folded from the run's events — the ticket detail's flow view. Run-creation negotiation (preflight + drafts): - GET /api/workflows/{id}/launch-plan compiles workflow fields, repo selectors, eligible hooks, runner availability, budget support, approval policy, relevant Learning Ledger hints, exact payloads, and preflight into one read-only plan. Nothing is created; actual submission still preflights. - POST /api/workflows/{id}/preflight dry-runs the deterministic preflight (required input fields, runner tags, secrets, hooks, workflow source) and returns ready | needs_input | blocked with questions[], blockers[], warnings[], and suggestedDefaults — nothing is created or enqueued. - POST /api/workflows/{id}/run with negotiate: true enqueues only when preflight is ready; otherwise it returns 422 (needs_input) or 409 (blocked) with the negotiation state and a saved run draft instead of creating a run that would fail. - Drafts: POST /api/run-drafts creates one, PATCH /api/run-drafts/{id} merges answers into the input and re-preflights, and POST /api/run-drafts/{id}/submit enqueues the real run once ready and requires Idempotency-Key. Workflow package files: - Admins can export a workflow as a portable .runyard-workflow.json file, then validate/preview/import it on another Hub. Imports publish the source as a DB workflow bundle and install the workflow disabled until local secrets/runners/config are ready. - CLI: runyard workflow-package export -o file.runyard-workflow.json then runyard workflow-package preview|import file.runyard-workflow.json. Post-run hooks (optional): - Side effects after a run's gates pass (static publish, git push, webhook) are explicit hook invocations, never implicit workflow magic. - Admins define bounded hook profiles at POST /api/hooks; callers discover eligible profiles at GET /api/hooks?workflow= and select them per run via input.postRunHooks: [""]. - Hook outcomes surface as hook_failed / hook_config_required / hook_blocked alongside the run result; a failed hook never turns a green build into a failed run.