# 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:
Jobgoes in: answer a message, steer the run in progress, or compact.Commandgoes in too: cancel, approve or deny a tool call.Eventcomes out: what happened, as facts. A run started, text arrived, a tool finished. Events never ask for anything.Stateis those events folded into what a UI draws, by a pure reducer.Toolis how the built-in tools, or Lua, give the model a tool.
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.
- A key, a paste or a runtime event arrives. Keys go through the keymap of the current
mode; events run their
lug.onhooks. - 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). lug-uichecks 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.