Delegate well-specified coding work to local LLMs, with quality gates at every level.
director (you + Claude Code) — writes the brief, reviews the plan, approves the report
manager (Qwen3.5-122B, vLLM) — expands the brief into a concrete step plan; audits results
intern (qwen3.5:9b, Ollama) — executes one step at a time, emitting full files
The economics: a plan detailed enough for an 8B executor is verbose but cheap to review. Errors get caught at plan time (costs a paragraph) instead of debug time (costs a session). The intern can't push back on ambiguity, so the manager prompt forces self-contained steps with exact signatures, concrete input→output examples, and machine-checkable acceptance criteria. The manager audits conformance ("did step N happen as written"); the director audits the decomposition ("is the plan sound").
When NOT to use this: if the plan would be as long as the code, skip the intern and write the code directly. The hierarchy pays only for mechanical, voluminous work (bulk refactors, test generation, applying a pattern across files).
Not code-only: any task that emits files with machine-checkable acceptance
qualifies — data files, markdown briefings, config. Use structural checks
(grep -c "## Section" out.md, python3 -c "import json; json.load(open('x.json'))")
for non-code outputs. The looser the checks, the more weight falls on audits and
director review.
Each step carries "executor": "intern" | "manager" (default intern). The decision
ladder: intern for mechanical, well-specified steps; manager for steps needing
real judgment (multi-constraint synthesis, conflicting sources, subtle logic) that are
still too voluminous to keep upstairs; the director writes it directly when the
plan would be as long as the output. Manager-executed steps skip the manager audit
(self-review is near-worthless) — the report flags them "director must review"
instead, so the human/Claude gate replaces the model gate.
The models stay tool-less; the harness runs searches. Steps may declare
"web_queries": [{"query": "kv cache quantization fp8", "fetch_top": 2}]Before the executor runs, the harness queries SearXNG (searxng.endpoint in config;
delete/empty to disable), injects top results (title/URL/snippet) and — for
fetch_top — full page text (HTML-stripped, truncated) into the executor's prompt.
Results count against the context budget; the raw evidence is saved to
.crew/web/step_<N>.json; the audit gets the source list so citation claims are
checkable. Search failure fails the step before the LLM call — never silently
research-less. Fetches of result URLs are guarded (http/https only, no
loopback/private hosts, bounded reads, text content-types, capped redirects).
Patterns that work well:
- Research brief: web_queries + a synthesis step (usually
executor: manager) producing a structured .md with a required## Sourcessection, checked viagrep -c. - Map-reduce over files: N steps each reading a chunk via
context_filesand emittingfindings/<n>.json, plus a final aggregation step. Bulk extraction and classification over a known file set is the crew's sweet spot; interactive "find where/why X" exploration is not — that needs tools the executors don't have.
- Python 3 (stdlib only — no pip installs)
- vLLM serving an OpenAI-compatible endpoint (default
localhost:8000) - Ollama (default
localhost:11434)
Endpoints, models, timeouts, token budgets and the check-binary allowlist live in
config.json.
mcp_server.py wraps crew.py as MCP tools so the crew sits in the assistant's
per-turn tool list instead of being a CLI it must remember:
claude mcp add --scope user localcrew -- python3 /path/to/LocalCrew/mcp_server.pyTools: crew_stats, crew_health, crew_skills, crew_plan, crew_run,
crew_run_status. crew_run starts the run detached and returns
immediately (console log in <ws>/.crew/run_console.log; poll
crew_run_status) — an MCP call never blocks for the minutes a run takes.
env_path_prepend prepends a directory to PATH so acceptance checks resolve
the project's python3/pytest (e.g. a conda env's bin) — the check allowlist
is exact-token and rejects absolute paths, so PATH is the sanctioned route.
The wrapper adds no logic and no auto-approval: plan review and independent
verification stay with the director. Stdlib-only, stdio transport.
Fill-in-the-blank brief templates live in examples/briefs/ (test generation,
bulk refactor, map-reduce extraction, research brief).
python3 crew.py stats # usage + success rates (global ledger)
python3 crew.py health # both endpoints answering?
python3 crew.py plan --brief brief.md --workspace ws/ # manager writes ws/plan.json
# -> DIRECTOR REVIEWS ws/plan.json (this gate decides everything)
python3 crew.py run --workspace ws/ # execute all steps
python3 crew.py run --workspace ws/ --resume # continue after a failed step
python3 crew.py run --workspace ws/ --step 2 # one step only
python3 crew.py run --workspace ws/ --dry-run # show what would run
python3 crew.py audit --workspace ws/ --step 2 # re-check + re-audit after manual fixesA run is fail-fast: any step that exhausts its retries or fails the manager audit stops the run and writes a report for the director.
Skills use Claude Code's native format — <dir>/<name>/SKILL.md with name:/
description: frontmatter — so one file serves the whole hierarchy: Claude Code
discovers workspace .claude/skills natively, and the crew reads the same files.
- Search dirs:
skills.dirsin config (default["{workspace}/.claude/skills"]).~/.claude/skillsis deliberately NOT a default — it's full of Claude-Code operational skills that would mislead an 8B intern. Opt in only if crew-safe. crew.py skills --workspace wslists the discovered catalog.- At
plantime the manager sees the catalog (names + descriptions) and may attach"skills": [...]per step — default zero, maxskills.max_per_step(2), only when the step's acceptance depends on it. Attached skills show in the plan summary with sizes so the director can veto over-attachment. - At
runtime the full skill bodies are injected into the intern's prompt and the manager's audit prompt; they share the context char budget with context files.
Content convention (not machine-enforced): crew-visible skills must be model-agnostic conventions/checklists — output formats, invariants, style rules. No Claude Code tool names, slash commands, or MCP references: an 8B intern will dutifully "follow" instructions it cannot execute.
When a run has trouble (any step retried or non-DONE), the manager makes ONE extra
call over the failure evidence (all intern attempts + retry feedback from this run's
run_log.jsonl, check tails, audit issues) and returns at most one skill proposal —
or null, the expected common case (step-specific problems, harness-absorbed defects,
and catalog duplicates must NOT become skills). Toggle: skills.auto_propose.
Proposals are staged in .crew/skill_proposals/<name>/SKILL.md — never written
to .claude/skills directly. One cat shows rationale + description + body
(rationale lives in frontmatter, so it's inert to discovery and prompt injection).
The director decides:
python3 crew.py propose-skill --workspace ws --step N # on-demand proposal
python3 crew.py approve-skill --workspace ws --name n # promote to .claude/skills
python3 crew.py approve-skill --workspace ws --name n --attach 1 # ...and attach to
# step 1 of plan.json, re-syncing the resume hash — then: run --resume
python3 crew.py reject-skill --workspace ws --name n # delete the staging dirrun_report.md lists pending proposals with paste-ready commands. The tool never
self-approves; a proposal-call failure never changes the run's exit status.
Plans are checked for deterministic dooms before any model runs:
- Context budget: each step's existing context files + attached skill bodies
must fit
context_char_budget. Violations reject the plan atplantime (the manager gets the errors as retry feedback) and refuserunat startup;approve-skill --attachwarns when an attachment pushes a step over. Files created by earlier steps are exempt (size unknown until run time). - Truncation awareness: when a backend reports it hit the output token limit
(
done_reason/finish_reason == "length"), retry feedback says so explicitly and asks for shorter output — a truncated file otherwise masquerades as a syntax error that retries can never fix. - No unverified facts: the planning prompt forbids schema/signature claims that aren't grounded in the brief or a context file. The harness can't check facts for you: grep-verify DDL and signatures before they enter a brief.
- Gather
context_files(missing file or blown char budget fails the step before any LLM call — a missing context file means a dependency broke). - Intern emits
{"files":[{"path","content"}], "notes"}(Ollamaformat: json). - Output validated before writing: paths must be within the step's
target_files, contents non-empty; violations are fed back as a retry. - Pre-existing targets are snapshotted to
.crew/backup/step_<N>/, then files written (sandboxed to the workspace). - Acceptance checks run (
shlex.split+shell=False, cwd=workspace, timeout, allowlisted binaries). Failures feed stderr back to the intern (max 2 retries). - Manager audits the result against the step spec; a
failverdict stops the run. An unparseable audit is treated as a fail (fail-safe).
The delegation decision is easiest to miss at task intake — once the director starts
planning, it plans for itself as executor. hooks/precheck.py is a Claude Code
UserPromptSubmit hook that regex-scans each prompt for delegable-work signals
(task-class AND bulk-scale, or a standalone strong signal like a research brief;
English + Japanese) and injects a one-line advisory before the model sees the
prompt. Advisory-only: false positives are expected and the director still decides.
Register it in ~/.claude/settings.json:
"UserPromptSubmit": [{"hooks": [{"type": "command",
"command": "python3 /path/to/LocalCrew/hooks/precheck.py"}]}]- Fail-safe: any error → exit 0, silent — a broken pre-check must never block a
prompt. Because that hides breakage,
precheck.py --selftestruns canned prompts and prints verdicts. - Config:
precheckblock inconfig.json, overridable via an untrackedconfig.local.json(same shape; hook-only) so per-machine tweaks don't dirty the tree.advisory_hintcustomizes the injected guidance text. - Optional
llm_escalationasks the intern model yes/no against the decision test and suppresses the advisory on "no" (fails open on any error). Default off: in live testing qwen3:8b judged a clearly delegable bulk-refactor prompt not-delegable — 8B false negatives would hide correct advisories. Heuristics are the gate.
Every run appends one JSONL record (workspace, task, per-step status/attempts/executor,
outcome, skill proposal) to run.ledger — default ~/.localcrew/ledger.jsonl, empty
string disables. crew.py stats [--last N] aggregates run/step success rates and a
per-executor breakdown. Ledger writes never fail a run.
ws/plan.json the reviewed plan (sha256 recorded in state + report)
ws/.crew/run_state.json per-step status — powers --resume
ws/.crew/run_log.jsonl every LLM call: role, tag, duration, full response
ws/.crew/run_report.md director-facing summary (written even on abort)
ws/.crew/backup/step_N/ pre-write snapshots of target files
- File writes are restricted to the workspace and to each step's declared
target_files. - Checks run without a shell; pipes/
;/redirects are inert and rejected up front.bashis allowed only asbash -n <file>. No absolute or..path arguments. - Honest caveat:
python3 -cand pytest are arbitrary code execution — the trust anchor is the director's plan review plus the plan sha256 tying the reviewed plan to the executed run, not the command validator.
- vLLM needs
chat_template_kwargs: {"enable_thinking": false}or reasoning burns the whole token budget andcontentcomes back null. - Ollama must be called via the native
/api/chatwith"think": false— the OpenAI-compat endpoint at:11434/v1ignores thinking switches. - Null/empty
contentis always treated as failure, never as an empty-but-OK answer.