lug

# Architecture

Lug is one Rust process per session. It runs the agent on Rig, draws it with ratatui, and hands everything else to Lua. This page is the map; each crate's README has the detail.

## Engine and distro

The binary provides mechanisms. The runtime plugin provides policy and UX. Lug is a distro, like NvChad: the binary is the engine, and the stock runtime, basic.lug, is a plugin in its own repo that haul installs.

Where What lives there
The binary Security boundaries, per-frame work, contracts, and mechanisms a plugin cannot build from lug.*
basic.lug What feels core but the API can express: checkpoints, plan mode, the todo panel, guard rules, notifications, the dashboard, themes
Other plugins Opinionated or niche features: PR review, tool packs around CLIs, workflows

When the binary has to change, it gains the smallest general mechanism a plugin could use, and the feature is built on it in Lua.

## The crates

lug             the command line, the terminal, the app loop
 ├─ lug-lua     the Lua bridge: the lug.* API, the UI's state, where input goes
 │   ├─ lug-ui       everything that draws
 │   ├─ lug-config   options, directories, project trust
 │   └─ lug-keys     key notation and lookup, the : line
 ├─ lug-headless     key scripts and golden frames
 ├─ lug-stream       durable streams: sessions and plugins' event streams
 ├─ lug-syntax       the code tokenizer
 └─ lug-core         the runtime over Rig

lug-core is the only crate that depends on Rig, and no Rig type leaves it. Upgrading Rig touches that crate and nothing else.

## The runtime

The rest of Lug reaches the agent through four things:

Job, Command ─► Runtime ─► rig-agent (agent loop, hooks) ─► rig-core (providers)
                └─► Event ─► State::apply ─► TUI / Lua

Every tool call is held until the host answers it. Approval rules, the mode and the dialog settle it, and each answer is written to approvals.log.

## A frame

Lua composes the screen; Rust renders retained UI resources.

  1. A key, a paste or a runtime event arrives. Keys go through the keymap of the current mode; events run their lug.on hooks.
  2. Before drawing, Lua returns the frame's render plan: components in rectangles, as plain values. The windows, splits, tabs and statusline all live in Lua (lug/screen.lua, lug.bsp).
  3. lug-ui checks the plan and draws each component, then what Rust always draws on top: the approval dialog and the completion popup.

Rust knows nothing of splits, panes, tabs or agents, and no ratatui type ever reaches Lua. The loop draws only when something changed, at most once every 16 ms.

## Lua

Every lug.* function is one of three kinds:

Kind Means
Read May be called while Lua composes the screen
Change Changes state; raises during composition, and the frame is drawn again after it
Wait Yields its coroutine until the answer comes: a process, a model call, a dialog

Bad arguments raise. What goes wrong at run time returns nil and a message. Hooks, rules and tool handlers each run as their own coroutine, and one that keeps failing is detached rather than taking Lug down.

require searches your lua/, then the plugins haul added, then the Lua built into the binary: the screen, lug.bsp, the dialogs, the : line and lug.bootstrap.

## Sessions and streams

A session is a durable stream: an append-only file of JSON lines under ~/.local/state/lug/streams/, one event per line, held by one lug at a time. Plugins keep streams of their own through lug.stream, and lug.agent.call saves its conversations the same way.

## Tools and processes

The built-in tools are read, write, edit, grep, ls and bash. Lua adds more with lug.tool.register; there is no MCP client. Every program Lug starts, from bash or lug.proc.run, goes through one path in lug-core, in a process group of its own, and that path is where the sandbox will go.

Tools run unsandboxed for now. Approvals are the guard, and plugins are trusted code. A project's .lug/init.lua loads only after you have approved its exact contents.

## Tests

Tests check behaviour at the edges: the runtime's events against Rig's mock model, the Lua API, and golden frames drawn headless from key scripts. None needs a network or an API key; --mock FILE plays a scripted model.