Tools
What the agent can use: files, the shell, tests, Git, code intelligence, a debugger and the web.
What the agent can use
| Tool | Does |
|---|---|
read | Read files, several at once, with numbered lines. A long file comes back a page at a time. An image is read as a picture for models that can see. |
write, edit | Write or change a file. A file must be read before it is edited, and the card shows the diff, after formatting. |
bash | Run a shell command. Long output is trimmed, and the untouched output saved under .luxe/artifacts/: each stream whole up to 8 MB, or its first and last 4 MB past that. |
test | Run the project’s tests and report each failure with its file:line, and which tests your change broke. Output is saved as bash’s is. |
grep, glob | Search file contents and names, with capped output |
ast_grep | Search and replace by code structure. Needs ast-grep. |
git | Status, diffs, history, commits, branches, and GitHub pull requests, issues and CI checks through gh |
todo | The agent’s checklist, shown in the dock’s PLAN |
question | Ask you a question with choices, in a picker |
skill | Load a skill |
symbol_search, read_symbol, module_report | Find symbols across the project and read one without the whole file |
impact | What else a change to a symbol or file affects: callers, dependents and covering tests |
lsp_diagnostics, lsp_navigation | Errors and warnings, definitions, references and types, from a language server |
debug | Drive a debugger: breakpoints, stepping, inspecting variables. Offered when a debug adapter is installed. |
web_search, web_fetch | Search the web and read pages; see Web |
screenshot | Capture a web page or your own dev server, with a Chromium-based browser, or with your approval, your screen; see Screen capture |
mcp | Call tools on the MCP servers you configured, when there are any |
Each tool has to beat the shell command it replaces. luxe.dev/tools shows how, measured.
A call with an argument the tool does not know still runs, and its result names the argument it ignored, with the one the agent probably meant.
/tools lists what the agent can use in the current session, and why any tool is missing. Some tools are sent with every
request; the rest are listed by name and loaded when the agent first needs one, which keeps every
request smaller.
The test tool runs tests the way the project does: through its own .venv or the package
manager its lockfile names (uv, poetry, pnpm, yarn and so on), not whatever happens to be
on PATH.
Turn tools off
The Tools page of /settings turns each one on or off, or set it in
the file:
{ "tools": { "disabled": ["web_search", "screenshot"] } }A change applies the next time Luxe starts, since the tools the agent is offered are fixed for a run.
For one session, luxe --no-tool web_search turns a tool off and luxe --with-tool web_search
turns one back on. read, write, edit and bash cannot be turned off.
Format on save
Each file the agent writes or edits goes through its formatter, and the result shows the file as
the formatter left it. That includes a file changed by an applied rename, code action or ast_grep
rewrite. rustfmt and gofmt always run. The others run only where the project has
their config, in the file’s directory or one above it:
| Language | Formatter | Runs when the project has |
|---|---|---|
| Rust | rustfmt, at the project’s edition | always |
| Go | gofmt | always |
| Python | ruff | ruff.toml, .ruff.toml, or [tool.ruff] in pyproject.toml |
| TypeScript, JavaScript | biome | biome.json or biome.jsonc |
| TOML | taplo | taplo.toml or .taplo.toml |
A formatter that is not installed is skipped. When Luxe pipes a file through one (taplo), an empty answer, or a file that changed again while it ran, leaves the file as it was. To turn formatting off:
{ "tools": { "format_on_save": false } }Time limits
Every tool call has a time limit, after which it is stopped: 120 seconds, or 60 for tools that wait
on the network. bash defaults to 30 seconds and test to 300, and the agent can ask for longer,
up to an hour. question waits for you as long as it takes. LUXE_TOOL_TIMEOUT_SECS replaces the
120- and 60-second limits; bash and test keep their own.
ctrl+esc stops the whole batch of calls, not just the one running. A call that never started is
reported as having had no effect; one stopped partway says so, since it may have done half its work.
A command that starts something and leaves it running, such as a dev server, returns when the command itself ends. The server keeps running.
Tools work from the directory Luxe was started in. A cd inside one bash call does not carry over
to the next, so start Luxe in the project you want to work on.
Code intelligence
After every edit, Luxe asks the file’s language server for errors and warnings and adds the ones
the edit introduced to the result the agent sees, so it can fix what it broke before moving on.
Problems that were already in the file are counted, not listed. The result also names changed code
that no test covers. Set LUXE_LSP_ON_EDIT=0 to turn both off.
The agent is held to small files and functions: 500 lines and 20 lines. When an edit takes a file
or a function past one of those for the first time, the result says so, so the agent can split it
while the change is fresh. A file that was already long says nothing more. Functions are measured
in Rust, Python, Go, TypeScript and JavaScript; files in any language. Set LUXE_SIZE_CHECK=0 to turn this off, for a project whose own
conventions differ.
Luxe launches these language servers when they are installed:
| Language | Server | Install |
|---|---|---|
| Rust | rust-analyzer | rustup component add rust-analyzer |
| TypeScript, JavaScript | typescript-language-server, tsgo for TypeScript 7, or tsc --lsp | npm i -g typescript-language-server typescript |
| Python | pyright, or basedpyright | npm i -g pyright |
| Go | gopls | go install golang.org/x/tools/gopls@latest |
| C, C++ | clangd | apt install clangd, brew install llvm |
| Bash | bash-language-server | npm i -g bash-language-server |
| Ruby | ruby-lsp, or solargraph | gem install ruby-lsp |
| Lua | lua-language-server | mise use -g lua-language-server |
| Svelte | svelte-language-server | npm i -g svelte-language-server |
| JSON | vscode-json-language-server | npm i -g vscode-langservers-extracted |
| YAML | yaml-language-server | npm i -g yaml-language-server |
A rename or code action the agent applies writes only inside the project. An edit naming a file anywhere else, or inside a fetched repository, is refused whole. Otherwise every file is written or none is, and the result says which edits didn’t fit their file and which file operations, such as creating or renaming one, it left undone.
/lsp shows which servers are running, and luxe doctor starts each one to check it works. Luxe
never installs a server itself. When one is missing, the result tells the agent the command that
installs it, which runs through bash under your rules like any other.
A server the project installed
Luxe prefers the copy a project installed for itself, in its node_modules/.bin or virtual
environment, since that version matches the project. The same goes for formatters such as ruff
and biome, and for MCP servers a project declares. Runs with nobody watching, such as
--headless and the loop, use only what you installed yourself unless you pass --trust-project.
To decide for every run, in your own config:
{ "lsp": { "project_servers": "always" } }auto (the default) uses a project’s servers when you are watching, always uses them every time,
and never uses only yours. A project’s own config cannot change this.
A language server runs your project’s build tooling. rust-analyzer runs build.rs and procedural
macros, and a TypeScript server loads tsconfig.json plugins. For a repository you do not trust,
use a container rather than relying on this setting.
Add a language server
{
"ruby": {
"command": "ruby-lsp",
"extensions": ["rb"],
"root_markers": ["Gemfile"],
"install_hint": "gem install ruby-lsp"
}
}An entry for a language Luxe already knows changes only what it names, so
{ "rust": { "command": "rust-analyzer-nightly" } } swaps the binary and keeps the rest.
alternatives lists other servers to try, in order, when the first will not start.
Debugger
The debug tool drives a real debugger, so the agent can set breakpoints, step through code and
inspect variables rather than add print statements. After three failing test runs in a row, Luxe
suggests it. LUXE_DEBUG_NUDGE=0 turns that suggestion off.
| Language | Adapter |
|---|---|
| Python | debugpy (pip install debugpy) |
| Rust, C, C++ | CodeLLDB, lldb-dap or gdb |
| Go | Delve (dlv) |
/debug shows which adapters are installed and any running sessions. To add one, reuse the launch
style of a built-in adapter (debugpy, lldb, gdb or delve). This one debugs Zig programs
with lldb-dap:
[
{
"id": "zig",
"command": ["lldb-dap"],
"extensions": ["zig"],
"dialect": "lldb"
}
]transport is stdio (the default) or tcp_dial_in, for an adapter that connects back to Luxe:
put ${DAP_ADDR} in its command where it takes the address, as Delve’s
dlv dap --client-addr ${DAP_ADDR} does, and Luxe replaces it with host:port.
addr_placeholder names a different placeholder.
A breakpoint the adapter could not set is listed under the launch, with the adapter’s reason. An adapter that fails to start says why, with what it printed.
Web
web_search works without a key. To use a particular search provider, set its key; the first one
set is used:
| Key | Provider |
|---|---|
| none | Exa, through its public endpoint |
BRAVE_API_KEY | Brave |
TAVILY_API_KEY | Tavily |
EXA_API_KEY | Exa, on your own account |
FIRECRAWL_API_KEY | Firecrawl |
web_fetch reads a page as markdown. If a page refuses, it tries again as a browser, and then, with
FIRECRAWL_API_KEY set, in a real browser for pages built with JavaScript.
web_fetch and screenshot reach the internet and your own machine (localhost), so your dev
server works. Other machines on your network, cloud metadata addresses and file:// are refused,
including through an IPv6 address that carries one of them. A page elsewhere can’t redirect a fetch
to your machine, and a fetch reads at most 10 MB. screenshot loads only the host you asked for, so
a redirect, frame or image from any other host fails to load, and it runs the browser on a fresh
profile, sandboxed unless Luxe runs as root or in a container. LUXE_WEB_ALLOW_PRIVATE=1 lifts the
address checks, allows file:// in screenshot, and lets it load other hosts. A fetch gives up
after 30 seconds, or LUXE_WEB_TIMEOUT_SECS, within the call’s 60-second limit.
A search provider’s other features, such as crawling a site, are available through its MCP server; see MCP.
GitHub repositories
Given a repository address, such as https://github.com/owner/repo, web_fetch clones it instead
of reading the page, so the agent can search the real files. The clone is shallow, the latest commit
only, and goes into .luxe/gh-cache/owner/repo in your project, which git ignores. Reading it never
asks permission, because it is inside the project.
A repository you did not write sits inside one you did, so Luxe keeps it inert:
- Symbolic links are checked out as plain files holding the link’s target. A link cannot point the
agent at your
.env, or anywhere else outside the clone. - Git never prompts. A private repository needs your git credentials for github.com, for example
from
gh auth setup-git. - Git LFS files, submodules and tags are not downloaded.
- Language servers and format-on-save do not start there, because rust-analyzer would run the
repository’s
build.rs. - Nothing in it runs unless the agent runs it with
bash, under your permission rules as usual.
A clone over 500 MB on disk, or one that takes longer than 45 seconds, is stopped and removed.
Fetching a clone more than a day old clones it again, and a clone nobody has fetched in 14 days is
deleted. To clear them all, delete .luxe/gh-cache/.
The files are still text the agent reads, and text can carry instructions, so treat a clone like any other page from the web. To read repositories as web pages instead:
{ "tools": { "clone_repositories": false } }