A terminal-native coding agent you can inspect.
Any [OI]-compatible endpoint · durable plans · an independent completion supervisor · an append-only journal you can replay
Loom is a coding agent for your terminal. Point it at any endpoint that speaks the Chat Completions or Responses protocol, hand it a goal, and it reads, edits, runs and verifies. Every state change — messages, tool calls, file hashes, verification results — is written to an append-only journal, and the transcript is only a projection of it. That is the whole design premise: an agent you can replay, rewind and audit instead of one you have to trust.
The executable is loom. New installations use loom configuration paths; existing
loon configurations are reused for compatibility.
| Replayable by construction | The journal is authoritative. Sessions resume to the exact context they held, and a rewrite of history is visible rather than silent. |
| Your endpoint, not a vendor list | Chat Completions and Responses are both first-class. Arbitrary base URLs work, including local models and gateways with no discovery and no tool calling. |
| Supervision that can say no | An independent reviewer reads the goal, plan and evidence. It cannot accept completion while plan items are open or required verification fails. |
| Edits with a paper trail | Hash-protected writes reject stale edits, patches are atomic, and mutations keep undo metadata. Conflicts are reported, never clobbered. |
| Persistent processes | A named shell keeps its working directory and exports. Monitors stream long builds. SSH multiplexes one authenticated session. |
| Honest numbers | Token counts are labelled estimates until the provider reports real usage. No invented progress percentages. |
Rust 1.75 or newer. No credentials are needed for the first run — the bundled provider is a deterministic mock.
cargo build --release
./target/release/loom
# Install the command locally:
cargo install --path crates/loon-cli --force
# Offline smoke test (the default provider is a deterministic mock):
loom run "Say hello" --output textThen connect a real endpoint from inside the TUI:
/providers add local http://127.0.0.1:11434/v1
/model local/my-model
Providers are usable immediately after saving; restarting is unnecessary. An endpoint
without model discovery can still be used as /model PROVIDER/MODEL_ID.
Every image below is rendered from the real Ratatui widgets with sample data — not a
mockup. Regenerate them with cargo run -p loon-tui --example preview -- docs/previews.
| Command | Behavior |
|---|---|
/help |
Every command, grouped by what you are trying to do (sessions, context, files and undo, long-running work …) |
/tools |
Browse tools, their effects and parameter schemas |
/model |
Home with five recent selections, provider entries, combos and global search |
/model PROVIDER |
Search models inside one provider |
/model all |
Browse every discovered model |
/model PROVIDER/MODEL_ID |
Select a model, including an unlisted model |
/providers |
Browse, add, edit, refresh/test and remove providers |
/providers refresh all |
Fetch model catalogs concurrently, retaining cached models on failure |
/providers add NAME URL [KEY] [chat|responses] [KEY_ENV] [LEVELS] |
Add an endpoint directly; use - for an omitted optional value |
/providers key-clear NAME |
Remove saved inline/environment credentials for that provider |
/combos |
Manage ordered fallback or round-robin groups |
/combos add NAME round_robin provider/model provider/model |
Create a rotating group; fallback is also supported |
/mode, /mode NAME [TASK] |
Execution strategy; a task starts immediately under it |
/keybindings, /keybind ACTION CHORD |
Keymap editor, or rebind one action (persisted) |
/settings, /config |
Quick preferences and a complete configuration browser; arrows adjust values, Enter edits |
/config KEY=VALUE |
Validate, apply and persist a setting (/config tips=true) |
/config KEY VALUE |
Same, for values containing spaces; /config set KEY VALUE also works |
/config KEY |
Show one setting and its description |
/ultraloop GOAL starts a task with an independent completion supervisor. When the main
agent stops, the supervisor reads the goal, durable plan, recent tool results and the
agent's explanation. It sends concrete next steps back when the work is unfinished,
including when the main agent claims it cannot do the task. Repeated invalid tool calls can
also be redirected by the supervisor. Completion requires an explicit verdict with evidence;
unfinished plan items and failing required verification prevent it.
/ultraloop Implement the API, add regression coverage and get the tests passing
/ultraloop status
/ultraloop model cloud/reasoner
/ultraloop limit 0
/ultraloop off
/ultracode Fix the parser and verify every supported input format
Reviews are unlimited by default (limit 0). Set a positive review limit to bound a run.
Esc twice stops the current turn; /ultraloop off disables further reviews, including while
a turn runs. on enables review for the next/current task. model default returns to the
main model with a separate context. State survives /resume; /new starts fresh.
A review that does not come back as a valid verdict is repaired rather than thrown away: a reply cut short by the output limit is re-asked with a larger budget, and a reply the parser rejects is re-asked with the rejected text and the reason it failed, up to three requests. If no usable verdict arrives, the turn fails with the reason and the goal is recorded as unverified on the plan, so it stays visible instead of quietly dropping out of review. The same policy covers context compaction, which asks the model through the same path.
A goal that declines its own verification — /ultraloop do not verify the goal, or any
wording telling the supervisor to never stop — is treated as binding. The supervisor is told
about it, and if it returns completed anyway the verdict is replaced with continue and the
override is journaled, so the run does not stop on a review that ignored the instruction.
The refusal is read from the goal text itself, so it survives /resume and a supervisor model
swap. A plain status report such as "not done yet" is not a refusal.
/ultracode sets maximum coding effort, and an optional task starts immediately. The
supervisor never overrides cancellation or permission rules. Provider/network failures and
unusable reviewer responses are reported as failures, never successful completion.
You can also require a concrete verification command:
/config check_command=cargo test
/config ultra_rounds=3
The command runs through the normal tool/permission path after the agent stops. Failures
return their output to the main agent. After the repair budget, an ordinary turn fails
honestly; with Ultra Loop enabled, the supervisor can propose another approach and continue.
ultra_rounds=0 or an empty command disables this check. Verification calls and results are
persisted together, so resumed sessions retain valid tool-call history.
Headless runs support the same supervisor:
loom run "Implement the API and verify it" --effort ultra --ultraloop \
--supervisor-model cloud/reasoner --ultraloop-limit 20 --output jsonlCtrl+C cancels a headless run. Omit the supervisor model to use the current model, and omit the limit for unlimited reviews.
/tools opens the live tool browser, including MCP tools, descriptions and parameter
schemas. The standard set includes:
| Tool | Purpose |
|---|---|
read, write, edit, fuzzy_edit, multi_edit |
Bounded reads and protected edits with undo metadata |
apply_patch |
Atomic single-file unified diff, with exact context and stale-write protection |
grep, glob, tree |
Targeted search and overviews; glob/grep honor ignore files without requiring Git metadata |
bash |
Bash syntax, development/toolchain environment, separate stdout/stderr and process-group cleanup |
process |
Start, list, poll or stop managed background builds and servers; incremental cursors and bounded logs |
shell_session |
One long-lived shell per named session, reused across calls so cd, exports and activated virtualenvs persist; per-call exit status and timeout |
monitor |
Watch a long-running process: incremental polls, wait_for regex with timeout, bounded log tail, real exit status |
ssh |
Persistent SSH via OpenSSH ControlMaster: connect once, then every command reuses the same authenticated session |
fetch |
Bounded HTTP(S) text retrieval for documentation, APIs and source files |
list_directory, file_info, read_many, find_symbol, project_info, file_compare |
Repository inventory, batched reading, declaration search and comparisons |
read_json, json_edit, notebook_read, notebook_edit |
JSON-pointer operations and notebook cell edits with stale-write protection |
git_status, git_diff, git_log, git_show, git_blame, git_branches |
Read-only Git inspection with literal paths and bounded output |
run_tests, run_build, run_lint |
Project-aware Cargo, Node, Go and Python checks through normal permissions |
context7_search, context7_resolve_library, context7_query_docs |
Current library documentation via Context7; credentials configurable in Settings |
todo_read, todo_write, update_plan, task, note |
Durable checklists, task dependencies, evidence and working notes |
load_skill, memory, read_output, tool_search |
Coding workflows, persistent facts, full output and MCP discovery |
Managed processes use pipes rather than an interactive PTY. Patch calls target one explicit
file and do not delete files. Fetch returns source text and does not execute JavaScript.
Compatible-endpoint aliases such as exec_command/cmd and read_file/file_path are
normalized before validation, and canonical fields take precedence. Calls/results stay
paired across multiple rounds and interrupted turns. Malformed arguments return corrective
feedback, and repeated failures are bounded unless the supervisor redirects the run.
The system prompt directs the agent to load relevant coding skills, read existing todos, record every requirement before multi-step implementation, keep one item in progress, and update the checklist as work proceeds. Findings, changed paths, failed attempts, verification evidence, blockers and next actions belong in durable notes. Compaction preserves this working state. These are model instructions backed by persistent tools; unfinished todos also prevent the supervisor from accepting completion.
Two further rules constrain how the agent answers. It must not guess or assume what it can
check: it reads the code, runs the command, or says the fact is unknown and names what would
settle it. When explaining why something happened it must find the line that explains it and
cite it as path:line, or quote the command and the output line, rather than offering a
plausible cause it never located. It also prefers the dedicated tool — write, edit,
read, grep, glob, the git_* tools — over a shell equivalent, keeping bash for
running commands rather than writing or printing files. The prompt text lives in
crates/loon-core/src/prompt.rs and its length is capped by a test, so every rule has to earn
its bytes.
Failures show the actual command and diagnostic, even inside a collapsed group. Ctrl+O expands tool output; Ctrl+Y copies the last assistant answer. Multiline paste keeps its original lines and the input viewport follows the caret.
File mutations show what changed, not just that something happened: edit, fuzzy_edit,
multi_edit and write append a bounded unified diff to their result, and the transcript
colours it — removals red, additions green, hunk headers yellow, with +N −M as the summary
for a collapsed run. The diff is clipped after 60 changed lines per side (120 for a full
rewrite) and says so rather than truncating silently; a rewrite that changes nothing
produces no diff at all. Every stored
diff line keeps its own row, so colouring stays per-line no matter how large the edit is.
The same colouring applies to a diff pasted into an assistant answer.
Fenced code blocks are syntax highlighted by their fence tag — Rust, Python,
JavaScript/TypeScript/C/C++/C#/Go/Java/Kotlin/Swift, shell, JSON, TOML/YAML, SQL and diffs — with keywords, strings, numbers and
comments styled separately. The highlighter is a tolerant lexer rather than a parser: an
excerpt cut mid-string still renders, and highlighting never changes the underlying text,
so copying a command out of the transcript yields exactly what was shown. An unknown fence
tag falls back to plain code. File reads get the same treatment from their path, so a
read of lib.rs renders with Rust highlighting and a .txt file is left alone; a diff
always keeps its red/green rather than being re-tokenized as source. One request-level t/s counter
covers thinking, writing and tool arguments together. ~ marks a character-based estimate
until the provider reports total output/completion tokens. Reasoning tokens are not added
again. The timer includes request latency, resets on each request/retry and freezes during
tool execution.
Animation includes the banner, tool spinners, streaming cursor, pulsing active-input/modal
borders, modal entrances and an activity scanner. /config animations=false disables
motion. /config popup_rows=10 adjusts slash suggestions. /theme previews terminal, dark,
light, ocean, violet, ember and graphite palettes while you navigate. Enter saves; Escape
restores the prior palette.
Code is highlighted by language — 24 families with their own keywords, comment markers and
string delimiters, including Go, Java, Kotlin, Swift, C, C++, C#, Ruby, Lua, YAML, Dockerfile
and Makefile. A file named for its kind (Makefile, Dockerfile.prod) is recognised, not
just one with an extension. Token classes are separated by colour and weight, so a keyword
(accent, bold), a type (accent, italic), a function (its own palette slot) and an attribute
(warn, bold) are all distinguishable.
The colours come from the palette you chose: the lexer emits semantic slots and the renderer
maps them onto the active theme, so highlighting follows /theme instead of fighting it.
[ui] syntax = false turns highlighting off for a plain transcript — useful when a terminal
theme fights the token colours, and faster on very large outputs.
Diffs mark the changed characters within a modified line, not only the line: a -/+ pair
has its common prefix and suffix trimmed so the differing span stands out. A wholly rewritten
line is left as a plain red/green row, because emphasising the entire line says nothing.
Assistant answers render their structure: **bold**, *italic*, [links](url), - and
1. lists (wrapped items align under their text), > quotes with a rule down the left edge,
and --- rules. Markers only count where they mean something — snake_case is not italic,
and --- inside a fenced block stays a diff header.
All management lists support typing to search, arrows, mouse wheel, Page Up/Down, Home/End, Enter or click, and Escape to clear search/go back. Editors support Tab/Shift+Tab, cursor movement, bracketed paste and Ctrl+S to save. API keys are masked. Blank credentials in an edit retain the current key; a key environment variable takes precedence when set through the editor.
Provider model IDs are namespaced, so identical IDs from different providers do not collide.
Discovery accepts a data or models array, a bare array, and paginated has_more
catalogs. A pasted /responses, /chat/completions or /models endpoint is normalized to
its API base. Authentication, malformed responses and transport failures remain visible.
Open /settings → All configuration to browse appearance, UI behavior, session
defaults, permissions, providers, named models, combos, MCP servers, lifecycle hooks and
Context7. Each field has its own row/editor, with nested collections for headers,
environment entries, model lists, routing members and rules. Left/Right adjusts
choices/numbers, Enter edits text, Ctrl+D removes a selected collection entry, and
Escape returns to the previous page. Add entries with + Add entry.
Changes in this browser are staged. Save all changes validates and applies them without closing the menu; Discard staged changes restores saved values. Errors and connection results appear inside the menu. Quick settings also expose per-session Ultra Loop, review limits, reviewer model, plan mode and the persistent goal.
The browser has user, project and local scopes. Project/local saves write sparse
overrides to .loon/config.toml / .loon/config.local.toml; unchanged fields keep
inheriting parent defaults. Comments and unknown extensions survive edits. Removing an
inherited named entry requires selecting its parent scope. Clearing an optional overlay
value restores inheritance. An external config change blocks saving until the draft is
reloaded.
Open /settings → Context7, or /context7, to configure enabled, API key,
key environment variable and timeout. Enter a masked key, choose Test
connection to test the draft, then Save all changes. The running tools update
immediately. Blank key input keeps the saved credential; use the clear action or
Clear saved key to remove it.
Environment credentials are optional and take precedence over a saved key:
export CONTEXT7_API_KEY='your-key'
loomSaved keys are masked in the UI, excluded from status/event output, and written to the
selected configuration file with 0600 permissions on Unix. Use the user or local scope for
credentials. The agent can search documentation directly or resolve a library and query its
docs at the relevant version. It falls back to official URLs through fetch when Context7
is unavailable.
For other documentation servers, use /mcp or /settings → All configuration →
mcp. HTTP servers can be configured by port/URL; stdio servers expose command,
arguments, environment and timeout. Saving server changes reconnects the live catalog.
Loom updates itself from GitHub Releases. On launch it asks the release feed for the newest version; if that is newer than the running build it downloads the binary for its own target triple, verifies the published SHA-256, replaces the installed file and restarts into the new version.
loom update # check, install, and restart in one step
loom update --check # report the newest release without installing
loom update --prerelease # include a prerelease build
loom update --no-restart # install but keep this process (scripts, CI)The replacement uses the permissions the process already has. Loom never runs sudo, never
installs a privileged helper, and never writes a second copy somewhere PATH would not find
it. If the install directory is not writable — a root-owned /usr/local/bin, for example —
the update reports the path and the exact install command to run instead, and the working
binary is left untouched:
loom: cannot replace /usr/local/bin/loom: the directory is not writable by this user
(/usr/local/bin: Permission denied). Loon does not run privileged commands; install
the new binary yourself with `sudo install -m 0755 <downloaded-loom> /usr/local/bin/loom`The download is verified before anything is written, and an unverifiable release is refused:
- a release that publishes no
.sha256for its binary is not installed; - a digest that does not match the downloaded bytes aborts the update and names both digests;
- a staged file is written next to the target,
fsynced, thenrenamed, so an interrupted or failed update leaves the previous binary in place; - only the raw binary for this platform's target triple is accepted — archives are ignored.
Configuration lives in the [update] table (see the commented example from
loom config init):
[update]
enabled = true
auto_install = true # false: report a newer release, install only via `loom update`
check_interval_hours = 24 # 0 checks on every launch
# repository = "rawnullbyte/LoomCode"
# api_base = "https://api.github.com" # GitHub Enterprise or a mirror
# include_prereleases = false
# timeout_secs = 15LOON_UPDATE selects the launch behavior without editing the file: off disables the
check, check reports a newer release without installing it, and auto installs it. That
last distinction matters on a machine where replacing an install needs a step you want to
take yourself. LOON_UPDATE_API_BASE, LOON_UPDATE_REPOSITORY and
LOON_UPDATE_INTERVAL_HOURS redirect the feed and the check interval. loom update is an
explicit request, so it works even when the launch check is off.
A check that cannot reach the feed is not an error at launch: being offline does not stop
Loom from starting, and loom update reports the reason and exits non-zero. When an update
does install, the process re-executes itself with the same arguments, and a relaunch guard
prevents a second install in the same run.
| Command | Behavior |
|---|---|
/sessions, /resume |
List and search sessions started in this directory; sessions from other workspaces are counted but not offered |
/resume ID_OR_PREFIX, /resume N |
Restore a session and its transcript; ambiguous prefixes are refused, and an id from another directory names that directory |
/new, /clear, /rename TITLE |
Start a saved session (/clear is an alias) or name the current one |
/delete ID_OR_PREFIX, /delete N |
Delete a saved session and its journal; the session you are in is refused |
/todos |
Browse/add/edit tasks and change pending, in-progress, completed or blocked state |
/todos add TEXT, /todos start N, /todos done N |
Manage a plan directly |
/notes |
Browse/add/edit/remove durable session working notes |
/notes add TEXT, /notes remove N |
Manage notes directly |
/goal TEXT |
Set a persistent session objective; /goal clear removes it |
/memory [global|workspace] [TEXT] |
Cross-session facts and preferences |
/compact, /autocompact 50k|on|off|auto, /context |
Context management and usage estimates; on/off preserves model capacity |
/shell list|start NAME|run NAME COMMAND|cwd NAME|close NAME |
Persistent named shell sessions |
/monitors list|start COMMAND|logs ID|poll ID|stop ID|remove ID |
Background servers, watchers and logs |
/jobs list|poll ID|stop ID |
Existing background process management |
/skills, /workflows |
Reload/list bundled and custom workflows |
/mcp |
Manage MCP servers: add, edit, reconnect, remove, inspect tools |
Plans, notes, the goal, selected model and effort are stored separately from the compacted transcript. A bounded working-state excerpt is included in every request. Compaction preserves recent exchanges, can shorten a long tool-heavy turn, and retains original history if summarization fails, is empty, fails to shrink it or cannot be journaled.
Sessions are created by their first message, not when Loom starts: opening the app and
quitting without sending anything leaves nothing behind in /resume. Work state follows the
session the same way, so a message-less session cannot leave an orphaned plan or notes file.
From the shell, the same journal is inspectable without the TUI:
loom sessions list # most recently used first
loom sessions export ID # every event of one session as a list
loom sessions delete ID # asks you to type the id prefix to confirm
loom sessions delete ID --force # no prompt (scripts)
loom resume ID "next task" # continue a saved session headlesslyEvery command that takes an ID accepts either the full id or any unique prefix of it, so
the short ids printed by sessions list are directly usable. An ambiguous prefix lists the
candidates instead of guessing, and an empty one is refused.
Sessions are ordered by last activity, not by creation: the session you messaged five
minutes ago is at the top, even if others were created after it. sessions list shows both
LAST USED and CREATED so the order is self-explanatory. /resume in the TUI uses the
same order and annotates each entry with its age ("5m ago", "2h ago").
/resume and /delete are scoped to the directory the session was started in: the list
offers only sessions whose recorded workspace is this one, and indexes count only those
rows. A session from another directory is never offered, because resuming it would move
the agent into that directory. When sessions exist elsewhere they are counted in the
listing rather than silently dropped, and selecting one by id names the directory it
belongs to. loom sessions list stays global — it is the cross-project overview and prints
the CWD column for exactly that reason.
Deleting removes the session row and its events in one transaction. An unknown id is reported by name rather than as a file-system error, and the session you are currently in cannot be deleted.
loom resume accepts a full id or a unique prefix; an ambiguous prefix lists the candidates
instead of guessing. It reuses the same code path as loom run, so prompt files, model
overrides, output modes and exit codes behave identically.
There are 26 embedded coding workflows, available without installing files:
/implement, /debug, /review, /handoff, /refactor, /test, /optimize, /audit,
/document, /backend, /frontend, /api-design, /database, /migrations, /devops,
/dependency-upgrade, /repository-explore, /docs-research, /rust, /python,
/typescript, /go, /security-review, /test-repair, /release and /performance. See
their bundled instructions. Custom workflows can be a Markdown
file or a NAME/SKILL.md directory in .loon/skills/ or the global data directory's
skills/. Workspace files override global and bundled names. Use {{args}} for
slash-command arguments. The agent's load_skill tool reads workflows on demand.
The agent can inspect/search files, make hash-protected edits, run commands, use MCP tools,
delegate scoped subtasks (blocking) or start them in the background with background_spawn
and manage them with background_list, background_message and background_kill, maintain
its checklist with todo_read/todo_write (sharing state
with update_plan), track dependent tasks with task, write working notes with note, and
persist facts with memory. Independent consecutive read-only tools run concurrently (up to
six); mutating operations remain ordered. Truncated full output has a blob hash and can be
inspected or searched through read_output without rerunning the command.
The system prompt directs the agent to inspect before changing files, implement action requests fully, maintain useful plans, record decisions/blockers, verify changes and distinguish actual results from untested claims. These instructions guide behavior; they do not guarantee the quality of an arbitrary model.
| Effort | Per-request output budget | Preferred wire reasoning level |
|---|---|---|
| low | 4,096 | low |
| medium | 8,192 | medium |
| high | 16,384 | high |
| xhigh | 32,768 | xhigh |
| ultra | 65,536 | max |
Configured model output limits cap these budgets. For compatibility, reasoning parameters
are omitted unless the provider's reasoning_efforts lists accepted wire values, for example
['low', 'medium', 'high', 'xhigh', 'max']. Routing selects the closest supported level at
or below the requested level. [OI]-compatible endpoints vary in support; ultra is an
application setting, not a universal wire value. Chat uses reasoning_effort, Responses uses
reasoning.effort.
[session]
model = "local/my-model"
effort = "high"
[providers.local]
base_url = "http://127.0.0.1:11434/v1"
api = "chat"
# api_key_env = "LOCAL_API_KEY"
# reasoning_efforts = ["low", "medium", "high"] # only when supportedHeadless usage: loom run "Fix the parser" --model local/my-model --effort high --output jsonl.
A turn has no step limit: it runs until the model finishes, the turn fails, or you cancel it.
Exit codes: 0 completed, 1 failed, 2 cancelled, 64 invalid usage/config. JSONL includes
thinking activity, tools, plan updates and turn lifecycle events. Token counts shown as
estimates use a heuristic.
--output has three distinct modes: text (default, for humans), jsonl (one event per
line, for streaming), and json (a single document with session, events and turn, for
a consumer that parses once). json is not line-delimited: json.load on it succeeds.
The opening banner is a multi-line loom drawn in the transcript — a frame with warp threads
and leaves falling through it — so it scrolls away as the conversation grows instead of
occupying a fixed row. The status bar along the bottom leads with the colour-coded permission
mode (bypass in red, auto in yellow, ask in green, allowlist in cyan), then the model, the
live activity, the elapsed time of the current prompt and the current request's combined
throughput in tokens per second, plus the queue depth. Elapsed time coarsens with the
session (45s, 3m07s, 2h05m, 3d04h) so a long turn still reads at a glance, and the
context estimate refreshes at every step boundary, not only when the turn ends — a running
turn shows the size it has actually grown to. Reasoning events show thinking
activity when the provider supplies them; waiting, writing and tool execution are
distinguished. The UI does not invent progress percentages.
Three layers decide how a request runs, and each is explicit rather than inferred.
Execution modes (/mode, or a leading keyword) set the strategy and the runtime switches it
implies. A mode is not a label: plan, review and security really disable mutating tools
through the same gate plan mode already used, ultrawork really turns on the independent
completion supervisor, and ultracode really raises effort to ultra.
| Mode | Effect |
|---|---|
normal (default) |
Direct, proportionate execution |
plan |
Inspect and plan; workspace mutations are refused |
execute |
Execute the approved plan, tracking every item |
ultrawork (keyword ultrawork/ulw) |
Autonomous coordination + independent completion review |
team |
Parallel specialists with one owner per file |
research (keyword deepresearch) |
Documentation research before implementing |
review |
Read-only review of changes |
debug |
Reproduce and trace before changing code |
security |
Read-only risk audit with attack paths |
ultracode |
Highest configured effort + completion review |
repair |
Retry incomplete work with new evidence |
/mode # picker (or a status report headlessly)
/mode plan design the migration # switch, and start that task under the new mode
ulw: add a regression test # the same, from a leading keyword
Only unambiguous keywords route implicitly (ultrawork, ulw, ultracode, deepresearch).
review the parser is a task, not a mode change — the mode names that are ordinary English words
are reachable only through /mode, where you have said the next word is a mode. The mode is
journaled with the session, so /resume restores it.
Keybindings are data, not KeyCode matches scattered through the code. Every command key is
resolved through one map in [keys], with context-dependent scopes (the same Escape stops a turn
in the prompt and dismisses a dialog in a list).
| Action | Default |
|---|---|
| Submit / newline / interrupt-and-send | Enter / Shift+Enter / Ctrl+Enter |
| Stop the turn | Esc, Esc |
| Command palette | Ctrl+P |
| Help | F1 |
| History reverse search | Ctrl+R |
| Expand tool output / copy last answer | Ctrl+O / Ctrl+Y |
| Toggle work details | Ctrl+T |
| Sessions / themes / models / settings | Ctrl+G / Alt+T / Alt+M / Alt+S |
| Keybinding editor | F2 |
| Word motion / delete word | Ctrl+← → / Ctrl+Backspace |
| Cycle permission mode | Shift+Tab |
/keybindings # searchable editor: Enter captures a key
/keybind list # effective binding for every action
/keybind open_palette ctrl+k # rebind (names the action that lost the key, if any)
/keybind reset all # back to the profile defaults
/keybind profile emacs # default | emacs | vim
Chords are written ctrl+p, alt+left, shift+enter, f1, space. Conflict detection runs
before anything is saved: a chord another action owns in the same scope is refused (or, for an
explicit /keybind ACTION CHORD, taken over with the displaced action named), and a chord the
terminal needs (ctrl+z, ctrl+\, ctrl+s) is rejected with the reason. Bad entries in
[keys] make loom config validate fail rather than silently doing nothing.
The command palette (Ctrl+P) searches every slash command, configured model, saved session, theme, setting, execution mode and bound shortcut with subsequence scoring. Accepting an entry submits the real command through the ordinary path, so the palette cannot drift from what the application does — there is no second command registry.
delegate, background_spawn and task_fanout accept a role, a task category and an optional
explicit model. Roles ship in the binary and are overridable by .loon/agents/*.md:
orchestrator planner explorer librarian architect implementer frontend backend
debugger tester reviewer security documentation release
Read-only roles (reviewer, security, planner, explorer, librarian, architect) have
mutating tools removed from the child's registry, so "do not modify files" is enforced rather than
requested. Model choice is resolved in a fixed order — the role's model, then the category's route,
then the session model — and the delegation result names the model and the reason:
[routing.models]
quick = "local/small" # exploration, routine lookups
deep = "cloud/strong"
ultrabrain = "cloud/reasoner"
security = "cloud/reviewer"
[routing.effort]
quick = "low"
deep = "high"A stale route (a model that no longer resolves) is skipped with the reason reported rather than
breaking delegation. /agents shows each role's category, write policy and the model it would
actually use, with why.
- Enter sends, Shift+Enter starts a new line, Ctrl+Enter cancels the running request and sends immediately. Ctrl+Backspace deletes the previous word, and at the start of a line it deletes the line break itself, joining that line with the one above — it never takes the previous line's word along with the break. With a selection active it deletes the selection. These need the terminal's progressive keyboard protocol; Loom enables it automatically when supported.
- In the prompt, Ctrl+Left/Right moves by a word (the same boundaries Ctrl+Backspace deletes), Shift+arrows select text, and Ctrl+Shift+arrows select a word at a time. Typing or Backspace replaces the selection, and Ctrl+C copies it. Clicking anywhere in a multi-line prompt puts the caret on that line and column.
- Esc twice stops immediately and preserves completed edits, tool results and conversation. Pending messages pause; Enter on an empty input resumes them. A held Escape key does not count as two presses.
- Esc before the model has answered anything is a different gesture: the unanswered message is rewound out of the transcript and the conversation, and its text goes back into the prompt bar to edit and resend. Once the model has produced anything — text, a tool call, an edit — Esc stops the turn and keeps the work, as above.
- Messages submitted during a turn enter the next inference request, in order. Up on an empty input retrieves the newest pending message only if it has not already been sent. Ctrl+Enter interrupts and submits pending messages plus the new text immediately.
- Up/Down on the prompt recall prompts you have already sent, newest first; Down past the newest restores the draft you were typing. Typing a prefix makes Up walk only the entries that start with it (
cargosteps through your cargo commands), falling back to the whole history when the prefix matches nothing. This history is saved under the state directory, so it survives a restart and is shared across sessions. - Ctrl+R searches that history: typing narrows the match (case-insensitive), a second Ctrl+R steps to older matches, Enter accepts, and Esc puts back the draft you had. The prompt row shows
reverse-i-search › <query>while it is open. - Tool calls that run back to back share one group; every call in a small group shows a line of its real output, so a passing test run is visible without a keystroke. Ctrl+O expands every call in the group in full.
/newand/resume IDwork during a turn. A fresh session clears selection/stream state and shows its title again, including in short terminals.- Drag a transcript selection to copy; the selected cells are highlighted with a grey background. Ctrl+Y copies the last assistant block directly from its source text.
- Copy preserves source indentation/newlines and Unicode; visual wraps, prompt labels, ANSI escapes and the streaming caret are excluded. The selection uses a stable snapshot while output streams.
- Native clipboard helpers are attempted first with closed stdin and bounded waits; OSC 52 is the fallback for remote terminals. Clipboard behavior still depends on terminal/desktop support.
- Esc twice or Ctrl+C stops an active operation. Page Up/Down and the wheel scroll the transcript outside management lists. Shift+Tab cycles permission modes; explicit denies always apply.
- Permission modes are
bypass,auto,ask,allowlist. Changing a mode updates the actual gate. The original default,bypass, is retained.
cargo test --workspace --no-fail-fast
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
# Recreate the actual-widget SVG previews with sample data:
cargo run -p loon-tui --example preview -- docs/previews
# Build and verify the documentation site:
python3 docs/site/build.py --checkTests include local HTTP discovery fixtures, duplicate provider IDs, pagination/auth errors, reasoning wire formats, live combo switching and rotation, plan/note resume, failed compaction, concurrent tool replay, streamed-text backpressure, immediate follow-up prompts, cancellation after resume, small-terminal modals, secret masking and Unicode/indentation-preserving copying. They do not use live paid-provider credentials.
LOON_HOME=/path keeps config/data/state isolated. Existing config and session paths are
retained.
Two commands keep setup honest. loom doctor prints environment and tool diagnostics
entirely offline unless you pass --connect, and exits non-zero when something is wrong:
loom doctor # paths, terminal, tools, providers, config — all offline
loom config path # where the config file lives
loom config validate # parse the config and show every finding
loom config init # write the commented example config (never overwrites)The documentation in this repository is the website. docs/site/build.py renders the
Markdown you are reading, plus docs/HARNESS.md, into a static site with
no third-party dependencies. The
GitHub Actions workflow deploys it to Pages on every push to main.
python3 docs/site/build.py --check # build to docs/site/_out and verify linksBecause the site is generated from the same files, the documentation cannot drift from the repository.
See the harness guide for HTTP MCP by port/URL, deferred tool discovery, lifecycle hooks, durable task dependencies, agent profiles/worktree isolation, scoped rules, plan mode, permission precedence and supported boundaries. Resize redraws retain drafts and modal selection. Rewind removes the chosen message and later chat, and restores its original text for editing.
MIT — see LICENSE.
Follow-ups typed while the agent is streaming or running tools are editable until the next inference request begins. They do not wait for the whole task to finish. Effort, context, automatic compaction, model selection and supported session settings also apply at the next request boundary. Provider and other configuration changes that require rebuilding services wait for idle.
Automatic compaction has an independent session.autocompact boolean in configuration and in the settings UI. /autocompact off leaves the model window intact. Thresholds reserve completion capacity; unsuccessful or non-shrinking summaries back off instead of retrying on every step. Recent tool-call/result batches stay paired, and the current user's goal is retained during compaction of a long single turn. Token counts are estimates.
Thirty bundled workflows include design, frontend-design, ui-audit and long-running-tools. Use /skills to browse or let the agent load them through load_skill. Design workflows inspect the existing product and references, respect asset constraints, implement working states, and check responsive behavior and keyboard access.
Use /shell start main, then /shell run main cd subdir to retain working directory and environment. /monitors start npm run dev starts a background watcher; inspect its ID with /monitors logs ID, then stop/remove it when finished. List/poll/log status queries remain available while the agent works. Shells are pipe-based; interactive full-screen programs and terminal password prompts require an external terminal.