sous
The sous-chef for Claude's kitchen: delegate mechanical, volume-heavy coding tasks from Claude Code / Claude Desktop to a local MLX model on your Mac. Claude designs the menu; sous does the prep — in a sandboxed, auditable, autonomous tool loop. You (and Claude) review everything it cooks.
Why
Heavy Claude Code use runs into plan usage limits, and most of what consumes them is generated output. sous exists to stretch that budget: the mechanical, volume-heavy output comes from a local model at zero marginal cost, while Claude spends its much cheaper input-side attention writing instructions and reviewing the resulting diff. Claude stays the head chef, and the same plan carries further into the week.
This is a hybrid local + cloud arrangement, deliberately not an all-or-nothing switch to a local model: pointing Claude Code itself at a local endpoint trades away the frontier reasoning you are paying for, while sous offloads only the small, mechanical work your Apple silicon Mac can handle on its own.
How sous compares
Other ways to put local models next to Claude Code make a different trade:
- Full-local replacements point Claude Code (or a fork of it) at a local endpoint — every task drops to local-model quality, including the ones you wanted a frontier model for.
- Routers/proxies swap models per request, but the work still runs synchronously inside your session, and nothing sandboxes what the local model does to your files.
- Subagent/skill delegates hand tasks to a local model without a persistent queue, path confinement, command allowlisting, or an audit trail.
sous is the hybrid: Claude keeps the reasoning, and an asynchronous queue hands the mechanical work to a local worker that is sandboxed, budgeted, approval-gated, and journaled — with the diff always reviewed before it counts.
Requirements
- Apple silicon Mac (tested on M-series with 64 GB; the default model needs ~30 GB free unified memory — for 32 GB and 16 GB machines, see Smaller machines)
- Python 3.14 (standard build) via uv
- Claude Code or Claude Desktop with a Pro/Max plan
Install
uv tool install sous-mcp # PyPI package name; the CLI it installs is `sous`
sous install-launchd # start at login, keep alive (recommended)
claude mcp add --transport http sous http://127.0.0.1:8383/mcp
From a checkout, uv tool install . works instead of the PyPI package.
(The bare sous name on PyPI is an unrelated, abandoned placeholder — the
package you want is sous-mcp.)
Optionally install the delegation skill so Claude knows when and how to use it:
cp -r skills/delegating-to-local ~/.claude/skills/
First delegation downloads the model (~28.7 GB for the default) — one time.
What Claude gets
| Tool | Purpose |
|---|---|
delegate_task |
queue a self-contained task (returns immediately) |
task_status |
poll progress / queue position / approval requests |
task_result |
fetch report, changed files, verify output, diff |
cancel_task |
stop a queued or running task |
respond_to_command_request |
approve/deny a non-allowlisted command |
server_status |
model + queue + config health |
Configuration — ~/.sous/config.toml
[server]
port = 8383
[model]
id = "mlx-community/Qwen3.8-27B-mxfp8"
idle_unload_minutes = 30
max_context_tokens = 32768
temperature = 0.7
top_p = 0.8
top_k = 20
[budgets]
max_turns = 40
max_minutes = 15
max_tokens_per_generation = 4096
[commands]
allowlist = ["pytest", "python -m pytest", "npm test", "npx eslint",
"npx prettier", "ruff", "black", "mypy", "go test",
"cargo test", "cargo check", "make test"]
timeout_seconds = 120
approval_timeout_minutes = 10
[tasks]
retention = 200
Every value is optional; the allowlist is re-read on every command execution,
so edits apply instantly. Swap [model].id for any MLX text or vision model
(e.g. the -8bit/-4bit conversions, or a fast MoE coder via mlx-lm).
temperature/top_p/top_k control the worker's sampler (Qwen's own
documented non-thinking-mode defaults). Greedy decoding (temperature 0)
sounds safer but isn't: it gives the model no way to escape a bad
completion once it happens, since a near-identical prompt plus a nudge
still argmaxes to the same wrong output every time.
Smaller machines
The default model wants a 64 GB machine. Both alternatives below share the
default's qwen3_5 architecture, so they load through the exact same mlx-vlm
path — edit [model].id in ~/.sous/config.toml and the next delegation
downloads and uses them.
| Unified memory | [model].id |
Weights |
|---|---|---|
| 64 GB (default) | mlx-community/Qwen3.8-27B-mxfp8 |
~28.7 GB |
| 32 GB | mlx-community/Qwen3.8-27B-mxfp4 |
~15.2 GB |
| 16 GB | mlx-community/Qwen3.5-9B-MLX-4bit |
~6 GB |
The 32 GB pick is the same Qwen3.8-27B checkpoint at mxfp4 — same model at
half the footprint for a modest quality cost. The 16 GB pick drops to the 9B
tier because an 8-bit 9B (~11 GB) would crowd the ≈10.7 GB Metal working-set
limit of a 16 GB machine once the KV cache lands on top of the weights; on
16 GB, also consider [model].max_context_tokens = 16384 if you see memory
pressure. (mlx-community/Qwen3.5-9B-mxfp8/-mxfp4 look like the obvious
picks, but as of 2026-08 they are empty placeholder repos with no weights.)
Both alternatives passed the same worker-path validation as the default (see Validation status), run on the 64 GB test machine — which validates the models and quants through sous's whole stack, not the memory fit on physical 32 GB / 16 GB hardware (that remains arithmetic: weights plus KV-cache headroom). Smaller workers still fail more tasks in general and make reviewing the diff matter more — but a reviewed draft from a small local model costs your plan nothing.
Security model
- Workers are confined to the
project_rootof their task (symlink-resolved;.git/writes denied). The sous control directory (~/.sous/— config, allowlist, task db, transcripts) is never writable from inside the sandbox, and aproject_rootthat contains it is rejected outright. - No shell: commands run as argv (never through a shell), with an
environment scrubbed down to
PATH,HOME,LANG,LC_ALL,TERM,TMPDIR(anything else, including*_TOKEN/*_KEY/*_SECRET/*_PASSWORDvars, is stripped). Only allowlisted commands run without approval. - Non-allowlisted commands pause the task for explicit human approval
(auto-deny after
approval_timeout_minutes). - Path confinement bounds the worker's edits, not what an allowlisted
command can do. Allowlisting a command that executes repo-resident code —
any test runner (
pytest,npm test,make test, ...) — is equivalent to granting arbitrary local code execution over code the worker just wrote: the worker can write aconftest.pyor test file that does anything the command's process can (network egress, reading any user-readable file), and the allowlisted verify run executes it without approval. Calibrate the allowlist accordingly, and review diffs before trusting verify output. - There is no network sandbox: an allowlisted or human-approved command can
still reach the network if the command itself does (e.g.
npm testhitting a registry). Keep the allowlist narrow and review approval requests before saying yes. - Command timeouts kill the command's whole process group (SIGTERM, short
grace, SIGKILL) before file changes are audited — but a descendant that
double-forks and calls
setsid()escapes into a new session and survives the group kill. Closing that residual requires cgroup/OS-level confinement that macOS does not offer. - The before/after file audit around each command is stat-based:
(mtime_ns, size, ctime_ns)per file. mtime and size alone are forgeable by code the command executes (equal-length rewrite +os.utimerestore); ctime is what makes the audit tamper-resistant, because no userspace API can set it andos.utimeitself bumps it. That resistance has limits: a process running as root (e.g. via a mount trick or raw-device write) or manipulation of the system clock between the two snapshots could still hide a change. The audit is a safety net against the sandboxed worker and the code it runs — not against a privileged attacker. - Every worker turn is journaled to
~/.sous/tasks/<id>/transcript.jsonl. - The MCP endpoint binds to 127.0.0.1 only.
Validation status
Validated end to end against the default model
(mlx-community/Qwen3.8-27B-mxfp8) on an M5 Pro / 64 GB:
- Worker path — a delegated "add type hints and docstrings" task completed
in 57s over 3 turns (
done/completed). The worker edited the file, chose to runpytestto check itself, and reported accurately; an independent re-run of the tests confirmed it. - MCP surface — driven by a real MCP client over streamable HTTP, the same
path Claude Code uses: all six tools registered with the expected names, and
a delegated task ran to
done/completedin 42s with a correct diff and verify output. - Smaller-machine models — both Smaller machines
picks passed the same worker-path check on the same machine (delegated
type-hints task, worker self-verified with
pytest, independent re-run confirmed, accurate report):Qwen3.8-27B-mxfp4in 35s over 4 turns — confirming mlx-vlm loads mxfp4-mode quants — andQwen3.5-9B-MLX-4bitin 25s over 8 turns.
Tool-call parsing accepts both the XML-ish <function=…>/<parameter=…> format
that Qwen3 emits and the hermes JSON format used by other MLX models.
Limitations
- MLX generation cannot be aborted mid-stream. Generations are serialized by a per-engine lock (and the engine is never idle-unloaded while one is in flight), so a truly wedged generation delays subsequent tasks until the daemon is restarted. Running the worker in a separate process (process isolation) is the future fix.
scripts/e2e_smoke.pyuses a 0.6B model so it stays cheap to run. That model is too small to reliably emit afinishcall, so the script usually endsfailedorbudget-exhaustedeven when it writes the right file — it exercises the plumbing, not model competence. The real-model runs above are the meaningful end-to-end evidence.
Development
Setup, the checks CI runs, and the pull-request process are in CONTRIBUTING.md.
Manual E2E with the real model: sous serve, register with claude mcp add,
then ask Claude to delegate something trivial and watch sous status.
Design spec: docs/superpowers/specs/2026-08-14-sous-design.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sous_mcp-0.1.0.tar.gz.
File metadata
- Download URL: sous_mcp-0.1.0.tar.gz
- Upload date:
- Size: 180.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9e8fe43de6e4056021900d82a9f2dec7fbd6705fa2b9415bcecc7f4a9501851
|
|
| MD5 |
55139418f9358487738d45b133011b95
|
|
| BLAKE2b-256 |
d21986288b4ca9afb915ab5448f43cfd9945f5de1393a31913c238c95c0f5e32
|
Provenance
The following attestation bundles were made for sous_mcp-0.1.0.tar.gz:
Publisher:
ci.yml on krcm0209/sous
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sous_mcp-0.1.0.tar.gz -
Subject digest:
a9e8fe43de6e4056021900d82a9f2dec7fbd6705fa2b9415bcecc7f4a9501851 - Sigstore transparency entry: 2482426475
- Sigstore integration time:
-
Permalink:
krcm0209/sous@49717c50e63470cc82ff4bb37b200ea6830b1bb9 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/krcm0209
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@49717c50e63470cc82ff4bb37b200ea6830b1bb9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sous_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sous_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 39.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90fd69ba4e7b3fe6fb558c0e5e087b8bdbafae774636d1061db6949537c071f6
|
|
| MD5 |
e9b07c1ef72356b4ed2e6b0d8684a0fb
|
|
| BLAKE2b-256 |
c11d3e5780c3a2ec0ef720706478520149299932fffded9d6f47644aebaa23d6
|
Provenance
The following attestation bundles were made for sous_mcp-0.1.0-py3-none-any.whl:
Publisher:
ci.yml on krcm0209/sous
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sous_mcp-0.1.0-py3-none-any.whl -
Subject digest:
90fd69ba4e7b3fe6fb558c0e5e087b8bdbafae774636d1061db6949537c071f6 - Sigstore transparency entry: 2482426554
- Sigstore integration time:
-
Permalink:
krcm0209/sous@49717c50e63470cc82ff4bb37b200ea6830b1bb9 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/krcm0209
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@49717c50e63470cc82ff4bb37b200ea6830b1bb9 -
Trigger Event:
push
-
Statement type: