local-code-worker

module
v0.0.0-...-974e3f2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT

README

local-code-worker

A local MCP server that lets a cloud coding agent (Claude Code, Codex, Cursor) hand bounded, mechanical code generation to a model running on your own machine, then review the result as a plain diff before anything touches the repository.

The cloud model keeps the thinking: it decides what to change, picks the exact files, reviews the diff and validates the result. The local model does the typing. Output tokens are the slow and expensive part of generation, so moving predictable code out of the cloud saves money and time without giving up control.

cloud agent ──request_changes──▶ local-code-worker ──▶ LM Studio (local model)
     ▲                                  │
     └──────── unified diff ◀───────────┘   repository untouched
cloud agent ──apply / revise / decline──▶ local-code-worker ──▶ repository

Why

Cloud agents are great at deciding what code should exist, and poor value for money when they spend thousands of tokens typing boilerplate, mappings, switch cases or a small change inside a large file. This worker hands that typing to a local model and returns a deterministic diff the cloud agent can review in a few hundred tokens.

The local model never gets filesystem, shell, search or git access. It only sees numbered in-memory text buffers and answers with line edits:

edit_lines(path, start_line, delete_count, content)

The worker applies those edits to a virtual copy, builds the candidate, diffs it against the original snapshot and keeps it in memory. The real repository changes only when the cloud agent calls apply_changes.

Results

Measured on a 40-task evaluation with qwen3.8-27b-mlx through LM Studio on Apple Silicon. These numbers describe this worker, model and protocol on mechanical coding tasks, not a universal benchmark.

Metric Value
Overall pass 33/40 (82.5%)
First-pass 27/40 (67.5%)
Single-file edits 92.6%
Multi-file edits 50%
File creation / deletion 100%
Median / p90 generation 6.5 s / 16.5 s

Line edits instead of full-file rewrites are what make it worth it: a small change in a ~1,400-line file took ~1.1k completion tokens and ~96 s instead of ~12.5k tokens and ~865 s for regenerating the whole file.

Full numbers, failure modes and routing advice: skills/local-code-worker/references/routing.md.

What to delegate

Route Tasks
Delegate one-file mechanical edits, a small change in a large file, enum/status mappings, switch and table additions, adapters, DTOs, boilerplate, predictable tests, explicit create/delete, refactors with a fixed design
With caution 2-3 tightly coupled files (implementation + test), moderate logic with an exact spec, concrete revisions
Keep in the cloud architecture, concurrency or security design, ambiguous debugging, broad refactors, project-wide discovery, changes too small to be worth a round trip

The agent-facing version of this guidance is the skill in skills/local-code-worker/SKILL.md. Point your agent at it (or install it as a skill) so it knows when delegation pays off.

Setup on a Mac (step by step)

Tested on Apple Silicon with LM Studio and an MLX model. A 27B model in 8-bit needs roughly 30 GB of free unified memory; pick a smaller coding model if your Mac has less.

1. Install Go and LM Studio
brew install go
brew install --cask lm-studio

Open LM Studio once so it finishes its first-run setup. It ships the lms command line tool. If lms is not on your PATH yet:

~/.lmstudio/bin/lms bootstrap
2. Download and load a model

In LM Studio, search for the model and download it (Discover tab), or use the CLI:

lms get qwen3.8-27b-mlx      # download
lms ls                       # list downloaded models and their keys
lms load qwen3.8-27b-mlx     # load it into memory
lms ps                       # confirm it is loaded

Then, in the model's settings in LM Studio, turn Reasoning Section Parsing OFF. With it on, Qwen's structured output is returned in reasoning_content, message.content stays empty, and the worker rejects the response. Details: docs/lm-studio.md.

You do not need to enable Structured Output in the UI; the worker sends a JSON Schema response_format with every call.

3. Start the LM Studio API server
lms server start             # serves http://127.0.0.1:1234

(Or Developer tab -> Start Server. Keep "Serve on Local Network" off.)

Check that the API answers and note the exact model id:

curl -s http://127.0.0.1:1234/v1/models

The id shown there is what you pass as -model below.

4. Build and run the worker
git clone https://github.com/valanced/local-code-worker.git
cd local-code-worker
go build -o bin/local-code-worker ./cmd/local-code-worker
./bin/local-code-worker -model qwen3.8-27b-mlx

Or install the binary straight into $(go env GOPATH)/bin:

go install github.com/valanced/local-code-worker/cmd/local-code-worker@latest
local-code-worker -model qwen3.8-27b-mlx

The daemon listens on http://127.0.0.1:7337/mcp. It starts even when LM Studio is down; the model is only needed when a generation runs. Check it:

curl -s http://127.0.0.1:7337/healthz   # -> ok
5. Keep it running in the background (optional)

The install script builds the binary into ~/.local/bin, writes a user LaunchAgent, starts it and waits for the health check. It also restarts the service after a rebuild, so rerun it after git pull.

scripts/install-launchd.sh
# different model or LM Studio URL:
LCW_MODEL=qwen/qwen3-coder-next scripts/install-launchd.sh
LCW_LM_STUDIO_URL=http://127.0.0.1:1234/v1 scripts/install-launchd.sh

scripts/status-launchd.sh
scripts/uninstall-launchd.sh

Logs: ~/Library/Logs/local-code-worker/. More: docs/macos-service.md.

6. Connect your agent

Claude Code:

claude mcp add --transport http --scope user local-code-worker http://127.0.0.1:7337/mcp

Codex (~/.codex/config.toml):

[mcp_servers.local-code-worker]
url = "http://127.0.0.1:7337/mcp"

Cursor (~/.cursor/mcp.json):

{ "mcpServers": { "local-code-worker": { "url": "http://127.0.0.1:7337/mcp" } } }

All clients share one daemon and one in-memory request store. Finally, give your agent skills/local-code-worker/SKILL.md (install it as a skill or reference it from your agent instructions) so it knows when to delegate.

7. Try it

Ask your agent for a small mechanical change, for example "add a String() method for this enum, delegate the code to local-code-worker". You should see request_changes return a diff, then apply_changes and record_outcome.

Troubleshooting
Symptom Fix
model is required / wrong model picked Pass -model with the exact id from /v1/models
Response has reasoning but no content Turn Reasoning Section Parsing OFF for the model
Output truncated Raise -max-tokens, or split the task
Timeouts on large edits Raise -timeout (default 5m)
health check failed after install tail ~/Library/Logs/local-code-worker/stderr.log

Tools

Tool What it does
request_changes Capture explicit read_files / write_files from repo_root, ask the local model for a candidate, return a unified diff. Creates a pending request. Repository untouched.
revise_changes Send concrete feedback on a pending candidate and get a new diff against the original snapshot. Limited by -max-revision-attempts.
apply_changes Re-verify every captured source by hash, then write the candidate. If anything changed on disk since capture, returns stale and writes nothing.
decline_changes Drop a pending candidate without touching the repository.
record_outcome After apply and validation, record accepted (files still match the candidate) or cloud_fixed (the cloud agent had to repair it), with line-level repair stats.

A typical session:

  1. The cloud agent investigates and decides the exact change and files.
  2. request_changes with a precise brief and explicit file lists.
  3. The agent reads the diff. Wrong in a concrete way: revise_changes with a specific correction. Fine: apply_changes. Hopeless: decline_changes.
  4. The agent runs the project's formatter and tests.
  5. record_outcome, then stop.

The full contract, including every status and edge case, is in docs/mcp-contract.md.

Configuration

Flag Default Meaning
-listen 127.0.0.1:7337 Listen address; loopback only
-mcp-path /mcp MCP Streamable HTTP path
-lm-studio-base-url http://127.0.0.1:1234/v1 LM Studio API; loopback only
-model auto Model id; required when LM Studio exposes more than one
-timeout 5m HTTP deadline for one generation
-max-tokens 16384 Completion cap per generation
-max-revision-attempts 3 Model calls allowed per request for revisions
-max-file-bytes / -max-total-bytes 0 (unlimited) Capture size limits
-request-ttl 24h Evict requests idle longer than this; 0 disables
-temperature, -top-p, -top-k, -presence-penalty, -repeat-penalty Qwen defaults Sampling

Safety model

  • Loopback only: the daemon refuses non-loopback listen addresses, the MCP SDK rejects non-localhost Host headers (DNS rebinding), and cross-origin browser requests are blocked.
  • No authentication: any local process can call it, and it can write inside any repo_root it is given. Run it only on a machine you trust.
  • Explicit scope: only listed files are read or written. Absolute paths, .. traversal and symlinks (file or any parent directory) are rejected.
  • Text only: non-UTF-8 files are rejected.
  • Nothing is written before apply_changes, and apply re-checks every source hash first. Writes are atomic per file (temp file + rename). A failure after verification can still leave earlier files of the same candidate written; V1 does not roll back.
  • Requests live in memory and are lost when the daemon restarts. File contents are kept only as long as a request needs them: sources until apply or decline, the applied candidate until record_outcome. Requests idle longer than -request-ttl (24h) are evicted.

Development

go build ./...
go vet ./...
go test ./...

cmd/lcw-*-smoke are manual smoke tools against a live LM Studio. Design notes: docs/architecture.md, docs/model-protocol.md, docs/model-prompt.md.

Status

V1, used daily by the author. Tested with one model on macOS. Other OpenAI-compatible local servers may work if they support JSON Schema response_format, but only LM Studio is tested.

License

MIT, see LICENSE.

Directories

Path Synopsis
cmd
lcw-lifecycle-smoke command
Command lcw-lifecycle-smoke runs RequestChanges, ReviseChanges, and ApplyChanges against LM Studio.
Command lcw-lifecycle-smoke runs RequestChanges, ReviseChanges, and ApplyChanges against LM Studio.
lcw-mcp-smoke command
Command lcw-mcp-smoke drives a running local-code-worker daemon over MCP.
Command lcw-mcp-smoke drives a running local-code-worker daemon over MCP.
lcw-model-smoke command
Command lcw-model-smoke sends one structured-output request to a local LM Studio server.
Command lcw-model-smoke sends one structured-output request to a local LM Studio server.
lcw-request-smoke command
Command lcw-request-smoke runs one live RequestChanges call against LM Studio.
Command lcw-request-smoke runs one live RequestChanges call against LM Studio.
lcw-revise-smoke command
Command lcw-revise-smoke runs RequestChanges and then ReviseChanges against LM Studio.
Command lcw-revise-smoke runs RequestChanges and then ReviseChanges against LM Studio.
lcw-vfs-smoke command
Command lcw-vfs-smoke runs one VFS request against LM Studio.
Command lcw-vfs-smoke runs one VFS request against LM Studio.
local-code-worker command
Command local-code-worker is the long-lived local MCP daemon.
Command local-code-worker is the long-lived local MCP daemon.
internal
diff
Package diff builds a deterministic unified diff from original writable file state to the current candidate.
Package diff builds a deterministic unified diff from original writable file state to the current candidate.
files
Package files is the only component that reads or writes a repository.
Package files is the only component that reads or writes a repository.
mcpserver
Package mcpserver adapts the worker service to MCP Streamable HTTP.
Package mcpserver adapts the worker service to MCP Streamable HTTP.
model
Package model holds local-model types and loopback LM Studio helpers.
Package model holds local-model types and loopback LM Studio helpers.
model/vfs
Package vfs edits virtual text buffers with one operation, edit_lines.
Package vfs edits virtual text buffers with one operation, edit_lines.
worker
Package worker is the in-memory request model and request orchestration.
Package worker is the in-memory request model and request orchestration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL