Sandboxed Claude-Code-style toolkit for local models: orient, grep, surgically edit, run and watch processes, read git, fetch docs, and track a checklist inside one folder. Every capability is off by default.
Sandboxed Claude-Code-style toolkit for local models: orient, grep, surgically edit, run and watch processes, read git, fetch docs, and track a checklist inside one folder. Every capability is off by default.
Sandboxed Claude-Code-style toolkit for local models: orient, grep, surgically edit, run and watch processes, read git, fetch docs, and track a checklist inside one folder. Every capability is off by default.
Sandboxed Claude-Code-style toolkit for local models: orient, grep, surgically edit, run and watch processes, read git, fetch docs, and track a checklist inside one folder. Every capability is off by default.
Turns a local LM Studio model (built and tested against google/gemma-4-e4b, 7.5B) into something
close to a coding agent: it can orient itself in a repo, grep it, edit files surgically, run and watch
processes, read git history, fetch docs, and track its own checklist — all inside one folder you
choose, with every capability behind a switch that is off by default.
Tool surface by configuration
The model only sees tools whose capability is enabled, so the list stays small until you widen it.
Twelve tools is a comfortable working set for a 7.5B model; forty-two is not. Enable what the job
needs and leave the rest off.
An LM Studio tools provider plugin. It gives any local model in LM Studio hands on a single
folder: list, read, search, optionally write files, and optionally run shell commands.
Every filesystem path the model supplies is resolved against the workspace root and rejected if it
escapes it, so a model cannot wander out of the folder you point it at.
Tools
Tool
Always on?
What it does
workspace-tools
Turns a local LM Studio model (built and tested against google/gemma-4-e4b, 7.5B) into something
close to a coding agent: it can orient itself in a repo, grep it, edit files surgically, run and watch
processes, read git history, fetch docs, and track its own checklist — all inside one folder you
choose, with every capability behind a switch that is off by default.
Tool surface by configuration
The model only sees tools whose capability is enabled, so the list stays small until you widen it.
Twelve tools is a comfortable working set for a 7.5B model; forty-two is not. Enable what the job
needs and leave the rest off.
An LM Studio tools provider plugin. It gives any local model in LM Studio hands on a single
folder: list, read, search, optionally write files, and optionally run shell commands.
Every filesystem path the model supplies is resolved against the workspace root and rejected if it
escapes it, so a model cannot wander out of the folder you point it at.
Tools
Tool
Always on?
What it does
list_directory
yes
Lists entries at a path, with file sizes.
read_file
yes
Reads a text file, truncated at the configured size limit.
search_files
yes
Recursive case-insensitive text search, returns path:line: text. Skips node_modules, .git, dist, etc. Caps at 100 matches.
write_file
only if Allow writing files
Writes a file, creating parent directories. Overwrites.
run_command
only if Allow running shell commands
Runs a command with the workspace root as cwd, with a timeout.
run_until_success
only if Allow running shell commands
Retries a command until it exits 0 or hits the attempt cap, with a delay between tries. Returns the last failure so the model can fix the cause.
start_loop / check_loop / list_loops / stop_loop
only if Allow shellandAllow background interval loops
Background timer that re-runs a command every N seconds across messages, buffering the last 10 results.
set_tasks / get_tasks / update_task
yes
Persistent checklist. The model plans steps, marks them in_progress / done / blocked, and can resume from it in a later message.
Orientation. project_overview detects the stack, scripts, git branch and layout in one call — what the model should run first in an unfamiliar folder.
glob_files / grep / find_file
yes
Real discovery. grep takes a JavaScript regex with context lines and three output modes; glob_files supports **, *, ? and {a,b}, newest first.
Surgical editing. edit_file refuses an ambiguous match and tells the model to add context; multi_edit is all-or-nothing; preview_edit is a dry run. Every mutation snapshots first.
Dev servers and watchers that outlive a tool call. Output is a ring buffer; reads are incremental so polling does not re-send the same lines.
fetch_url / web_search
only if Allow web access
Reads real docs instead of hallucinating an API. Fetched page text is labelled as untrusted data in the result.
verify
only if Allow shell
Detects and runs this project's typecheck, lint, build and test commands, then reports only the lines that matter. Closes the edit → check → fix loop without the model having to know the command.
undo_last_edit
only if Allow writing files
Restores the most recently modified file from its automatic backup.
apply_patch
only if Allow writing files
Applies a unified diff, all hunks or none. Tolerates drifted line numbers, refuses mismatched context.
remember / recall
recall always; remember needs write
Durable project notes in .lmstudio-notes.md, so a convention learned today survives into next week's session.
changed_files
yes
What the model has modified this session, derived from the backup trail.
Subagents
run_subagent delegates a job to a second prediction against the same loaded model, with its own
fresh context and a narrowed tool set, and returns only its final report. The point is context
isolation: a subagent can burn twenty tool calls crawling a codebase and hand back five lines, which
matters enormously when the main model has a small context window.
Type
Powers
Tools
For
explorer
read only, no shell
11
"where is X implemented, and how does it work"
reviewer
read + git history
17
"review this file and list real problems"
verifier
read + shell
17
"run the tests and tell me exactly what fails"
worker
read + write + shell
27
one small, well-defined change, carried out end to end
The scoping is structural, not advisory. A subagent's tools are built by the same factories the main
provider uses, but against a Workspace whose capability flags have been turned off — so a read-only
subagent is not asked to avoid editing, it is handed no edit tools at all. No type ever receives
run_subagent, so subagents cannot spawn subagents.
Guard rails: one subagent at a time, a step ceiling (config, hard max 25), a refusal if the task is
too short to stand alone, and a refusal if the requested type needs a capability you have switched
off. Each round is a full local prediction, so this trades wall-clock time for context.
Which model a subagent runs on. The same one you are chatting with, in the normal case. The SDK's
no-argument llm.model() returns any loaded model and will happily load one that was never asked
for, so it is deliberately not used: with Subagent model set, that key is used; with it empty, the
subagent takes a model from the already-loaded list and never triggers a load. Either way the report
names the model it ran on, so a wrong pick is visible rather than silent. Set the field explicitly if
you keep more than one model loaded.
The subagent also inherits the parent generation's abort signal, so stopping the chat stops the
subagent instead of leaving it running against your GPU.
Why verify matters more than it looks
A local model will happily edit five files and announce success without ever running anything. verify
removes the excuse: it reads package.json scripts (picking npm/pnpm/yarn/bun from the lockfile),
Cargo.toml, go.mod, pyproject.toml or a Makefile, runs what it finds, stops at the first
failure, and returns the error lines rather than a thousand lines of build spam. The operating brief
tells the model to call it after every batch of edits and to keep fixing until it is green.
Auto-compaction
Long conversations lose their beginning: once the chat exceeds the model's context window, the host
drops the oldest turns and the model forgets decisions it made an hour ago.
A plugin cannot delete or rewrite earlier turns — a prompt preprocessor may only change the newest
user message. So compaction works the other way round. When the conversation passes a configurable
share of the context window (default 75%), the plugin summarises the older turns and attaches that
summary to each new message. As the raw turns fall out of the window, the summary stays, because it
now lives at the end of the conversation rather than the beginning.
The summary is deliberately factual: what the user is building, decisions that must not be revisited,
files changed with paths, commands run and what they reported, and what is still outstanding.
Cost control matters here, because each refresh is a real generation on your local model:
Nothing happens at all below the trigger percentage.
The summary is rebuilt only after the conversation grows by another 8 messages.
Overlapping refreshes are impossible; a second one cannot start while the first is running.
The most recent 6 turns are never summarised — they are still present verbatim.
Every failure path is inert: if the model cannot count tokens, the summariser errors, or history is
unavailable, the turn proceeds exactly as if compaction were switched off.
Settings: Auto-compact long conversations (on) and Compact at (% of context) (75).
The prompt preprocessor
Registered alongside the tools, this is the piece that makes a small model behave like a coding
agent rather than merely having the buttons. It prepends a <workspace-context> block to each
message containing a tight operating brief (orient first, read before editing, prefer edit_file
over rewriting, verify after changing, keep the checklist current) plus live facts: workspace root,
git branch and dirty state (read straight from .git, never by spawning git), detected project type,
the current checklist, and the head of AGENTS.md / CLAUDE.md if present.
It is cached for ~30s per root, wrapped so a failure can never cost you your message, and switched
off with Inject workspace context.
The three loop shapes
Retry loop — run_until_success. Tight inner loop inside one tool call. Attempt count is
capped by Max retry attempts regardless of what the model asks for.
Interval loop — start_loop. Survives between messages, ticks on a timer, fires once
immediately so the model gets a first result without waiting a full interval. Bounded by
Minimum loop interval, a max_runs argument (hard ceiling 200), and a limit of 5 concurrent
loops. Loops die when the plugin process stops.
Task loop — set_tasks / update_task. Not a timer at all; it is durable state so a long job
survives across turns. Written to .lmstudio-tasks.json in the workspace root only if
Allow writing files is on — otherwise it lives in plugin memory, so the write toggle is never
quietly bypassed.
Settings
Configured per-chat in LM Studio's plugin settings panel:
Workspace root — absolute path the model may touch. Empty = the chat's working directory.
Allow writing files — default off.
Allow running shell commands — default off. Turning this on lets the model execute arbitrary
commands as your user account. Only enable it for folders you would hand to a junior with sudo-less
shell access.
Max file read size (KB) — default 128. Also caps files scanned by search_files and the
output buffer of run_command.
Command timeout (seconds) — default 30.
Max retry attempts — default 5. Ceiling for run_until_success.
Allow background interval loops — default off. Requires shell access too; with shell off, the
loop tools are not registered at all.
Minimum loop interval (seconds) — default 60. Floor on start_loop, so a model cannot
busy-spin your machine.
Running it
Development (plugin lives only while this runs, hot-reloads on save):
cd C:/Users/Admin/lmstudio-plugins/workspace-tools && npx lms dev
Install permanently into LM Studio / publish to your LM Studio account:
cd C:/Users/Admin/lmstudio-plugins/workspace-tools && npx lms push
Typecheck:
cd C:/Users/Admin/lmstudio-plugins/workspace-tools && npx tsc --noEmit
Notes
package.json must not set "type": "module" — LM Studio bundles the plugin as CommonJS and
a module-typed package makes the generated entrypoint fail with
ReferenceError: require is not defined in ES module scope.
The model only sees tools that are enabled, so flipping Allow shell off mid-conversation
genuinely removes the capability rather than just discouraging it.
list_directory
yes
Lists entries at a path, with file sizes.
read_file
yes
Reads a text file, truncated at the configured size limit.
search_files
yes
Recursive case-insensitive text search, returns path:line: text. Skips node_modules, .git, dist, etc. Caps at 100 matches.
write_file
only if Allow writing files
Writes a file, creating parent directories. Overwrites.
run_command
only if Allow running shell commands
Runs a command with the workspace root as cwd, with a timeout.
run_until_success
only if Allow running shell commands
Retries a command until it exits 0 or hits the attempt cap, with a delay between tries. Returns the last failure so the model can fix the cause.
start_loop / check_loop / list_loops / stop_loop
only if Allow shellandAllow background interval loops
Background timer that re-runs a command every N seconds across messages, buffering the last 10 results.
set_tasks / get_tasks / update_task
yes
Persistent checklist. The model plans steps, marks them in_progress / done / blocked, and can resume from it in a later message.
Orientation. project_overview detects the stack, scripts, git branch and layout in one call — what the model should run first in an unfamiliar folder.
glob_files / grep / find_file
yes
Real discovery. grep takes a JavaScript regex with context lines and three output modes; glob_files supports **, *, ? and {a,b}, newest first.
Surgical editing. edit_file refuses an ambiguous match and tells the model to add context; multi_edit is all-or-nothing; preview_edit is a dry run. Every mutation snapshots first.
Dev servers and watchers that outlive a tool call. Output is a ring buffer; reads are incremental so polling does not re-send the same lines.
fetch_url / web_search
only if Allow web access
Reads real docs instead of hallucinating an API. Fetched page text is labelled as untrusted data in the result.
verify
only if Allow shell
Detects and runs this project's typecheck, lint, build and test commands, then reports only the lines that matter. Closes the edit → check → fix loop without the model having to know the command.
undo_last_edit
only if Allow writing files
Restores the most recently modified file from its automatic backup.
apply_patch
only if Allow writing files
Applies a unified diff, all hunks or none. Tolerates drifted line numbers, refuses mismatched context.
remember / recall
recall always; remember needs write
Durable project notes in .lmstudio-notes.md, so a convention learned today survives into next week's session.
changed_files
yes
What the model has modified this session, derived from the backup trail.
Subagents
run_subagent delegates a job to a second prediction against the same loaded model, with its own
fresh context and a narrowed tool set, and returns only its final report. The point is context
isolation: a subagent can burn twenty tool calls crawling a codebase and hand back five lines, which
matters enormously when the main model has a small context window.
Type
Powers
Tools
For
explorer
read only, no shell
11
"where is X implemented, and how does it work"
reviewer
read + git history
17
"review this file and list real problems"
verifier
read + shell
17
"run the tests and tell me exactly what fails"
worker
read + write + shell
27
one small, well-defined change, carried out end to end
The scoping is structural, not advisory. A subagent's tools are built by the same factories the main
provider uses, but against a Workspace whose capability flags have been turned off — so a read-only
subagent is not asked to avoid editing, it is handed no edit tools at all. No type ever receives
run_subagent, so subagents cannot spawn subagents.
Guard rails: one subagent at a time, a step ceiling (config, hard max 25), a refusal if the task is
too short to stand alone, and a refusal if the requested type needs a capability you have switched
off. Each round is a full local prediction, so this trades wall-clock time for context.
Which model a subagent runs on. The same one you are chatting with, in the normal case. The SDK's
no-argument llm.model() returns any loaded model and will happily load one that was never asked
for, so it is deliberately not used: with Subagent model set, that key is used; with it empty, the
subagent takes a model from the already-loaded list and never triggers a load. Either way the report
names the model it ran on, so a wrong pick is visible rather than silent. Set the field explicitly if
you keep more than one model loaded.
The subagent also inherits the parent generation's abort signal, so stopping the chat stops the
subagent instead of leaving it running against your GPU.
Why verify matters more than it looks
A local model will happily edit five files and announce success without ever running anything. verify
removes the excuse: it reads package.json scripts (picking npm/pnpm/yarn/bun from the lockfile),
Cargo.toml, go.mod, pyproject.toml or a Makefile, runs what it finds, stops at the first
failure, and returns the error lines rather than a thousand lines of build spam. The operating brief
tells the model to call it after every batch of edits and to keep fixing until it is green.
Auto-compaction
Long conversations lose their beginning: once the chat exceeds the model's context window, the host
drops the oldest turns and the model forgets decisions it made an hour ago.
A plugin cannot delete or rewrite earlier turns — a prompt preprocessor may only change the newest
user message. So compaction works the other way round. When the conversation passes a configurable
share of the context window (default 75%), the plugin summarises the older turns and attaches that
summary to each new message. As the raw turns fall out of the window, the summary stays, because it
now lives at the end of the conversation rather than the beginning.
The summary is deliberately factual: what the user is building, decisions that must not be revisited,
files changed with paths, commands run and what they reported, and what is still outstanding.
Cost control matters here, because each refresh is a real generation on your local model:
Nothing happens at all below the trigger percentage.
The summary is rebuilt only after the conversation grows by another 8 messages.
Overlapping refreshes are impossible; a second one cannot start while the first is running.
The most recent 6 turns are never summarised — they are still present verbatim.
Every failure path is inert: if the model cannot count tokens, the summariser errors, or history is
unavailable, the turn proceeds exactly as if compaction were switched off.
Settings: Auto-compact long conversations (on) and Compact at (% of context) (75).
The prompt preprocessor
Registered alongside the tools, this is the piece that makes a small model behave like a coding
agent rather than merely having the buttons. It prepends a <workspace-context> block to each
message containing a tight operating brief (orient first, read before editing, prefer edit_file
over rewriting, verify after changing, keep the checklist current) plus live facts: workspace root,
git branch and dirty state (read straight from .git, never by spawning git), detected project type,
the current checklist, and the head of AGENTS.md / CLAUDE.md if present.
It is cached for ~30s per root, wrapped so a failure can never cost you your message, and switched
off with Inject workspace context.
The three loop shapes
Retry loop — run_until_success. Tight inner loop inside one tool call. Attempt count is
capped by Max retry attempts regardless of what the model asks for.
Interval loop — start_loop. Survives between messages, ticks on a timer, fires once
immediately so the model gets a first result without waiting a full interval. Bounded by
Minimum loop interval, a max_runs argument (hard ceiling 200), and a limit of 5 concurrent
loops. Loops die when the plugin process stops.
Task loop — set_tasks / update_task. Not a timer at all; it is durable state so a long job
survives across turns. Written to .lmstudio-tasks.json in the workspace root only if
Allow writing files is on — otherwise it lives in plugin memory, so the write toggle is never
quietly bypassed.
Settings
Configured per-chat in LM Studio's plugin settings panel:
Workspace root — absolute path the model may touch. Empty = the chat's working directory.
Allow writing files — default off.
Allow running shell commands — default off. Turning this on lets the model execute arbitrary
commands as your user account. Only enable it for folders you would hand to a junior with sudo-less
shell access.
Max file read size (KB) — default 128. Also caps files scanned by search_files and the
output buffer of run_command.
Command timeout (seconds) — default 30.
Max retry attempts — default 5. Ceiling for run_until_success.
Allow background interval loops — default off. Requires shell access too; with shell off, the
loop tools are not registered at all.
Minimum loop interval (seconds) — default 60. Floor on start_loop, so a model cannot
busy-spin your machine.
Running it
Development (plugin lives only while this runs, hot-reloads on save):
cd C:/Users/Admin/lmstudio-plugins/workspace-tools && npx lms dev
Install permanently into LM Studio / publish to your LM Studio account:
cd C:/Users/Admin/lmstudio-plugins/workspace-tools && npx lms push
Typecheck:
cd C:/Users/Admin/lmstudio-plugins/workspace-tools && npx tsc --noEmit
Notes
package.json must not set "type": "module" — LM Studio bundles the plugin as CommonJS and
a module-typed package makes the generated entrypoint fail with
ReferenceError: require is not defined in ES module scope.
The model only sees tools that are enabled, so flipping Allow shell off mid-conversation
genuinely removes the capability rather than just discouraging it.