# agents.md The project is albedo. It is a coding agent daemon written in Gleam on the BEAM, with a detachable native CLI in Go (Charm: bubbletea, lipgloss, bubbles). The daemon owns everything: sessions (each one an actor over a durable, append-only SQLite transcript), the model/tool loop, the provider transport, the extensions that contribute tools and context, and a persistent Python kernel the model drives through cells. The CLI is a thin client: it finds or starts the daemon, then renders. The daemon also serves a local HTTP API that webhooks, the orchestrator view, and the test suites use. In a checkout the CLI starts the daemon with `gleam run` (entry: `src/albedo.gleam` -> `albedo/daemon/server`). An installed binary launched outside the checkout needs `ALBEDO_ROOT` pointed at the repo to start a daemon; `./cli/bin/albedo` finds the root on its own, and a packaged daemon can be swapped with `ALBEDO_DAEMON`. Secrets and caches live in `$ALBEDO_HOME` (default `~/.albedo`). ### Tooling rules The package manager is gleam. Please when you want to add a package, which is never unless told to, do `gleam add ...` (dev deps: `gleam add --dev ...`), please do not try to edit `manifest.toml` yourself. Gleam generates it. PLEASE USE `gleam check` to type check and `gleam format src test` to format Gleam. Python is formatted with `ruff format priv/python test`. `pre-commit run --all-files` formats the whole repository, and the commit hook re-formats staged Gleam and Python files. There is no separate linter: the compilers are the linters. - Gleam suites: `gleam test` - Go: `go -C cli vet ./...` and `go -C cli test ./...` - Rust (optional `view` renderer): `cargo test --release --locked --manifest-path native/render/Cargo.toml` - Everything that must pass before a commit: `./test.sh` Anything that opens a browser must set `ALBEDO_NO_BROWSER=1`; `test.sh` exports it for the whole gate. ### Test layout and the build lock `./test.sh` is the gate, in a fixed order: format check, cargo test + renderer install, `gleam test`, the Python harness suites, Go vet + unit tests, the Go e2e suite, then the Python e2e suite. The order is not stylistic. The daemon suites boot a real daemon with `gleam run`, and a nested gleam build waits on the build lock the outer `gleam test` holds, so they must never run inside `gleam test`. Anything that builds or runs gleam concurrently with `gleam test` deadlocks the same way. This is also why the e2e suites drive the prebuilt `cli/bin/albedo` binary instead of invoking gleam, and why the Go e2e suite runs with `-count=1` after a fresh build: it boots one hermetic daemon and must never reuse a cached run or a stale binary. Test placement (expanded rules in `.agents/skills/writing-tests/SKILL.md`): the default is an end-to-end scenario through the real daemon on the shared harness in `test/e2e/` — a scripted fake model provider, one shared daemon per run, isolation by session and workspace, never a daemon per test. Unit tests are the exception; each module docstring must say which bug it catches that E2E cannot. `test/harness/api_docs_test.py` is the invariant test to imitate: it builds the model's real Python namespace and fails whenever a public binding exists that the model is never told about. `test/manual/` is opt-in (needs a provider, a PTY, or benchmark artifacts) and is never part of the gate. ### `src/` — the daemon (Gleam) - **`src/albedo/daemon/`** — session actors and durable state: submission/run/turn plumbing (`session_*.gleam`), the append-only transcript and conversation store (SQLite via sqlight), the event bus, projections, the family (agents, mail, bus), folders, quota, usage, the reaper, the image store, and `server.gleam`, the HTTP entry point. - **`src/albedo/daemon/migrations/`** — existing SQLite upgrades, with ordered startup data steps in `daemon/migrations.gleam`; schema additions remain called from owning initialisers (see `robot-docs/migrations.md`). - **`src/albedo/harness/`** — the model-facing half: `loop.gleam` (the tool loop), `tool.gleam`, `extension.gleam` and `extensions.gleam` (plugin interfaces and composition), `compaction.gleam`, `runtime.gleam` (boots the built-ins), plus credentials, oauth, rotation, settings, and the usage feed. - **`src/albedo/harness/extensions/`** — one directory per extension. Enabled by default: `python`, `run`, `work`, `mail`, `agents`, `schedule`, `paperclips`, `files`, `memory`, `instructions`, `commands`, `skills`, `models`, `openai`, `codex`, `antigravity`, `alibaba`, `claude`, `rolling`, `snapcompact-memory`, `lcm-memory`, `remote`. Installed but off until enabled: `mcp`, `view`, `proxy`, `webhooks`, `warm`, `snapcompact`, `lcm`. The provider extensions are ordered before `models` on purpose (their own model lists win), and the memory/compaction pairs are ordered so archives cover past folds. - **`src/albedo/openai_api/`** — the provider transport every extension shares: request/turn types, SSE framing, stream reducers, replay after restarts, chat/responses encodings. A provider with a foreign wire format encodes an `openai_api.Exchange` and supplies only its own reducer (antigravity does this); HTTP, retries, limits, and callbacks stay shared. The extension record is the core abstraction everything composes through: ```gleam extension.Extension( name: "project-context", description: "workspace instructions for the model", requires: [], plugins: [extension.ContextPlugin(load: load_workspace_context)], initialise: fn(_) { Ok(Nil) }, ) ``` An extension bundles plugins: context (system instructions), tool (model tools + Python modules + host routes), command (one definition drives the CLI menu, user invocation, and the kernel's typed `commands` bindings), managed (stateful contributions that observe session events), models/model-provider (catalog, wire protocol, login), service (HTTP under `//`), compaction, and fold. Selection is per session through `/extensions`; at most one compaction strategy may be enabled, and changes require an idle session. ### `cli/` — the terminal (Go) - **`cli/cmd/albedo`** — entry point and local state - **`cli/internal/config`** — client config, capabilities, MCP config - **`cli/internal/daemon`** — daemon lifecycle (find or start, auth token) and the typed API client - **`cli/internal/tui`** — the bubbletea UI: chat, model picker, orchestrator view, page screens, forms - **`cli/test/e2e`** — the Go e2e suite against the real binary and a hermetic daemon ### `priv/` and `native/` - **`priv/python/`** — the actual Python kernel: `albedo_kernel.py` (cell execution, persistent namespace), `albedo_proc.py`/`albedo_shell.py` (run jobs), `albedo_api.py`, `albedo_trace.py`, and `albedo_plugins/`. The daemon boots this per session; changes apply to new kernels. - **`priv/linguist/`** — GitHub's language data for file detection (`harness/languages.gleam`) - **`priv/cache-ttl.json`** — the prompt-cache TTL prior table (see `robot-docs/cache-ttl.md`) - **`native/render/`** — optional Rust renderer behind the `view` extension, which shows the model its changes as highlighted images for the final review pass. Install with `native/render/install.sh`; needs cargo. ### robot-docs/ `robot-docs/` is one model-facing doc per subsystem (agents, auth, migrations, cache-ttl, cache-warming, commands, compaction, context, extensions, files, mcp, models, paperclips, provider-requests, proxy, quota, skills, usage-feed, webhooks, workspaces). This file is the map; those are the details. When you change a subsystem's behavior, update its doc in the same change; when you need to understand one, read it there first. ### Other directories - **`test/`** — `e2e/` (default suite, shared harness), `harness/` (invariant and logic unit tests, Gleam and Python), `daemon/` (Gleam daemon suites), `openai_api/` (transport), `manual/` (opt-in) - **`flake/`**, **`default.nix`** — nix packaging and dev shell (`nix develop` ships gleam, go, cargo, python, and pre-commit); `nix build .#albedo` produces a self-contained package - **`.agents/skills/`** — repo-local agent skills (`writing-tests`, `charm-tui`, `gleam-design`, `tg`, `orchestrating-agents`)