History navigation
- PageUp / PageDown
- Scroll history or the active document overlay.
- ↑ / ↓
- Cycle the input-draft history when not navigating a suggestion popup.
The lash binary runs an interactive terminal UI by default, autonomous single-shot modes under --print, a JSONL event stream under --mode json, an stdin/stdout RPC loop under --mode rpc, and administrative flows like --export and --reset. This repository owns the executable and its user-visible contract.
If ~/.lash/config.json does not exist, lash launches the setup wizard. The wizard provisions an active provider, collects credentials, asks for the default execution mode and RLM dialect, and writes the config file before dropping you into the TUI.
$ lash
# Setup wizard:
# 1. Choose a provider (Anthropic, OpenAI, OpenAI-compatible, Codex, Google OAuth)
# 2. Paste API key (or run OAuth / device-code flow)
# 3. Optionally paste a Tavily API key for the built-in web-search tool
# 4. Choose standard or RLM; if RLM, choose lashlang or typescript
# 5. Drop into the interactive TUI
Re-run the wizard at any time with lash --provider. Use /provider from inside a session to switch providers without restarting.
Type a / at the start of the prompt to invoke a command. Tab-completion is available. Skill names registered in ~/.lash/skills/ or .agents/lash/skills/ appear as additional /<skill> commands.
| Command | Aliases | Usage | Description |
|---|---|---|---|
/clear | /new | /clear | Reset conversation |
/compact | — | /compact [focus instructions] | Open a compaction Agent Frame seeded by a summary |
/controls | — | /controls | Show keyboard shortcuts |
/fork | — | /fork | Open a forked session in a new terminal |
/tree | — | /tree | Browse and switch branches in the current session |
/version | — | /version | Show lash-cli and lash-sansio versions |
/info | /status | /info | Show current session/runtime info |
/model | — | /model [name] | Show or switch the LLM model |
/variant | — | /variant [name] | Show or switch the model variant (e.g. high, max) |
/mode | — | /mode | Show the session's locked execution mode and, for RLM, its pinned dialect (for example rlm · typescript) |
/expand | — | /expand [compact|normal|full] | Show or set the timeline expansion level (0/1/2 also accepted); the same toggles as Ctrl+O and Alt+O, and it works while scrolled up |
/provider | /login | /provider | Switch, add, or re-authenticate providers |
/logout | — | /logout | Remove stored credentials for active provider |
/retry | — | /retry | Replay the previous turn payload |
/resume | /continue | /resume [id-or-name] | Search sessions or resume by id/name (interactive picker if no arg) |
/skills | — | /skills | Browse loaded skills |
/help | /? | /help | Open the commands and shortcuts overlay |
/exit | /quit | /exit | Quit |
The TUI uses a small, consistent set of shortcuts. The full list is also available in-session via /controls.
LASH_COPY_BINDING if the default conflicts with your terminal.A running process shows running; a terminal one shows its outcome — success, error, cancelled, or abandoned. abandoned (ADR 0019) means the owner was lost without recording an outcome and renders in the failure style, like error. A detached command (shell.start with detach: true) never appears in the dock: it returns immediately with status: "detached" and is owned by the host/OS, not tracked as a session process.
Run lash --help for the full list. The most common flags, grouped by purpose:
--provider--model <name>--variant <name>high, max, xhigh).--api-key <key>--base-url <url>--tavily-api-key <key> (env TAVILY_API_KEY)--execution-mode <mode>standard (default) or rlm.--rlm-dialect <dialect>lashlang or typescript for the lifetime of a new RLM session. Refused in standard mode.--rlm-var name=<json>--rlm-vars-file <path>--rlm-termination <mode>natural (default) or require an explicit structured finish with finish-required.--resume <id_or_name>.db files are listed as incompatible and refused with their preflight schema stamps; they are never opened or deleted.--resume-prompt <prompt>-p, --print <prompt>--mode <interactive|json|rpc>interactive is the TUI default; json requires --print and writes JSONL events to stdout; rpc reads LF-delimited requests from stdin and writes LF-delimited records to stdout.--await-background-work--turn-usage-json <path>--debug--trace-level <standard|extended>standard.--debug-ui-trace trace.json--debug-ui-trace-interval-ms <ms>--reset~/.lash/store/ and the host session roster under ~/.lash/sessions/, preserving provider configuration.--info--check-update / --update--export <session-id>--export-format html|json, --export-trace <jsonl>, and --export-out <path>.lash --print "<prompt>" runs Lash autonomously: it boots, executes the prompt, prints the settled TurnFinish::AssistantMessage prose to stdout, and exits. Print mode runs without a TUI; status and tool output go to stderr. An autonomous system prompt is injected so the assistant completes the task without asking for input.
Each completed tool call prints a one-line status marker to stderr: [tool] <name> · <status> · <duration>, where status is ok, error, or cancelled and the duration is dropped when it is negligible. The <name> is the runtime tool name (exec_command, not the shell.exec surface path); see the Lash SDK's reporting contract.
$ lash --print "Summarise this branch's changes in two bullets."
# Assistant prose to stdout, tool/lifecycle logs to stderr:
# [tool] exec_command · ok · 41ms
# [tool] read_file · ok
$ lash --print "Investigate the latest test failure." \
--await-background-work \
--turn-usage-json out/usage.json
--mode json and --mode rpc are machine-facing CLI surfaces. They do not enter the TUI and they reserve stdout for LF-delimited JSON records. The stream is versioned: the opening turn_start record (and the RPC ready record) carries protocol_version: 1, so a consumer can pin the wire shape before parsing the rest.
$ lash --mode json --print "Summarise this repository."
{"type":"turn_start","protocol_version":1,"stream_id":1}
{"type":"event","stream_id":1,"activity":{"id":"a1","correlation_id":"c1","type":"model_request_started","protocol_iteration":0}}
{"type":"event","stream_id":1,"activity":{"id":"a2","correlation_id":"call-3","type":"tool_call_completed","call_id":"call-3","name":"exec_command","args":{"...":"..."},"output":{"...":"..."},"duration_ms":41}}
{"type":"turn_finish","stream_id":1,"ok":true,"cancelled":false,"assistant_text":"...","outcome":{...},"usage":{...},"children_usage":[],"errors":[],"execution":{...},"tool_calls":[...]}
Each event wraps one TurnActivity: an id, a correlation_id, and a flattened TurnEvent tagged by type. Tool events report the runtime tool name (exec_command), not the shell.exec surface path; see the Lash SDK's event vocabulary and stability contract.
RPC mode starts a reusable session and reads one JSON object per line from stdin. It opens with a ready record ({"type":"ready","protocol_version":1,"protocol":"lash.rpc.v1","methods":["prompt","ping","shutdown"]}). A prompt request streams turn_start, event, and turn_finish records tagged with the request id, then writes a final response.
{"id":1,"method":"prompt","params":{"prompt":"List the top-level crates."}}
{"id":2,"method":"shutdown"}
Configuration lives at ~/.lash/config.json (overridable via the LASH_HOME environment variable). The schema is defined by LashConfig in lash-cli and uses serde(deny_unknown_fields), so unknown top-level fields are rejected to catch typos.
{
"active_provider": "openai",
"charge_safety": { "mode": "require_guarantee" },
"execution_mode": "rlm",
"rlm_dialect": "typescript",
"providers": {
"openai": { "type": "openai", "api_key": "sk-..." },
"anthropic": {
"type": "anthropic",
"api_key": "sk-ant-...",
"options": {
"cache_retention": "long",
"max_output_tokens": 16384,
"reliability": {
"response_start_timeout": 30000
}
}
}
},
"auxiliary_secrets": {
"tavily_api_key": "tvly-..."
},
"mcp_servers": {
"docs": {
"transport": "stdio",
"command": "uvx",
"args": ["mcp-server-docs"],
"graceful_period_ms": 3000,
"post_kill_wait_ms": 1000
}
},
"agent_models": {},
"model_defaults": {
"openai": { "model": "openai/gpt-5.5", "variant": "medium" }
}
}
| Field | Type | Purpose |
|---|---|---|
active_provider | string | Key of the currently active provider; must exist in providers. |
providers | map | Per-provider credentials and metadata, keyed by provider kind. Shared provider tuning lives under options, including cache_retention (none | short | long), max_output_tokens, and reliability.response_start_timeout in milliseconds. Omit the response-start timeout or set it to 0 to retain the legacy min(request, chunk) bound. |
charge_safety | object | Live host retry policy. Defaults to {"mode":"require_guarantee"} when unset. Applied on every creation and reopen, including /clear, /resume, autonomous runs, and native queued work; it is excluded from durable session policy serialization. |
execution_mode | string | Default mode for fresh sessions: standard or rlm. |
rlm_dialect | string | Default language pinned to fresh RLM sessions: lashlang or typescript. |
auxiliary_secrets | object | Secrets for non-LLM services. Currently tavily_api_key. |
mcp_servers | map | MCP server configs by name. Discriminated by transport: stdio, streamable_http, or sse. Each server may set graceful_period_ms before forced termination and post_kill_wait_ms after it. |
agent_models | map | User-overridable model names per subagent capability. |
model_defaults | map | Fresh-session model + variant default per provider kind. Session resumes ignore this and use the persisted head. |
By default, Lash requires a provider idempotency or resume guarantee before retrying a generation that may already have been billed. To accept bounded duplicate billing, set this top-level config key:
"charge_safety": {
"mode": "accept_duplicate_billing",
"max_unsafe_retries": 2,
"max_duplicate_cost_tokens": 2000
}
Lash clamps max_unsafe_retries to at most five per logical LLM call. The optional token bound counts input, output, cache-read, and cache-write tokens already billed for the abandoned generation. Missing partial usage counts as zero toward this bound, so it cannot cap unknown charges; the retry-count limit still applies. Omit max_duplicate_cost_tokens or set it to null for no token bound. Nonretryable failures remain nonretryable. This is configuration, with no CLI flag.
Provider retry settings live under providers.<kind>.options.reliability.retry. ProviderRetryPolicy.jitter_ms defaults to 500 milliseconds of per-attempt additive random jitter; 0 disables it. retry_after_cap_ms defaults to 60000 (60 seconds). A longer server Retry-After now refuses immediately instead of sleeping a truncated delay.
Failed-turn diagnostics show the charge refusal code, billed tokens, and whether partial output exists. Zero usage is labeled as reported or defaulted because the evidence does not distinguish missing usage from a reported zero. Partial text stays out of the terminal timeline. Advisory issues render as warnings, and omitted tool records disclose their counts. JSON/RPC finish records also carry failure_evidence and omitted.
The CLI reads a small set of environment variables; most are overrides for paths or behavior the config file doesn't cover.
| Variable | Purpose |
|---|---|
LASH_HOME | Override the root data directory (defaults to ~/.lash/). |
OPENAI_API_KEY / OPENAI_COMPATIBLE_API_KEY | Provider API keys used during setup or one-shot sessions when no stored key is configured. |
OPENROUTER_API_KEY | OpenRouter API key accepted as an OpenAI-compatible shortcut when --base-url points at https://openrouter.ai/api/v1. |
TAVILY_API_KEY | Tavily API key for the bundled web search tool. Alternative to --tavily-api-key and the config file. |
LASH_GOOGLE_CLIENT_ID / LASH_GOOGLE_CLIENT_SECRET | Override the OAuth client credentials used by the Google / Code Assist provider. |
CODE_ASSIST_ENDPOINT / CODE_ASSIST_API_VERSION | Override the Google Code Assist API endpoint and version for provider integration testing. |
LASH_COPY_BINDING | Override the copy keystroke: ctrl-shift-c (default) or ctrl-y. Ctrl+C stays reserved for cancel semantics, so ctrl-c falls back to the default. |
LASH_RUNBOOK_DIALECT | Default RLM dialect when an older config file does not yet declare rlm_dialect. |
LASH_LOG | tracing-subscriber log filter (e.g. debug, warn, lash=trace). |
LASH_NO_OSC52 | Disable OSC 52 clipboard escape sequences. |
LASH_INSTALL_DIR | Override the install dir used by --update. |
LASH_REPO | Override the GitHub repo used by --check-update / --update. |
LASH_TOKIO_STACK_BYTES | Override the worker-thread stack size used by the CLI Tokio runtime. |
GOOGLE_CLOUD_PROJECT[_ID] | Google Cloud project ID for the Gemini / Code Assist provider. |
Every session has a UUID and normally a generated human-readable name. --resume <id_or_name> accepts either; legacy .db filenames are refused as incompatible. Without an argument, /resume opens an interactive picker showing the 50 most recent live root sessions from Lash's unified catalog, sorted by recency. The host roster contributes display metadata only, so catalog-only sessions remain visible with fallback labels. The picker hides zero-turn sessions when any non-empty session exists; direct /resume <id-or-name> may still target any session.
The current durable compatibility markers are session schema 50, trace schema 18, and remote protocol 54. lash --version and /info report them.
This pin is a forward-only, whole-home runtime-data cutover. Older session, process, trigger, and effect-replay databases are refused before provider startup; there is no migration chain. Retain or export needed data with the old compatible binary first. Then run lash --reset and confirm to remove the entire runtime store and session roster together, preserving provider configuration. Never reset databases piecemeal. Old trace files need a compatible reader; remote peers need matching protocol versions.
/fork branches the current session at the current message and opens the fork in a new terminal window. Lash resolves the launcher from the running executable and falls back to finding lash on $PATH if that binary isn't launchable. The launched fork runs lash --resume <new-session-id> in the new terminal.