# yog
yog is the desktop manager for
[lernie](https://github.com/mudbungie/lernie) loops: an egui/eframe desktop
window that browses every lernie workspace, surfaces what needs attention, and
drives the loop lifecycle — start work from nothing, a path, or a
ball, message or stop agents, assign and close balls — over the `lernie`
and `bl` command-line tools.
It is its own package ([mudbungie/yog](https://github.com/mudbungie/yog)),
deliberately outside the lernie and balls workspaces: both ship as composable
components and yog composes on top of them. It takes **no Cargo dependency** on
either today — it spawns the `lernie` and `bl` binaries and reads the
workspaces' git repos directly. The intended direction is a fuller GUI with
direct library dependencies on both; lernie and balls take neither back.
## Architecture
**`docs/DESIGN.md` is the authority** — the state inventory, the attention
model, the write paths, the module map, and the line budgets live there, and it
is kept in sync with the code. This section is only the map.
yog is a stateless renderer: every frame is a pure function of on-disk state at
that tick, so two instances against the same repos converge with no
coordination. The only durable, yog-owned state is `ui.json` (pins, collapse
overrides, attention watermarks — DESIGN §4.1) and the `ops.jsonl` action log
(§4.2); everything else is derived from disk. The code is split the house way:
**pure view-model modules with no egui** — the per-tick `git_tree`, the `nav`
roster, `attention`, `projects`/`binding`, `start`, `ui_state`, the inspector
VMs (`transcript`, `steps_view`, `jsonview`, `inboxview`, `budgets`) composed by
the tested `inspector` tab dispatch, and `config_edit` — fronted by **thin egui
glue in `shell`** (coverage-excluded), with the keyboard bindings a pure
`keymap` table.
Visually, the window wears the **congeries palette** (`src/theme`, DESIGN §11):
Yog-Sothoth is *a congeries of iridescent globes*, and the UI is exactly that —
lore-named sphere-hues against a violet-black void (hydra green for liveness,
spectral blue for in-flight, brazen bronze for pending/warn, ichor for errors,
gate violet for yog itself), with every colour authored once in the theme
module and each driven tool keyed to its own hue.
The window is three information altitudes (DESIGN §11): the attention strip and
roster (does anything need me? what's running?), the focused workspace (its
agent descent tree with per-agent state badges, the four `refs/lernie/*` marks
— conflicted, budget-exhausted, abandoned, notify — budgets, streaming tails,
and tool pulses), and a per-agent tabbed inspector. Two composite flows sit on
top: the **start flow** (DESIGN §3.4) — prompts land as new roots in the
focused workspace (a fresh world bootstraps one automatically; creating more
is a deliberate act, walling off spheres like clients or corporate vs.
personal) and may carry a path or a ball payload (`bl create` + `bl claim
--as <workspace-name>`), through an editable goal composer whose
Send fires `lernie prompt` detached — and the **config editors** stage-and-validate
brazen `config.toml`, lernie global config, and per-workspace config branches
(DESIGN §9). Replays (`<lernie-data>/replays/*`) render through the same view,
read-only.
## Running
```
yog # browse every workspace, attention-sorted
yog --workspace /path/to/workspace # start focused on one workspace
```
yog enumerates every lernie workspace (named workspaces under
`$XDG_DATA_HOME/yog/workspaces/`, foreign workspaces and replays under the
lernie data root) and opens on the first one that needs attention. Balls bind
to workspaces by claimant: every claim a workspace makes is stamped `--as` its
minted name, so assignment is late-mutable metadata, not a location (DESIGN
§3.2). The optional
`--workspace <path>` flag overrides that startup focus. There is no `--repo`
flag — the whole roster is the view.
From a source checkout, `make run` does the same via `cargo run`; pass
`WS=<path>` to preset the initial focus.
egui is winit-based; its only runtime deps are the X11/Wayland libs
already present on any Linux desktop. No `apt install` step is required.
The window drives the `lernie` and `bl` binaries over `PATH` (or `LERNIE_BINARY`
/ `BL_BINARY`); install lernie from its repo (`make install` there).
## The world
yog composes its own **nested world** — a substrate environment under
`$XDG_DATA_HOME/yog/world` that overrides `LERNIE_HOME` and `XDG_STATE_HOME`
and hands it to every child it spawns, so yog's `bl`/`lernie` state never
collides with your ambient tools (brazen's config, credentials, and model
cache all stay shared — one host `bz`, one config, its secrets and its
regenerable cache). The world is the authority in **DESIGN §16**; one `rm -rf
$XDG_DATA_HOME/yog` erases all of it and leaves your ambient substrates
untouched.
Two escape hatches (DESIGN §8.4) let a human — or a foreign frontend — join that
world from a shell with one prefix:
```
eval "$(yog env)" # drop THIS shell into the world; ambient bl/lernie/bz
# now operate on yog's nested state
yog exec bl list # run one command inside the world (its exit is yog's)
yog exec --cwd /path bl close bl-1234 --as me
```
`yog env` prints the world's shell-quoted `export` lines; `yog exec [--cwd DIR]
<cmd…>` layers the world env over your inherited environment, inherits stdio,
and propagates the child's exit code (a terminating signal maps to `128 +
signum`). Both work headless — no display required.
By default the nested balls clone tracks the project's shared `balls/tasks` store
branch, so a ball yog claims is visible to your ambient `bl list` and vice versa.
A per-project **no-marks knob** (stealth `bl conf task-remote none`, and/or a
custom task-branch) trades that coordination for invisibility — driven through
bl's own config surface, never a yog config file (DESIGN §16.3).
## Building and contributing
| `make build` / `make release` | Debug / release build |
| `make test` | Run the test suite (parallel; see the Makefile note on spawn discipline) |
| `make coverage` | Enforce the 100% coverage floor via pinned tarpaulin (see `tarpaulin.toml`) |
| `make lint` / `make fmt` | Clippy `-D warnings` + ast-grep rules audit + `cargo deny check` / rustfmt |
| `make rules-audit` | Run the pinned ast-grep rules (`rules/`) over `src` and verify the `rules/fixtures` negatives still fire |
| `make check` | fmt-check + lint + coverage — the complete gate CI and the pre-commit hook mirror |
| `make run` [`WS=<path>`] | Launch the window (optionally focused on one workspace) |
| `make install` [`INSTALL_PREFIX=<p>`] | Release-build and drop `yog` into `$INSTALL_PREFIX/bin` (default `~/.local/bin`) |
| `make install-hooks` | Point `core.hooksPath` at `.githooks` — do this once per clone |
Code style is governed by the Rust Bootstrap v3 standard — the flat-numbered,
yog-adapted rules in [`AGENTS.md`](AGENTS.md), machine-enforced by `rules/*.yml`
(pinned ast-grep), the clippy manifest, and `cargo-deny`; read it before writing
code.
Task tracking uses [balls](https://github.com/mudbungie/balls) (`bl`);
never commit directly on `main` — all changes land via a claimed worktree
and are delivered by `bl close`. The pre-commit hook enforces this, a
300-line ceiling on source files, and the 100% coverage floor.
## Delivery
Delivery to the upstream ([mudbungie/yog](https://github.com/mudbungie/yog))
is automatic. `bl close` squash-merges the worktree onto `main`, but it seals
that commit itself — outside `git commit` — so git's `post-commit` hook never
fires on a close. Delivery instead rides balls' own extension seam: the
`scripts/bl-push-main` plugin, wired into `close.post`, pushes `main` to
`origin` right after the close lands it. A push failure only warns — it never
fails the close, so a landed change is never lost to a transient network error;
re-push manually with `git push origin main`. Balls task state travels its own
path, pushed by the `bl-tracker` plugin; the two are independent.
Wiring the plugin into a checkout is checkout-local, not a repo edit: symlink
`scripts/bl-push-main` into the landing's `config/plugins/bin/`, then
`bl conf append close.post bl-push-main`. The `.githooks/post-commit` hook is
retained for the rare legitimate manual commit made directly on `main`; run
`make install-hooks` once per clone to arm the git hooks.
### Local install (CICD)
The *local* half of delivery is a sibling `close.post` plugin,
`scripts/bl-install-main`: when a close lands on `main` it recompiles that tip
and `make install`s `yog` into `$PATH`. It builds from an **ephemeral
`git worktree --detach main`**, never the root checkout — a plumbing delivery
leaves the root working tree stale and possibly holding uncommitted work, so the
`main` ref is the only trustworthy source of "what landed". The build shares the
repo's `target/` (`CARGO_TARGET_DIR`) to stay incremental, runs **detached**
(`setsid`) so `bl close` returns at once, and logs to `target/cicd-install.log`.
Like `bl-push-main` it **always exits 0** — a build failure only lands in the
log, never rolls back the close. Wire it the same way: symlink
`scripts/bl-install-main` into the landing's `config/plugins/bin/`, then
`bl conf append close.post bl-install-main`.
On every push to `main`, GitHub Actions (`.github/workflows/ci.yml`) runs
`make ci` on Linux (fmt-check, clippy `-D warnings`, tarpaulin 100% floor)
and `cargo build` + `make test` on macOS (Apple silicon).
## Publishing
The crate is published to [crates.io](https://crates.io/crates/yog) with
`make publish`. That target runs `cargo publish --dry-run` unconditionally;
the real upload runs only when you opt in explicitly with
`make publish CONFIRM=yes`. Publishing a release is a deliberate human
decision, never a side effect of CI or delivery.