Last updated 2026-05-07
Architecture
stax is three independent processes that cooperate over HTTP and a Server-Sent Events stream. None of them require the network beyond optional GitHub interactions.
Top-level layout
stax/├── platform/│ ├── cli-go/ Go CLI (active) — Cobra-based, single binary│ ├── cli/ TypeScript CLI (legacy) — same commands, ts-node│ ├── runner/ Node REST runner (Express + TypeScript)│ ├── web/ Next.js dashboard│ └── actions/│ └── video-capture/ Built-in Playwright action├── base/ General-dev container (no CUDA)├── inference/ LLM-serving container (CUDA 12.4 + vLLM)├── train/ Model-training container (CUDA 12.8)├── skills/ Claude Code skills (slash commands)├── docs/ Static landing site (HTML)├── docs-site/ This Astro documentation site├── connect.sh SSH helper for remote dev pods├── install.sh One-line installer for the CLI└── install.ps1 Windows installerRuntime topology
┌──────────────────────────────────────────────────────────────────┐│ Your laptop ││ ││ ┌──────────────┐ stdin/stdout ┌──────────────────────┐ ││ │ stax CLI │ ─────────────────│ git, gh, your editor │ ││ │ (Go binary) │ └──────────────────────┘ ││ └──────┬───────┘ ││ │ HTTP (POST /api/runs, etc.) ││ │ SSE (GET /api/events) ││ ▼ ││ ┌─────────────────────────────────────────┐ ││ │ stax runner (Node, :4800) │ ││ │ parser → expression → scheduler → │ ││ │ matrix → step-runner → action-resolver │ ││ │ │ ││ │ stores: artifacts/, secrets/, sandboxes/ │ ││ └─────────────────────────────────────────┘ ││ ▲ ││ │ HTTP + SSE (same endpoints) ││ │ ││ ┌──────┴────────┐ ││ │ Web dashboard │ ││ │ (Next.js, │ ││ │ :3000) │ ││ └───────────────┘ │└──────────────────────────────────────────────────────────────────┘The CLI
platform/cli-go/ — Go 1.26 + Cobra. The CLI does two
distinct jobs:
- Stacked-diff workflow:
clone,feature/fix/docs/chore,diff,sync,switch,submit,land,features,diffs,context,status,log,undo,split,config. State lives in.stax/per-repo. - Runner control:
runner,run,runs,workflows,secret,dashboard. These are HTTP clients that talk to the local runner.
The runner
platform/runner/ — TypeScript on Node 18+, with Express
for the REST surface and a hand-rolled SSE pipe.
platform/runner/src/├── index.ts Boots Express; wires backends; cleans stale sandboxes├── server.ts Routes for /api/runs, /api/workflows, /api/secrets, /api/events├── types.ts Shared TypeScript types├── engine/│ ├── executor.ts Lifecycle: trigger, sandbox, schedule, finalize│ ├── parser.ts YAML loader; workflow discovery; trigger normalization│ ├── scheduler.ts Topological-sort + matrix expansion + fail-fast + max-parallel│ ├── matrix.ts Cartesian product, include / exclude│ ├── expression.ts ${{ … }} parser (literals, ops, functions, contexts)│ ├── context.ts Builds ExpressionContext + GITHUB_* env vars│ ├── job-runner.ts Single job: services, env merge, step loop│ ├── step-runner.ts Single step: shell vs uses, GITHUB_OUTPUT/ENV/PATH parsing│ ├── reusable.ts Local + remote workflow_call resolution│ └── sandbox.ts worktree | copy-on-write | rsync-copy | none├── actions/│ ├── resolver.ts Marketplace cache + shim dispatch│ ├── loader.ts action.yml parsing│ ├── composite.ts Composite step recursion│ ├── node-runner.ts Node.js action lifecycle (pre/main/post)│ ├── docker-runner.ts Docker build/pull/run│ ├── commands.ts ::set-output, ::error, ::add-mask, …│ ├── shim-framework.ts Declarative shim → composite-YAML compiler│ └── shim-definitions.ts All language-setup shims├── artifacts/store.ts In-memory metadata + filesystem storage under .artifacts/└── secrets/ ├── store.ts Per-workspace secrets, optional AES-256-GCM ├── backend.ts SecretsBackend interface ├── r2-backend.ts Cloudflare R2 (encrypted JSON object) └── github-backend.ts gh CLI bridge → real repo secretsThe web dashboard
platform/web/ — Next.js App Router (this version has
breaking changes from public Next.js — see
platform/web/AGENTS.md). Pages live under
src/app/:
src/app/├── page.tsx Home / runs list├── prs/ Pull-request list + detail with Monaco diff viewer├── features/ Feature list + per-feature detail├── diffs/ Stacked-diff list and per-diff review├── runs/ Run list, run detail, live logs└── api/ Server-side proxies to the runner + git/gh helpers
The dashboard never talks to GitHub directly for run data — it
reads from the local runner. It does call the gh CLI for
PR metadata.
Built-in actions
stax ships purpose-built actions under
platform/actions/. They use the standard GitHub Actions
contract (action.yml, INPUT_* env vars,
$GITHUB_OUTPUT) so they run in the local runner and on
cloud GitHub Actions identically.
- video-capture — Playwright-driven walkthroughs for web, Android, and iOS.
The container images
Independent of the platform processes, base/,
inference/, and train/ are Dockerfiles that
publish to ghcr.io/elloloop/stax. They're meant for
remote development on RunPod (or any container host); see
Containerized Dev Pods.
Why these choices
- Separate runner from CLI. A long-running runner can cache action downloads, hold a sandbox open for inspection, and stream events to multiple consumers (CLI + dashboard) at once.
- Express + SSE. SSE is one-way and survives
proxies; the dashboard, the CLI
log -f, and any custom automation can subscribe. - Go for the CLI. Single static binary, fast startup,
cross-compiles cleanly to
linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64, andwindows/arm64. - Sandboxed execution. Concurrent runs each get their
own working tree — your editor's file isn't trashed when CI runs
npm run lint --fix.