Self-hosted workbench host for AI agents.
A single place per project where your AI coding agent can drop approval forms, share files both ways, render markdown for review, drive a persistent task list, and wait for you to respond — without bloating your chat context or forcing you to copy-paste.
Quick start · Why · Agent integration · Object kinds · Templates · Theming · API · Docker · License
📖 Live system overview — single-page tour of what Arctic Base is, the architecture, the 7 object kinds, and the agent surface. Best viewed via GitHub Pages or after cloning.
|
Top-level dashboard (KND chrome) |
Per-workbench dashboard (Forge · parchment + Charter serif theme · floating cards · burgundy accents) |
|
Management page (activity timeline · self-test · per-workbench actions) |
Mobile (topnav collapses to logo + buttons · single-column grid) |
More: docs/screenshots/ including a parchment-themed
workbench, the visual architecture docs, and the mobile management view.
Every workbench gets its own themed dashboard plus any number of HTML / MD
objects the agent uploads. The agent writes the HTML; Arctic Base hosts it
and re-themes it via --wb-* tokens. Two real examples from the demo data:
|
Forge · approval-questions form Spawned from the
|
Muzzy · custom fuzzing dashboard A self-authored HTML object (no template) — current target, run ledger, corpus plan, findings, guidance. Demonstrates that the same surface scales from "fill in a template" to "agent ships a bespoke single-page app." Hot pink + lemon CD-i theme.
|
LLM coding agents need out-of-band surfaces for:
Pasting forty-line approval questions into a chat thread bloats context, hides the canonical artifact, and makes audit trails impossible. Arctic Base gives the agent an HTTP API to publish those artifacts and a web UI for you to respond. Each project gets a themed workbench — its own dashboard, look, and metadata.
It's also self-hosted, single-user, no auth — designed for LAN / Tailscale, not the public internet.
Agent surface
/openapi.json, conventions doc at /api/agent/conventions, one-shot bootstrap, CLAUDE.md memory snippets, self-test, activity log.approval-questions, qa-form, pitch, runbook. Each works standalone (localStorage + JSON export) or wired through Arctic Base.window.workbench JS bridge — feature-detected; agent-authored HTML stays portable. Save / submit / load / onRemoteChange all wired.Workbenches
deploy_cmd, ci_url, secrets_path, etc. Agents see them in the manifest.Object lifecycle
kind: image object, link inserted at cursor.Operations & UI
@fontsource/*. No CDN at runtime; the SPA boots on a LAN with no internet.Requires Python 3.12+ with uv and Node 20+
with pnpm.
git clone https://github.com/GainSec/ArcticBase.git && cd arctic-base
make sync # install backend + frontend deps
make build # build the SPA
make backend-run # binds 0.0.0.0:2929 by default
Open http://localhost:2929. To develop the frontend with hot reload, run
make backend-dev and make frontend-dev in two terminals — the dev server
proxies API calls.
Hand any AI coding agent a single URL:
https://your-arctic-base/agent
That URL 302s to /openapi.json. From the conventions doc and the templates
endpoint, the agent figures out the rest:
agent → GET /agent (302 → /openapi.json)
agent → GET /api/agent/conventions (≈10 KB runbook)
agent → POST /api/agent/self-test (1-call sanity check)
agent → POST /api/agent/bootstrap {project_title} (creates workbench)
agent → GET /api/agent/templates (lists 4 templates)
agent → GET /api/agent/templates/approval-questions?fill_title=…
agent → edit QS array, upload as kind=approval-html
→ POST /api/workbenches/{slug}/objects (multipart)
agent → poll /responses?since_version=N every 2-5s
user → opens workbench in browser, fills form, submits
agent → reads back via /responses/latest/payload
The conventions doc covers what OpenAPI can't express: the
window.workbench runtime, polling cadence, theme contract, review-HTML
discipline, the templates-first authoring rule, runbook semantics,
pin/archive conventions, snapshots/audit/static-export usage.
Drop a CLAUDE.md / AGENTS.md fragment into your project so future sessions auto-discover the workbench:
curl -s "$URL/api/agent/claude-md-snippet?slug=$SLUG&format=claude" >> CLAUDE.md
| Kind | What it is |
|---|---|
md |
Markdown doc, rendered in browser with theme tokens (task-list checkboxes supported). |
html |
Self-contained HTML the agent authors. Theme + bridge auto-injected. |
approval-html |
Same as html but filed under "Approvals". The decision-doc pattern. |
qa-form |
Short ad-hoc Q&A form. Lighter than approval-html. |
runbook |
Persistent JSON task list (steps, status, blockers, timestamps). Agent reads + writes; user ticks/unticks. |
image |
Image to view in browser. Paste/drop into the editor uploads as this kind. |
file |
Generic binary / catch-all. |
Every kind supports pinned: bool (sticks to top of its drawer) and
archived: bool (hidden from default lists, recoverable). Default
list_objects excludes archived; pass ?include_archived=true for the
full inventory.
Four out-of-the-box, all self-contained:
| Template | Use for | API kind |
|---|---|---|
approval-questions |
Multi-decision review with options + recommendations + notes | approval-html |
qa-form |
Quick free-form Q&A (text / textarea / choice) | qa-form |
pitch |
Narrative + Approve / Defer / Reject footer | approval-html |
runbook |
Persistent JSON task list with status + blockers | runbook |
Templates reference --wb-* CSS variables so they auto-theme when served via
Arctic Base, and bake in fallback values so they still work opened standalone.
JSON export, localStorage cache, and window.workbench.submit() mirror to the
API are all wired. Don't hand-write from scratch — fetch a template, edit the
clearly-labelled QS array (or <section> block for pitch, steps array
for runbook), upload.
Each workbench has a theme.md with YAML frontmatter (machine-readable) +
Markdown prose body (brand voice / component metaphors). Required core
tokens validated server-side: bg, surface, ink, muted, rule,
accent, accent-2, success, error. Plus typography.body &
typography.mono, rounded.{sm,md,lg}, spacing.{unit,gutter,margin}. A
freeform custom: {} map allows per-workbench extras
(--wb-pink-pop, --wb-xenothium, etc.).
Optional layout: block drives composition, not just color:
layout:
density: compact | comfortable | spacious
corners: sharp | soft | rounded | full
shadow: hard-offset | soft | glow | none
card-style: bolted | flat | floating | inset
motif: bolts | hex | tape | none
banner-shape: rect | t-shape | circle | none
hero-style: cover | flat | crt-monitor | none
Same Svelte template, different feel: a "bolted-with-hard-offset-shadow-and-tape" workbench reads as brutalist arcade; a "floating-soft-rounded-with-hex-motif" reads as TCG codex. Both consume the same React-equivalent component.
┌──────────────────────────────────────────────┐
agent ─▶│ /api/* (FastAPI) │
│ │
│ /agent /api/agent/conventions │
│ /api/agent/bootstrap /api/agent/templates │
│ /api/agent/activity /api/agent/self-test │
│ │
│ /api/workbenches[/{slug}[/...]] │
│ /api/workbenches/{slug}/objects[/{id}] │
│ /api/workbenches/{slug}/objects/{id}/ │
│ content / render / responses │
│ /api/workbenches/{slug}/snapshot[s] │
│ /api/workbenches/{slug}/audit │
│ /api/workbenches/{slug}/static-export │
│ │
│ Storage → data/workbenches/{slug}/... │
│ (flat files; SQLite-swappable │
│ behind the Storage interface) │
└──────────────────────────────────────────────┘
▲
│ same origin
▼
┌──────────────────────────────────────────────┐
user ─▶│ / (Svelte SPA) │
│ │
│ Top-level: KND-themed dashboard, │
│ status console, topnav with │
│ logo + Blueprints + Settings │
│ Per-workbench: workbench-themed dashboard, │
│ themed iframes for served │
│ HTML, MD render, runbook, │
│ image view │
│ │
│ window.workbench bridge ↔ iframe postMessage│
└──────────────────────────────────────────────┘
uv run arctic-base. JSON logs to stdout; per-request X-Request-ID.
Storage is filesystem-only in v1, hidden behind a Storage Protocol so
SQLite or hybrid can drop in later./edit / /theme. No CSS
framework — just CSS custom properties + scoped styles.meta.json + raw content. Responses are append-only
versioned JSON files. Trash is a tar.gz per archived workbench. Snapshots
are labelled tar.gz under data/snapshots/{slug}/. Audit log is
data/workbenches/{slug}/audit.jsonl.Full machine-readable spec at /openapi.json. In-browser explorer at /docs.
# Workbenches
POST /api/workbenches create workbench
GET /api/workbenches list (last_accessed, then manual order)
GET /api/workbenches/{slug} read manifest + bump last_accessed
PATCH /api/workbenches/{slug} update mutable fields (incl custom_fields)
POST /api/workbenches/{slug}/duplicate clone (responses reset)
DELETE /api/workbenches/{slug} archive to trash
POST /api/workbenches/_reorder manual sort override
# Workbench operations
GET /api/workbenches/{slug}/banner PNG/SVG (auto-generated if none)
GET /api/workbenches/{slug}/export tar.gz download
GET /api/workbenches/{slug}/static-export self-contained zip
GET /api/workbenches/{slug}/audit NDJSON audit log
POST /api/workbenches/{slug}/snapshot labelled checkpoint
GET /api/workbenches/{slug}/snapshots list
GET /api/workbenches/{slug}/snapshots/{n}/download
POST /api/workbenches/{slug}/snapshots/{n}/restore body: {new_slug}
DELETE /api/workbenches/{slug}/snapshots/{n} permanently delete one
# Theme
GET /api/workbenches/{slug}/theme parsed tokens + prose
PUT /api/workbenches/{slug}/theme replace theme.md
PUT /api/workbenches/{slug}/theme/assets/{path} upload asset
# Objects
POST /api/workbenches/{slug}/objects create (JSON or multipart)
GET /api/workbenches/{slug}/objects[/{id}] list / read
PATCH /api/workbenches/{slug}/objects/{id} update meta (pinned, archived, etc.)
PUT /api/workbenches/{slug}/objects/{id}/content write (If-Match for stale-check)
GET /api/workbenches/{slug}/objects/{id}/render themed render (md / html / image / file / runbook)
DELETE /api/workbenches/{slug}/objects/{id} hard-delete
POST /api/workbenches/{slug}/objects/_reorder ids = [...]
# Responses
POST /api/workbenches/{slug}/objects/{id}/responses append event
GET /api/workbenches/{slug}/objects/{id}/responses?since_version=N
GET /api/workbenches/{slug}/objects/{id}/responses/latest/payload bare JSON
GET /api/workbenches/{slug}/objects/{id}/responses/export full history download
# Agent
GET /agent 302 → /openapi.json
GET /api/agent/conventions ≈10 KB runbook
GET /api/agent/templates[/{id}] list + fetch (?fill_title=)
POST /api/agent/bootstrap create + checklist
GET /api/agent/claude-md-snippet?slug=... paste-ready memory snippet
GET /api/agent/activity recent request log
POST /api/agent/self-test synthetic round-trip
All env-var driven; defaults shown.
| Variable | Default | Purpose |
|---|---|---|
ARCTIC_BASE_DATA |
./data |
Where workbench data lives |
ARCTIC_BASE_HOST |
0.0.0.0 |
Listen address |
ARCTIC_BASE_PORT |
2929 |
Listen port |
ARCTIC_BASE_MAX_UPLOAD_BYTES |
2147483648 (2 GB) |
Per-upload cap |
ARCTIC_BASE_FRONTEND_DIST |
../frontend/dist |
Built SPA path |
Copy .env.example to .env and edit if you want.
docker build -t arctic-base .
docker run -d --name arctic-base \
-p 2929:2929 \
-v $PWD/data:/data \
arctic-base
Multi-stage build: SPA built with Node, served from a Python runtime image.
State lives in the /data volume.
backend/ FastAPI app (Python 3.12+, uv-managed)
src/arctic_base/ package source
tests/ pytest test suite (68 tests)
frontend/ Vite + Svelte 5 + TypeScript SPA (pnpm)
src/lib/chrome/ Arctic Base shell components
src/lib/themed/ per-workbench themed components
data/ workbench data (gitignored)
docs/ design specs and implementation plans
make test
Runs pytest against the backend (68 tests; storage round-trip, API contract,
agent endpoints, templates, layout tokens, render flows, snapshots, audit log,
static export, self-test, runbook kind, pin/archive) and svelte-check
against the frontend (TypeScript + Svelte).
To be honest about scope:
?since_version=N); endpoints are
shaped to upgrade to long-poll / SSE without API churn./export, /snapshot, and the trash
tar.gz; or the data directory is just a directory you can tar and rsync.MIT.