Closes the gap to Claude Code across four upgrade rounds (all verified green: typecheck, 388 tests, build 313 KB). Sub-agent orchestration: - Resumable sub-agents (send_message) + MaxIterations "continue" path - Configurable multi-depth sub-agent nesting - Parallel fan-out for pure agent/agent__* batches, each writable delegation in its own throwaway git worktree (no file collisions) - Named specialist fleet: explore, code-reviewer, planner, debugger, test-writer (read-only types skip worktree isolation) - Named teammates: agent tool `name` arg + list_teammates + send_message by name (synchronous, addressable handle — not true async background) Plan mode: - True in-turn pause/resume on approval (no synthetic proceed turn) - exit_plan_mode tool + side-by-side diff rendering (/diff) Persistent memory: - Typed file-per-fact memory under config dir, index folded into the system prompt (bounded), memory + memory_write tools, /memory command Toolset + UI: - multi_edit, notebook_edit, web_search/web_fetch, workflow orchestration primitive (agent/parallel/pipeline/phase/log + worktree isolation + best-effort structured output with recursive schema validation) - Structured task system (task_create/list/get/update with dependency graph + ownership) replacing flat todo_write - Cron/scheduled tasks (cron_create/list/delete, schedule_wakeup) with durable persistence and idle-gated ticking - Interactive worktree session (enter_worktree/exit_worktree) with a live-cwd UI switch that survives /model changes - AskUserQuestion tool + QuestionPrompt UI - Permission rule granularity + user/project settings merge - Color diffs, sub-agent activity panel, /undo, dashboard refinements Co-Authored-By: Claude <noreply@anthropic.com>
locode
An agentic coding CLI, in the spirit of Claude Code, for models running locally via Ollama or LM Studio. It talks to either backend's OpenAI-compatible /v1/chat/completions endpoint, so any model you can serve from either one works here.
Install
npm install
npm run build
npm link # makes the `locode` command available globally
Quick start
Make sure Ollama (ollama serve, default http://localhost:11434) or LM Studio (with a model loaded, default http://localhost:1234) is running, then:
locode --model qwen3-coder:30b
If you omit --model, locode lists the models available from the backend and lets you pick one with the arrow keys (Enter to confirm). Your saved default (via config or $LOCODE_MODEL) is pre-highlighted.
Pass --model explicitly to skip the picker entirely. Or persist your defaults so you don't need flags every time:
locode config set backend ollama
locode config set model qwen3-coder:30b
locode
List models available from the configured backend:
locode models
Every conversation is auto-saved as you go. Resume later:
locode --continue # resume the most recent conversation
locode --resume # pick from a list of saved conversations
locode --resume <id> # resume a specific one
locode sessions list # see saved conversations (id, model, title) without starting the UI
locode sessions rm <id> # delete a saved conversation
locode can also use tools from external MCP servers:
locode mcp add my-server --command npx --arg -y --arg @some/mcp-server # stdio server
locode mcp add my-remote --url https://example.com/mcp # remote (streamable HTTP) server
locode mcp list # see configured servers (user-level + project .mcp.json)
locode mcp remove <name>
Project-level servers can also be checked into a repo via a .mcp.json file in its root:
{
"mcpServers": {
"my-server": { "command": "npx", "args": ["-y", "@some/mcp-server"] }
}
}
locode connects to every configured server on startup; their tools show up alongside the built-in ones, namespaced as mcp__<server>__<tool>.
locode can also install Claude Code plugins directly — the parts that map onto locode's own architecture, anyway (see below for what doesn't):
locode plugin add ./path/to/plugin # a local plugin directory
locode plugin add https://github.com/someone/some-plugin.git # cloned via git
locode plugin list
locode plugin remove <name>
Hooks (see below) are hand-edited JSON rather than added via a CLI subcommand:
locode hooks path # print the user-level hooks.json path to edit
locode hooks list # see every configured hook (plugin + user + project), merged
How it works
locode is a full-screen terminal app built with Ink (the same React-for-CLI framework Claude Code itself is built with) — it needs a real interactive terminal (piped/redirected input isn't supported). It takes over the terminal's alternate screen buffer (like vim/htop) — your prior scrollback is restored when you exit. The input box is always pinned to the last row of the window; the conversation fills the space above it and old messages scroll off the top as new ones arrive. Press Ctrl+C or type /exit to quit.
-
Ctrl+O: print the full text of the last
/compact(or auto-compact) summary. The collapsed notice you see right after compacting only shows a one-line hint — press Ctrl+O any time afterward to print the whole thing. -
Ctrl+B: while a
bashcommand is running, detaches it into the background and returns control to you immediately — the turn continues with abash_output-checkable job id instead of waiting for the command to finish. A notice appears in the transcript once the backgrounded command actually completes. Onlybashsupports this today. The model can kill a still-running backgrounded job withbash_kill; any jobs still running when locode itself exits are killed too, so they don't outlive the process as orphans. -
Backends:
--backend ollama(default) or--backend lmstudio, or--base-url <url>for anything else that speaks the same API. -
Tools:
read_file,list_files,grep,web_search,web_fetch,git_status,bash_output,todo_writerun automatically.write_file,edit_file,bash,bash_kill, andgit_commitshow a diff/preview in a bordered box and ask you to pick Yes / Yes-always-this-session / No with the arrow keys before running. -
Permission modes:
default(ask before every mutating tool),plan(research only — every mutating tool is blocked outright, no prompt; the model is expected to describe what it would do in its final answer instead),auto-edit(file edits auto-approved,bash/git_commitstill ask),auto-accept(everything auto-approved — use with care). Cycle withShift+Tabor set directly with/perm <mode>. -
Task checklists: for multi-step work the model can call
todo_writeto show a live checklist (☐/◐/☑) in the transcript instead of silently working through a list you can't see progress on. -
Project instructions: a
CLAUDE.md(orAGENTS.md) file in the project root is automatically read at session start and folded into the system prompt — put repo-specific conventions there and every session picks them up without being told. -
Images:
read_filereturns image files (png, jpg, jpeg, gif, webp, bmp — up to 5MB) as actual image content instead of trying to decode them as text, so vision-capable models can see them when the model itself calls the tool. To attach a file or image to your own message directly, use/import <path> [caption]. -
@file mentions: type@in the chat input to open a fuzzy file picker (searches the whole project, skippingnode_modules/.git/dist) — keep typing to filter,↑/↓to navigate,Tab(orEnter) to insert the highlighted path. Any@pathleft in your message when you hitEnterfor real is resolved against disk and attached to that message (text inlined, images attached as image content) — a stray@that isn't an actual file (e.g. an email address) is left as plain text. -
Git:
git_statuscovers read-only inspection (status,diff,log,show,branches) and runs automatically.git_commitcoversadd,commit,create_branch,checkout, andpush— each shows the actual diff/status/commits it's about to affect before you confirm (e.g. a commit's preview is the staged diff plus the message, a push's preview is the list of commits it would send). -
Sub-agents: the
agenttool lets the model delegate a self-contained task to a fresh, isolated tool loop (same tools, minusagentitself — no nested sub-agents) and get back only the final answer, keeping the main conversation's context focused. It shows up as a single⏺ Agent(description)/⎿ Sub-agent finished (...)line — the sub-agent's own intermediate steps aren't displayed. Any mutating tool calls it makes still go through the same permission prompts as the main conversation. -
MCP servers: locode connects to any MCP servers configured via
locode mcp addor a project's.mcp.json(stdio and remote/streamable-HTTP transports), and adds their tools to every session, namespaced asmcp__<server>__<tool>. Every MCP tool is treated as mutating (confirmation required on every call) regardless of what it reports — the MCPreadOnlyHintannotation is advisory and could be wrong (or set by a malicious server specifically to skip confirmation), so locode never trusts it. One misconfigured server doesn't block the others — check/mcpfor per-server connection status. -
Claude Code plugins:
locode plugin add <path-or-git-url>installs a Claude Code-compatible plugin — locode reads its.claude-plugin/plugin.json, then loads it all directly: MCP servers (its.mcp.jsonor manifestmcpServers, merged in like any other MCP server), slash commands (commands/*.md— frontmatterdescription/argument-hint, body is a template expanded with$ARGUMENTS/$1..$9and submitted as your message), agents (agents/*.md— the body becomes a sub-agent's system prompt, exposed as a callable tool namedagent__<plugin>__<agent>; atools:frontmatter list restricts what it can use, with Claude Code's built-in tool names — Read, Grep, Edit, etc. — automatically mapped to locode's equivalents), hooks (hooks/hooks.json, see below), and skills (skills/*/SKILL.md, see below). Check/pluginsfor what's loaded. -
Skills: named instructions the model loads on demand rather than a hook or a sub-agent — every installed skill (
skills/<name>/SKILL.md) is exposed through one sharedskilltool, whose own description lists every skill's name and "use this when..." blurb so the model knows when to call it. You can also invoke one directly with/<skill-name> [request], which skips the model's own judgment and submits the skill's instructions (plus your request, if any) as the turn. Check/skillsfor what's installed. -
Hooks: shell commands that fire on session lifecycle events —
SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,PermissionRequest,SubagentStart,SubagentStop,CwdChanged,FileChanged,ConfigChange,Stop,SessionEnd. Configured the same way MCP servers are — plugin-bundled (hooks/hooks.json), user-level (locode hooks path, hand-edited), and project-level (.locode/hooks.json) all merge together, every hook from every source runs. A hook receives a JSON payload on stdin (session_id,cwd,hook_event_name, plus event-specific fields likepromptortool_name/tool_input); exit 0 allows (stdout becomes injected context forSessionStart/UserPromptSubmit), exit 2 blocks (stderr is the reason shown), anything else is a non-blocking warning.PreToolUse,UserPromptSubmit, andPermissionRequestcan block; the rest are fire-and-forget.PreToolUsefires before permission modes apply, so a hook's block can't be bypassed by auto-accept.commandandhttphook types are supported (promptis declared in the config format but not yet executed); command hooks can opt into structured JSON output viaoutputSchema: "json".SessionEndfires on every exit path (including Ctrl+C and externalSIGTERM/SIGHUP). Check/hooksfor what's configured. -
Tool-calling mode: on connect, locode probes whether the model reliably uses native OpenAI-style function calling. If not, it switches to a prompt-based fallback mode where the model is instructed to emit tool calls as fenced
```tool_call ```JSON blocks, which locode parses itself. The result is cached per backend+model so future sessions skip the probe. Override with--tool-mode native|fallback|autoor the in-session/modecommand. -
Context tracking & compaction: the status bar shows
ctx NN%— context window usage, from realusage.prompt_tokenswhen the backend reports it (requested viastream_options.include_usage), or a~-prefixed char-based estimate otherwise. The window size itself is auto-detected (Ollama's/api/show, then LM Studio's/api/v0/models) and cached per backend+model; falls back to a configurable default (locode config set contextWindow <n>, or$LOCODE_CONTEXT_WINDOW) if neither responds. At 85% usage, locode automatically asks the model to summarize the conversation and replaces the history with that summary (a notice tells you when this happens) — or trigger it yourself anytime with/compact.
Note: even models with genuine native tool-calling support occasionally emit a tool call as plain text instead of a real structured call — this is model sampling variance, not a bug. If a turn seems to "describe" a tool call instead of running it, just ask again or try /mode fallback.
Slash commands
/model <name> switch the model used for the current backend
/backend <name> switch backend (ollama | lmstudio), keeps current model
/mode <name> view or force tool-call mode (native | fallback)
/perm [mode] cycle or set permission mode (default | plan | auto-edit | auto-accept)
/status show current model, backend, tool-call mode, and cwd
/dashboard show session stats: token I/O, elapsed/model time, turns, tool calls
/tools list available tools
/permissions list mutating tools allowed for the rest of this session
/sessions list saved conversations you can resume with --resume
/mcp show connected MCP servers and their tool counts
/plugins show installed Claude Code-compatible plugins (commands, agents, skills, MCP servers)
/hooks show configured hooks per lifecycle event
/skills show installed skills; /<skill-name> [request] invokes one directly
/compact summarize the conversation now to free up context
/export [file] save the conversation as markdown — opens an editable filename prompt (default: locode-export-<timestamp>.md)
/import <path> [caption] attach a local file or image to your next message
/clear clear conversation history
/help show this help
/exit, /quit exit
Config
Config precedence: CLI flags > env vars (LOCODE_BACKEND, LOCODE_MODEL, LOCODE_BASE_URL, LOCODE_CONTEXT_WINDOW, LOCODE_MAX_ITERATIONS, LOCODE_AUTO_COMPACT_THRESHOLD, LOCODE_REQUEST_TIMEOUT_MS) > persisted config file > defaults.
locode config set backend ollama
locode config set model qwen3-coder:30b
locode config set contextWindow 32768 # fallback size when auto-detection fails
locode config set maxIterations 40 # max tool calls per turn before locode gives up (default 50)
locode config set autoCompactThreshold 0.85 # fraction of context window at which auto-compact triggers
locode config set requestTimeoutMs 300000 # per-request timeout in ms (default 180000); raise this if
# your backend queues requests behind a concurrency limit
# (e.g. Ollama's OLLAMA_NUM_PARALLEL) under multi-session load
locode config get
locode config path
Known limitations
- Requires a real interactive terminal (TTY) — you can't pipe input into it or run it from a non-interactive script.
- Native tool-calling reliability varies by model and is non-deterministic even for capable models (see above).
- No sandboxing beyond the confirmation prompts — mutating tools operate on the real filesystem/shell with the permissions of the user running
locode. Only approve commands you understand. - Session resume replays prior user/assistant text so you can see it, but it doesn't re-display prior tool-call/tool-result lines from before the resume (the model still has that history — it's just not re-rendered).
- No in-app scrollback — once a message scrolls off the top of the window it's gone until you resize the terminal taller (the conversation itself is still intact and sent to the model; this only affects what you can visually re-read).
- Windows shell quoting for the
bashtool has only had light testing; behavior may differ from Unix shells for complex quoting. git_commitcovers add/commit/create_branch/checkout/push/reset/stash/merge/rebase/delete_branch. Usebashfor anything beyond that.- MCP tool results support text, image, audio, and resource content blocks. Images are returned in the same shape as
read_fileso vision-capable models can see them; audio and binary resources are summarized. Remote (HTTP) MCP servers support static headers (e.g. a bearer token) but not OAuth flows. - Compaction (
/compactor automatic) replaces history with a model-generated prose summary — it costs one extra model call and loses tool-call/tool-result detail (the model's own account of what happened survives; the raw record doesn't). The auto-compact threshold defaults to 85% and is configurable vialocode config set autoCompactThresholdorLOCODE_AUTO_COMPACT_THRESHOLD. - Plugin support (
locode plugin add) now covers every part of a plugin: MCP servers, slash commands, agents, hooks, and skills. A plugin'sallowed-toolsrestriction on a command isn't enforced (the expanded prompt just runs as a normal turn with the full toolset). Duplicate MCP server names across sources are now detected and surfaced in/mcp— project-level wins over user-level wins over plugin-level. Duplicate slash-command and skill names are also surfaced in/pluginsand/skills. - Skills are exposed as one shared
skilltool rather than one tool per skill — if two plugins install a skill with the same name, the first plugin in load order wins and the collision is shown in/skills. Skills can bundle siblingreferences/*.mdfiles that are included when the skill is invoked. - Hooks cover the most useful subset of Claude Code's lifecycle events:
SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,PermissionRequest,SubagentStart,SubagentStop,CwdChanged,FileChanged,ConfigChange,Stop,SessionEnd.CwdChangedis declared in the config format but not yet fired anywhere — a cwd never changes mid-session in locode today, so configuring it is a no-op for now.commandhooks andhttphooks are supported;prompthooks are declared in the config format but not yet executed. Command hooks can opt into structured JSON output viaoutputSchema: "json".PreToolUse,UserPromptSubmit, andPermissionRequestcan block; the rest are fire-and-forget.SessionEndfires on every exit path (including Ctrl+C and externalSIGTERM/SIGHUP), so it doesn't always have a real session id to report. All hooks for an event run in parallel with no defined ordering, and every configured hook always runs — there's no way to disable one without editing the file it came from.