Extracted from the homelab monorepo (git subtree split of services/ai-agent, full history preserved). Ticks the 'publish as a standalone repo' box in GOAL.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
6.6 KiB
Markdown
121 lines
6.6 KiB
Markdown
# ai-agent
|
|
|
|
**A self-hosted agent layer for your computer** — spawn and watch coding-agent
|
|
sessions from an installable PWA, browse every conversation with real per-turn
|
|
token costs, and manage the whole Claude context (CLAUDE.md files, skills,
|
|
hooks, memories, projects) that shapes what the agent does.
|
|
|
|
Think *Lovable, but open source and running on your own hardware*: the agent
|
|
builds things, the app shows you everything it touches — conversations, files,
|
|
projects, costs, deploys — and nothing leaves your machine beyond the model API
|
|
calls you configure.
|
|
|
|
> Extracted from my [homelab monorepo](https://git.gabvdl.xyz/gabrielvidal/homelab)
|
|
> after ~280 commits; full history preserved. It runs my homelab's AI operations
|
|
> daily at `ai-agent.lab.gabvdl.xyz`.
|
|
|
|
## What it does
|
|
|
|
- **Spawn / resume / interrupt agent sessions** from the composer — pick a
|
|
model (fetched live from the Anthropic API), an effort level, attach files —
|
|
and watch the run stream into a live thread over SSE within seconds.
|
|
- **Conversation archive** — every Claude Code transcript parsed into a rich
|
|
thread view: per-turn token usage and dollar cost, tool cards with duration
|
|
and diff stats, thinking blocks, task panels — each element with its own
|
|
visibility switch.
|
|
- **Claude-context editor** — every `CLAUDE.md` and the `.claude/` tree
|
|
(skills, hooks, agents, settings) as an editable file list with *real* token
|
|
counts and per-model costs, recomputed only when content changes.
|
|
- **Catalogs** — projects gallery (git history, goals, costs per project),
|
|
services catalog (static compose + Traefik parsing), assistant memories,
|
|
scaffolding templates, plans with estimated-vs-actual metrics.
|
|
- **Cron agents** — scheduled sessions defined by a cron expression plus a
|
|
prompt file; harness/model/effort declared in the prompt's frontmatter.
|
|
- **Notification hub** — the source of truth for the agent's "done"/"ask"
|
|
push notifications (Home Assistant integration included), with tappable
|
|
answers POSTed back to the blocked session.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────── Docker container ───────────────────────────┐
|
|
│ FastAPI backend (:8080) ──serves── React PWA (Vite, installable) │
|
|
│ • transcript parser (resumable, O(new bytes) per live tick) │
|
|
│ • SQLite store: token counts, costs, skill analytics │
|
|
│ • SSE hub: one event bus invalidates the UI │
|
|
│ • cron scheduler, notification hub, trusted-caller gate │
|
|
└──────────────┬─────────────────────────────────────────────────────────┘
|
|
│ POST /spawn|/resume|/interrupt (Bearer token)
|
|
┌──────▼───────────────┐
|
|
│ sidecar (runner) │ launches `claude -p … --session-id …`
|
|
│ host process — or │ transcripts land in watched dirs and
|
|
│ inside the container │ stream back into the viewer
|
|
└──────────────────────┘
|
|
```
|
|
|
|
The same `sidecar/sidecar.py` runs in one of two places and the backend can't
|
|
tell the difference: **on the host** (a session then has the host user's own
|
|
auth, hooks and skills — exactly a terminal session) or **inside the container**
|
|
(`RUNNER_IN_CONTAINER=1`, the standalone default — the image bundles the Claude
|
|
Code CLI and spawns sessions with no host process at all).
|
|
|
|
Notable engineering, hard-won on a real archive (~900 transcripts / 458 MB):
|
|
|
|
- **Resumable transcript parsing** — a live session appends every couple of
|
|
seconds; a `ParserState` per growing file is fed only the new bytes instead
|
|
of re-parsing from byte 0 (O(n²) over a session, before).
|
|
- **One git walk, not one per item** — both catalogs share a HEAD-keyed
|
|
`git log --name-only` bucketed per directory. `/api/services` went from two
|
|
`git log` forks per service per request (11 s) to <1 s.
|
|
- **Metadata-only list endpoints** + per-file content loads + one-row SSE
|
|
patches: the conversation list went 1.3 MB → 89 KB, `/api/bundle`
|
|
12.5 s → 26 ms, idle CPU ~58% → ~7% while a session streams.
|
|
- **Blue-green deploys** — `deploy.sh` health-gates a standby container on
|
|
`/api/health`, cuts the reverse proxy over, recreates, restores: zero
|
|
downtime, and a bad build never touches the live container.
|
|
- **Trusted-caller security gate** — the API answers only the reverse proxy,
|
|
the host, or a shared token; read-only API keys allow safe methods only
|
|
(a desk phone reads notifications aloud but can never spawn a session);
|
|
uploaded bytes are served `nosniff` + `attachment` so user content never
|
|
executes on the app origin.
|
|
- **Schema-drift canary** — the Claude Code transcript format is internal and
|
|
undocumented; `scripts/schema-drift.py` diffs a structural footprint of
|
|
fresh transcripts against a committed baseline and cross-checks the real
|
|
parser against raw record counts.
|
|
- **In-browser mock backend** — `?mock=1` swaps `fetch` + `EventSource` for a
|
|
deterministic, combinatorial in-memory seed (every UI state reachable from a
|
|
cold load), so the PWA demos and tests with no backend at all.
|
|
|
|
## Stack
|
|
|
|
FastAPI · Python 3.12 · SQLite (WAL) · React 18 · TypeScript · Vite · Tailwind ·
|
|
TanStack Query (IndexedDB-persisted) · Zustand (server-persisted) · SSE ·
|
|
orval (API types generated from the OpenAPI spec) · Docker · Traefik.
|
|
|
|
## Run it
|
|
|
|
Standalone (no host mounts — catalogs degrade gracefully when a mount is
|
|
absent):
|
|
|
|
```bash
|
|
docker compose -f docker-compose.standalone.yml up -d
|
|
# → http://localhost:8080 (set ANTHROPIC_API_KEY, or mount a logged-in
|
|
# Claude home at the runner's $HOME, to spawn sessions)
|
|
```
|
|
|
|
`scripts/standalone-smoke.sh` boots the image against a throwaway workspace
|
|
and asserts every catalog endpoint, the PWA shell and the bundled runner
|
|
answer correctly.
|
|
|
|
In my homelab it runs behind Traefik + Authelia with the host-side sidecar;
|
|
that deployment config (compose mounts, Traefik router, blue-green `deploy.sh`)
|
|
lives in the [homelab repo](https://git.gabvdl.xyz/gabrielvidal/homelab)'s
|
|
`services/ai-agent/` shim. The copies of those files in this repo's history are
|
|
kept as reference.
|
|
|
|
## Where it's going
|
|
|
|
See [GOAL.md](GOAL.md) — the north star is the open-source, intuitive, private
|
|
and secure agent layer of any computer: one container, zipgo-backed hosting,
|
|
in-container self-update.
|