# light-tools **Better file and shell tools for coding agents. Read less, write less, waste fewer turns.** Your coding agent should not need to read a 2,000-line file to change 5 lines. It should not need five separate tool calls to read five files it already knows it needs. And it should not dump thousands of log lines into context just because a command was noisy. `light-tools` replaces the basic file and shell tools used by coding agents with versions designed for AI work. It speaks MCP, runs as one Go binary, and needs no daemon or database. ## Install ```sh npm install --global @factor-i-o/light-tools ``` Requires Node 18.17+ and npm 10+. Or build it directly: ```sh go install -tags treesitter github.com/icediceice/light-tools/cmd/light-tools@latest ``` Then register it with your agent and restart: ```sh light-tools init --client claude ``` `--client antigravity` and `--client print` are also supported. `light-tools` is a stdio server started with the session, so there is no daemon to manage. Full procedure: [AGENT-SETUP.md](AGENT-SETUP.md). > **Important:** do not disable your agent's native file and terminal tools until you have confirmed `light-tools` starts correctly and you have a fallback. An agent with no native tools may be unable to repair its own setup. ## What changes? ### Read several files in one go If the agent needs code from several files, it can read the relevant parts together in one call instead of fetching them one by one. Large files are paged, with an exact `[CONTINUE]` cursor when more content exists. A single-path read — one file window or one named-symbol read — arrives in whichever shape is smaller: plain text with a one-line summary (hash, next page, size), or the JSON envelope when that wins, and it carries the full declaration — signature, doc comment and exact source span. Multi-file batch reads use a compact plain grammar and skip that comparison. ### Change only the code that changed Many old/new replacement tools make the model output both the code being replaced **and** the replacement. `light_file` can edit by span: point at the lines or symbol, then send only the new content — roughly half the edit payload when the two blocks are a similar size, on the more expensive output side. Every file mutation is snapshotted first, so a bad edit can be restored. ### Do not send the same file twice If the agent reads a file again and the content has not changed, `light_file` returns a short dedup notice instead of the same bytes — and the notice names `force:true`, the one-call way back to the bytes if the agent no longer holds them. If the file changed, it is sent normally. ### Collapse repetitive command output Sixteen service restarts, buried in lines that differ only by a counter: ```text … systemd[2158]: light-edge.service: Scheduled restart job, restart counter is at 599. … systemd[2158]: light-edge.service: Scheduled restart job, restart counter is at 600. (14 more, each identical but for the number) ``` becomes: ```text [L1-16] light-edge.service: Scheduled restart job, restart counter is at ▪1. ×16 ▪1: 599..614 (16 values, +1 each) ``` No two of those lines are byte-identical, so deduplication collapses nothing. Grouping by *shape* is what turns sixteen buried restarts into a stated range. Whatever occurred only once is still there. 500 compile lines and a verdict: ```text [L1-500] compiling module ▪1 of 500 ×500 ▪1: 0..499 (500 values, +1 each) [L501] BUILD FAILED ``` A verdict is a singleton by nature — it is the line you came for, and it survives verbatim. `light_bash`, `light_ssh`, `light_ops` and `light_file` (file read windows) all do this. Both samples above are generated by `go test ./internal/logs/`, not written by hand. ### Keep oversized output out of context When a stream is compacted, or is too large to return whole, the exact bytes go to an indexed spill and the result carries a ready-made `read_block` call to recover them. Elision always implies recovery: if no spill can back a view, the exact output comes back instead. `LIGHT_NO_COMPACT=1` restores the pre-compaction result shape exactly. The goal is not to hide information. It is to stop paying the context cost before the agent knows whether it needs that information. ## The tools | Tool | What it does | | --- | --- | | `light_file` | Read, search, inspect symbols and edit files without unnecessary full-file traffic | | `light_bash` | Run local commands with bounded, searchable output | | `light_ssh` | Run commands on a remote host through a named profile | | `light_scp` | Copy files to or from a remote host through a named profile | | `light_ops` | Read-only service, port and log inspection | All five register by default. You can withhold tools completely: ```sh light-tools --disable-tool light_ssh --disable-tool light_scp ``` A disabled tool is not registered, so the model cannot call it. ## Works with your existing code intelligence `light-tools` is not a replacement for code search, indexing, language servers or repository intelligence. Use whatever code-intelligence layer you prefer. `light-tools` handles a different part of the problem: **how the agent reads, writes and operates on the machine after it knows what it wants to do.** ## Safety and reliability Coding agents make ordinary mistakes: wrong field names, misspelled verbs, malformed values and overly broad shell commands. `light-tools` tries to make those mistakes cheap without silently guessing about dangerous mutations. - obvious argument mistakes can be repaired and reported back to the model - ambiguous or dangerous mutations are refused instead of guessed - file writes are snapshotted before mutation - disabled tools are not registered at all - errors point at the failing field or payload location and tell the caller what to fix - filesystem access can be limited with `allowed_roots` ### Mutation safety Modeled mutations are enumerated before execution whether they name explicit paths (`rm a.tmp b.tmp`) or an unquoted glob (`rm *.tmp`). When the whole surface can be durably captured, the command runs on first contact and returns a working `vault_restore` handle. A surface that cannot be protected still runs but reports why — except an unprotectable unquoted glob, the one case that refuses outright, naming the blocker and binding an unbacked retry to that exact expanded surface with a digest. **Confinement is not a shell sandbox.** `allowed_roots` bounds `light_file` paths, local SCP endpoints and caller-supplied `light_ops` paths. `light_bash` has its working directory bounded, but the commands it runs can still reach outside that directory. Read [SECURITY.md](SECURITY.md) before treating confinement as a security boundary. Exact argument-repair, continuation, spill, snapshot and confinement semantics live in [docs/REFERENCE.md](docs/REFERENCE.md). ## Measurement There is a reproducible benchmark in this repository. It measures one thing: for a fixed question, how many bytes have to be delivered into a model's context to answer it — with `light-tools`, and without. ```sh go test -tags treesitter ./internal/bench/ -run TestBenchmarkReport -update ``` Ten scenarios across two tracks, three arms each: `native-naive` (return the whole file, the whole stream), `native-skilled` (grep first, then read a window), and `light-tools` as it ships. Every scenario carries **one pattern per clause of its question** — *which file broke, and what was the error* is two facts, and both must survive in what the arm delivered. An unmarked row that loses a clause fails the suite rather than becoming a headline. Two exceptions are named explicitly: a row marked a **known loss** must keep failing to answer and must still carry a pointer to the exact bytes, and a **repeat-read** row proves the first read carried the answer instead. Against a **naive** baseline the reductions are large: a 2.0 MB access log to 570 B, a 339.9 KB journal to 606 B, a 19.3 KB source file to a 704 B symbol. Against a **skilled** baseline the picture is mixed, and that is the honest number: | | Log reading | Code reading | | --- | :---: | :---: | | Rows where **both** arms answered | 2 of 5 | 4 of 5 | | ├ light-tools delivered less | 1 | 2 | | └ light-tools delivered more | 1 | 2 | | Skilled baseline did not answer | 2 | 0 | | light-tools did not answer | 1 | 0 | | Held out (repeat read, different baseline) | 0 | 1 | Only rows where both arms answered are compared on bytes. A baseline that missed the question is **not** counted as a byte win for `light-tools` — being answered by one tool and not the other is a capability difference, and inflating the byte tally with it would be the same trick this benchmark exists to avoid. Where a question already names the string it is looking for, `grep` is the right tool and wins. Compaction earns its place in the other case — when you cannot name the string yet, because you do not know what is in the log. Round trips are counted separately: several rows where `light-tools` sends more bytes still answer in one call where the baseline needs two, or six. The benchmark also reports a case `light-tools` **loses**. Template collapse summarises each variable slot independently, so a rare correlation across two slots is lost: shown 20,000 access-log lines, the view states that a `500` occurred and that several paths exist, but not which path returned it. That row is in the table, marked, with the mechanism explained, and its assertion is inverted so the limitation cannot go stale unnoticed. Full results, methodology and limitations — including that the corpora are synthetic and that this measures delivered context, not task success — are in **[docs/BENCHMARK.md](docs/BENCHMARK.md)**. ### Local counters Separately, `light-tools` counts its own savings locally: terse output tokens, read-dedup bytes, write bytes versus a full rewrite, and — new in v0.4.0 — the bytes handed to output compaction against the bytes handed back. A local sample over **45 tool calls** in three throwaway sessions on this repository recorded 12,194 terse-output tokens, 164,087 B of read dedup and 116,363 B saved against a full rewrite. Those are self-reported one-sided counters with no baseline arm: they say what was saved relative to this tool's own alternative path, and they cannot support a with/without claim. The benchmark above is the measurement to quote. A small sample is not a benchmark or an extrapolated rate. For scale, the broader Light stack — a larger, separate deployment using the same targeting and output-reduction approach — delivered about **84% less** of its considered corpus into model context across 319K tool calls, with partial instrumentation: corpus size measured on 36.8% of calls and delivered size on 15.2%. That is a Light-stack measurement, not a `light-tools` one. Local telemetry is aggregate-only: no paths, arguments, commands, hostnames or usernames are sent anywhere. Disable it with `DO_NOT_TRACK=1` or a non-empty `LIGHT_NO_TELEMETRY`. ## Platforms | OS | amd64 | arm64 | Symbol extraction | | --- | --- | --- | --- | | Linux | native | native | tree-sitter | | macOS | native | native | tree-sitter | | Windows | native | native | tree-sitter on amd64; no-symbol fallback on arm64 | Windows ARM64 is built without CGo. All five tools still work; only symbol and outline extraction degrade. ## Other install options Checksum-verifying installers are also available from published GitHub releases: ```sh curl -fsSL https://raw.githubusercontent.com/icediceice/light-tools/main/install.sh | sh ``` ```powershell Invoke-WebRequest https://raw.githubusercontent.com/icediceice/light-tools/main/install.ps1 -OutFile install.ps1 ./install.ps1 ``` Pin or relocate with `-Version` / `-Destination` on PowerShell or `LIGHT_TOOLS_VERSION` / `LIGHT_TOOLS_INSTALL_DIR` on POSIX. Both require an exact asset entry in `checksums.txt`. ## More - [AGENT-SETUP.md](AGENT-SETUP.md) — full setup procedure - [SECURITY.md](SECURITY.md) — what is and is not protected - [docs/REFERENCE.md](docs/REFERENCE.md) — exact tool and verb semantics - [RELEASING.md](RELEASING.md) — release process --- Created by **[Factor I/O Studio](https://studio.factor-io.com)**. `light-tools` is one component of the Light stack, extracted to stand alone. GPL-3.0-or-later.