No description
  • Go 75.3%
  • TypeScript 21.9%
  • CSS 1.4%
  • Nix 1.1%
  • Shell 0.1%
  • Other 0.1%
Find a file
dpolakovics ad1bb79cc3
Merge pull request #4 from Cloonar/fix/verify-scratch-cleanup
fix(ci): remove the verify scratch store via podman unshare
2026-08-05 12:24:51 +02:00
.forgejo/workflows
.github/workflows
assets
cmd
containers/agent-tools
docs
internal
migrations
nix
web
.containerignore
.editorconfig
.gitignore
.golangci.yml
CLAUDE.md
CONTEXT.md
CONTRIBUTING.md
flake.lock
flake.nix
go.mod
go.sum
LICENSE
Makefile
README.md

lab

lab is a self-hosted server with a phone-first web interface for running coding agents against your git repositories — interactively or unattended.

You add repositories and credentials in the UI, then start manual instances (interactive agent sessions you drive from lab's built-in chat, from any device) or AFK runs (unattended sessions that pick up one ready-for-agent issue, resolve it, and open a pull request). Repositories without a usable forge tracker get a built-in issue tracker with lab-internal change requests — reviewable and mergeable from your phone.

Features

  • Manual instances — interactive agent sessions, each in its own git worktree on its own branch. Drive them through lab's embedded chat (messages, tool activity, answerable dialogs, interrupt), or hop into the provider's own web surface via a captured deep link (claude.ai for Claude Code).
  • AFK runs — unattended sessions that claim one ready-for-agent issue from the repo's tracker, resolve it, and open a PR. Budget clock, three-strikes pause, guarded teardown, restart-safe re-adoption.
  • Multiple agent providers — Claude Code and Codex ship today, behind an AgentProvider seam designed so a new provider is one adapter, zero refactor (see docs/agents/provider-authoring.md).
  • Tracker integration — Forgejo and GitHub forges, or lab's built-in tracker (issues, labels, comments, change requests with diff view and merge from the UI). Agents talk to whichever tracker the repo binds through one CLI: labctl.
  • Container or host runner — per repo, sessions run either in a rootless podman container (your dev image + lab's injected agent tools) or directly on the host.
  • Credentials vault — SSH keys, HTTPS tokens, and forge API tokens encrypted at rest (AES-256-GCM, operator-owned master key file); secrets are never displayed back and never land in URLs or repo config.
  • Incogni mode — per-repo leak-proofing for agent-authored content: neutral branch names, attribution off, server-side body sanitization, and a pre-push guard.
  • Operable by default — single static Go binary with the SPA embedded, SQLite (default) or PostgreSQL, live updates over SSE, PWA with Web Push, /healthz /readyz /metrics.
  • Nix-first packaging — a flake with packages.{lab,labctl}, a full NixOS module, and a devshell; but the binary runs on any Linux.

How it works

┌─ SolidJS SPA (embedded static) ── PWA, SSE client
│
├─ HTTP server (net/http)
│   ├─ /api/v1/*      operator API (session cookie / PAT)
│   ├─ /agent/v1/*    agent API (scoped run tokens; labctl talks here)
│   ├─ /api/v1/events SSE stream
│   └─ /healthz /readyz /metrics
│
├─ Core services
│   ├─ RepoService        add(clone)/settings/remove; bare reference clones
│   ├─ CredentialVault    AES-256-GCM at rest; materialize-for-op; master key file
│   ├─ InstanceService    start/stop manual instances; deep-link capture
│   ├─ AFKEngine          scheduler + reaper + claims
│   ├─ GitEngine          fetch/worktree/branch/teardown/sweep
│   ├─ TrackerRegistry    Tracker interface → forgejo | github | builtin
│   ├─ ProviderRegistry   AgentProvider interface → claude-code | codex
│   ├─ SessionRunner      tmux wrapper + prlimit cap / rootless podman
│   ├─ Seeder             trust, settings, skills bundle, CLAUDE.local.md, incogni
│   └─ Store              SQLite/Postgres repositories + goose migrations
│
└─ External: tmux · git · ssh · agent CLIs · forge REST APIs

Source-of-truth layering (load-bearing): tmux answers "is the session alive"; git refs answer "what is claimed / what work exists"; the tracker answers "is there a PR / is the issue open"; the DB stores configuration, credentials, the built-in tracker, and run history. Reconciliation (startup + throttled sweep) re-derives live state; the DB is never the only witness to something the world can contradict. A service restart or deploy never kills a running session — lab re-adopts surviving sessions on startup.

The unattended loop end to end: you label an issue ready-for-agent → the AFK scheduler claims it by creating a branch → the session gets a seeded prompt, the repo worktree, and labctl with a run token scoped to that one repo → the agent resolves the issue and opens a PR (or a lab-internal change request) referencing Closes #N → the reaper sees the PR as the done-signal and tears the run down under the guarded rule (dirty or unmerged work is parked, never destroyed) → you review and merge from your phone.

Getting started

docs/getting-started.md walks the whole first session: install → login → credentials → first repo → first interactive instance → first unattended run. The short version:

Requirements

Any Linux host. On PATH: git, tmux, ssh (openssh), prlimit (util-linux). Provider CLIs (claude, codex) are only needed for host-runner repos — with the container runner (the default on the NixOS module) they come from lab's published agent-tools images instead.

{
  inputs.coding-lab.url = "git+https://git.cloonar.com/Cloonar/coding-lab";

  # in the host config:
  imports = [ coding-lab.nixosModules.lab ];
  services.lab = {
    enable = true;
    baseUrl = "https://lab.example.com";   # set when behind TLS
  };
}

The module makes the host container-ready out of the box and asserts the load-bearing unit invariants (sessions survive service restarts). All options, secrets wiring (sops / LoadCredential), and reverse-proxy setup: docs/ops.md.

Bare metal

git clone https://git.cloonar.com/Cloonar/coding-lab && cd coding-lab
make lab labctl          # static binaries → bin/lab, bin/labctl  (needs Go 1.26 + Node)
./bin/lab                # listens on :8080, state in ~/.local/state/lab, sqlite

Migrations apply on startup and the vault master key is auto-generated on first start; open the web UI and the first-run wizard creates the operator account. A systemd unit template and the full flag/env configuration reference are in docs/ops.md.

Developing

nix develop        # go, gopls, golangci-lint, node, git, tmux, util-linux, sqlite
make lab           # build SPA + server binary with embedded UI → bin/lab
make labctl        # agent-side CLI → bin/labctl
make test          # go test ./... (real git/tmux/prlimit integration tests)
make lint          # golangci-lint run

Web dev loop (no embedding, live reload):

go run ./cmd/lab        # API on :8080
cd web && npm run dev   # Vite dev server; proxies /api and /healthz → :8080

nix flake check is the CI truth: package builds (which carry the Go test suite against real git/tmux/prlimit and the SPA vitest suite), golangci-lint, and an eval-proven NixOS module with its unit invariants asserted. See CONTRIBUTING.md.

Surfaces at a glance

Operator API (/api/v1, session cookie or lab_pat_… bearer token, CSRF-guarded for ambient auth): first-run setup + login, PAT CRUD, credentials CRUD (no secret readback; delete 409s while referenced), repos (add → async bare clone with SSE progress, settings PATCH, guarded delete, clone retry), instances (start/stop/stop-all), Parked list + Discard, AFK (start / auto toggle / three-strikes reset), built-in issues + comments + labels + ready queue, change requests (list, detail with live diff, merge, close), run history, provider catalog + provider auth (status / login start / login code), runtime settings, and GET /api/v1/events (SSE: repo.changed, run.changed, parked.changed, clone.progress, claude.auth.changed, issue.changed, cr.changed, heartbeat).

Agent API (/agent/v1, run-token auth, scoped to the run's repo): issue view (the run's claimed issue) / list / create / comment / close, label add/remove/list, idempotent label create, PR create — routes everything to the repo's tracker binding (forge or built-in), injects/validates Closes #N on PR create, and applies incogni sanitization server-side to every agent-authored body.

labctl (on every session's PATH; reads LAB_URL/LAB_TOKEN from the session env):

labctl issue view [n]                 show the run's claimed issue (or issue n), with comments
labctl issue list                     list open issues (number, state, created, labels, title)
labctl issue create --title T --body B [--labels a,b]
                                      file a new issue, labels attached at creation
labctl issue comment <n> <body>       comment on issue n
labctl issue label add <n> <a,b>      add labels (comma-separated) to issue n
labctl issue label remove <n> <a,b>   remove labels from issue n
labctl issue close <n>                close issue n (comment the reason first)
labctl label list                     list the repo's labels (name, color, description)
labctl label create --name N [--color C --description D]
                                      create the label if missing (idempotent)
labctl pr create --title T --body B   open a PR/CR for the current branch

Probes: /healthz (liveness, always 200), /readyz (503 while the DB is unreachable), /metrics (Prometheus). All three are mounted outside auth.

Layout

cmd/lab, cmd/labctl    binaries (module git.cloonar.com/Cloonar/coding-lab)
internal/              config, store, vault, gitx, tmuxx, startguard, provider,
                       tracker, afk, instance, reconcile, seeder, events,
                       httpapi, agentapi, metrics, webui, labctl, compat, …
web/                   SolidJS SPA (Vite, TypeScript, vitest)
migrations/            goose migrations, sqlite + postgres (parity-tested)
assets/skills/         vendored skills bundle, embedded and seeded per worktree
containers/            agent-tools OCI images (provider CLI + labctl injection)
nix/, flake.nix        packages, NixOS module, devshell, checks
docs/                  getting started, ops, ADRs, agent docs, v0 reference

Documentation

  • docs/getting-started.md — first-session walkthrough: install, first repo, first instance, first AFK run.
  • docs/ops.md — deployment (NixOS + bare metal), configuration reference, state-dir layout, backup/restore, container runner, CI runner prerequisites, observability.
  • docs/model-selection.md — recommended model/effort settings per stage of the agent workflow.
  • CONTEXT.md — the domain glossary; identifiers and UI copy use these terms verbatim.
  • docs/adr/ — every significant design decision as an ADR, from repo/language choice through the container runner and read-only imports.
  • docs/agents/ — docs consumed by coding agents working on this repo (issue-tracker conventions, triage labels, provider authoring).
  • docs/definition-of-done.md — the production checklist with per-item verification: what is automated, what needs a real host.
  • docs/agent-brief.md — the original product contract this codebase was built from (historical, see below).
  • docs/reference/lab-v0/ — the v0 prototype, read-only: the behavioral spec for the session/worktree/AFK core.

History

lab is itself a product of the workflow it hosts: this codebase is the production rewrite of a working prototype (vendored read-only at docs/reference/lab-v0/), implemented end to end by a coding agent from a settled product brief (docs/agent-brief.md), milestone by milestone, with the decisions recorded as ADRs along the way. The brief and the v0 reference are kept as historical documents — docs/ops.md and the ADRs describe the system as it is today.

Contributing

Issues and pull requests live on github.com/Cloonar/coding-lab. See CONTRIBUTING.md for the dev environment, test suites, CI gates, and the project's conventions (Conventional Commits, the CONTEXT.md vocabulary, ADRs).

License

GNU Affero General Public License v3.0 (AGPL-3.0). You are free to use, modify, and redistribute lab — including commercially — but if you distribute it or offer a modified version over a network, you must make your source changes available under the same license. The vendored agent-skills bundle under assets/skills/ includes material from mattpocock/skills, MIT-licensed (GPL-compatible) — its notice is kept at assets/skills/LICENSE.upstream.