Apprentice
A local, multi-provider code-delegation pipeline. A master orchestrator (e.g. Claude Code)delegates routine coding to apprentice models — a local Qwen 80B on your own GPU (viaOllama) and Gemini (Vertex AI) — then mechanically verifies, corrects, and learns from theresults over time via in-context retrieval. The expensive brain is spent on judgment; the cheapbrains do the typing.
The apprentices sit behind one small MCP server exposing three tools — delegate, assign,log_correction — so any MCP-capable orchestrator can drive them.
New here? Start with docs/MULTI_AGENT.md — it explains, in beginnerterms, what an "agent" is and how the boss + two-worker model fits together.
Note: the project was formerly
qwen-pipeline. Its default working directory and the MCPserver id are stillqwen-pipeline/qwen; only the project brand is Apprentice.
Why this exists
Many orchestrators (Claude Code among them) can only run their own model family — there's no wayto point a sub-agent at a local model or at Gemini. So the worker models live behind a localMCP server instead. The orchestrator calls a tool, the server runs the chosen model, theorchestrator reviews the output.
┌──────────────────────────────────────────────┐
│ ORCHESTRATOR — the "boss" / decision-maker │
│ splits tasks, picks provider, REVIEWS output, │
│ fixes mistakes, logs corrections, commits │
└───────────────┬────────────────────────────────┘
│ MCP tool call (stdio)
▼
┌──────────────────────────────────────────────┐
│ MCP server (src/server.py, FastMCP) │
│ delegate(task, role, provider?, model?, …) │
│ assign(task, done_when, repo, …) │
│ log_correction(…) │
└───┬───────────────┬───────────────┬────────────┘
▼ ▼ ▼
qwen gemini openai
(Ollama, (Vertex AI: (future,
local GPU) flash / pro) no key)
The "specialized agents" (test writer, C++ implementer, …) are not separate models — they arerole values that select a different system prompt for the same worker.
Requirements
- Python 3.11+
- Ollama running locally, with a worker model + an embedder pulled.
- (optional, for the
assignfile-aware agent) Aider in its own venv. - (optional, for the Gemini worker)
google-genai+ Google Cloud Vertex AI credentials. - An MCP-capable orchestrator (e.g. Claude Code) to drive the tools.
The reference machine is an RTX 5090 (32 GB VRAM) + 64 GB RAM, but the pipeline runs anywhereOllama can serve a model — scale the worker model to your hardware.
Getting started
git clone https://github.com/m-555/Apprentice.git qwen-pipeline
cd qwen-pipeline
# 1. core deps (pinned)
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt # Windows
# .venv/bin/pip install -r requirements.txt # Linux/macOS
# 2. pull the worker + embedder models (sizes approximate)
ollama pull qwen3-coder-next # ~51 GB, Q4_K_M — scale down for smaller GPUs
ollama pull nomic-embed-text # ~274 MB — for retrieval
# 3. (optional) the file-aware `assign` agent, in an ISOLATED venv
python -m venv .aider-venv
.aider-venv/Scripts/pip install -r requirements-aider.txt
# 4. register the MCP server with your orchestrator (Claude Code example)
claude mcp add --scope local qwen -- ".venv/Scripts/python.exe" "src/server.py"
claude mcp list # -> qwen ... ✓ Connected
Configuration lives in config/ — see docs/CONFIGURATION.md. To addthe Gemini worker, see Enabling Gemini below.
The worker model & expert-offload
| Reference setup | |
|---|---|
| Worker model | qwen3-coder-next:latest — 79.7B MoE (~3B active), Q4_K_M, 262k ctx, tools-capable |
| Embedder | nomic-embed-text (768-dim) — for retrieval |
| Runner | Ollama (HTTP API on 127.0.0.1:11434) |
Expert-offload: the ~48 GB model does not fit in 32 GB VRAM. Ollama keeps theattention/shared layers on the GPU and streams the MoE experts from system RAM. GPU utilisationlooks low because it is memory-bandwidth-bound — normal for an MoE with few active params. Warmthroughput ≈ 50–58 tok/s; cold load ≈ 55 s.
Warm model: Ollama keeps the model resident for keep_alive after the last request (default30m here). Requests within that window skip the load (~0.1 s). It's deliberately not infinite— a warm model holds the whole GPU, so a moderate timeout frees it for other work when idle.
Model storage gotcha (Ollama desktop app): the desktop app stores its model location in
db.sqliteand, when it spawns the server, setsOLLAMA_MODELSto that value — overriding yourenv var. If a largeollama pullfills the wrong disk, point both the env var and the app'sDB at your intended path, then confirm withollama list.
The MCP tools
delegate(task, role, provider="", context="", model="") -> str
Sends {system: ROLES[role], user: task (+context)} to the chosen provider and returns thegenerated text, plus a status footer with the gate verdict and an output_id. The pipelinemechanically verifies the output and auto-retries the worker on failure before returning.
- roles:
ts_implementer,cpp_implementer,py_implementer,test_writer,refactorer - providers:
qwen(local, default),gemini(Vertex AI),openai(future) - model: optional per-call override — for
gemini,"flash"(routine) or"pro"(hard).
assign(task, done_when, repo, provider="", files="", max_iters=0, apply=True, model="") -> dict
A file-aware worker agent (Aider) that reads repo itself and grinds a whole task to anobjective "done" with no orchestrator in the loop. The boss's role = define task + definedone + commit.
- Runs Aider (isolated venv, pinned) in a disposable git worktree off
repo's HEAD — the realtree is untouched. Loops: worker edits → rundone_when(a shell cmd that must exit 0) → onfailure feed the verbatim output back to the worker (up tomax_iters). - On green: extracts a clean diff (build/worker junk filtered) and, if
apply, mechanicallyapplies it to the real tree. You then just commit. - Returns a cheap summary:
{done_passed, applied, iterations, files_changed, patch_path, done_log_tail, worker_log_tail, output_id}— the full diff is inpatch_path.
log_correction(role, task, error_category, explanation, output_id="", correction_patch="", …) -> {"ok": true}
Appends one record to corrections/corrections.jsonl (and indexes it for retrieval). Call itafter every delegation, even when the worker was correct (error_category="none", empty patch).
error_category:logic | compile | style | edge_case | security | api_misuse | none.- Prefer the diff-only form: pass
output_id(from thedelegatefooter) + a unified-diffcorrection_patchinstead of re-sending the code — the pipeline reconstructs both sides.
The delegate → review → fix → log loop
- Split the task; delegate only the well-specified, self-contained part.
delegate(...)(snippet) orassign(...)(whole file-aware task) with the rightrole/provider — seeconfig/routing.md.- Review for: correctness, compiles/runs, project conventions, edge cases, security, and anylanguage-specific concerns (e.g. version-guarding for C++).
- Fix if needed (else corrected == worker output).
log_correction(...)— always.
The mechanical gate + worker→worker auto-retry handle most fixes with zero orchestrator tokens;the boss only steps in for judgment. Full routing rules: config/routing.md.
In-context retrieval — learning without training
The pipeline gets better over time via retrieval, not weight training (an 80B can't befine-tuned on one 32 GB GPU). The mechanism (src/retrieval.py):
- On
log_correction: the task is embedded withnomic-embed-textand a compact entry(vector + role/provider/category + few-shot fields) is appended tocorrections/index.jsonl. - On
delegate: the incoming task is embedded, the top-k most similar past correctionsfor the same provider+role are selected (favoring real mistakes permistake_vs_correct_mix)and injected into the system prompt as few-shot examples. - Fail-safe: if the embedder is unreachable, delegation still runs (just without examples) andcorrections are still saved — re-embed later with
python src/retrieval.py reindex.
Tunables in config/qwen.json → retrieval: enabled, top_k, role_filter,prefer_error_categories, mistake_vs_correct_mix.
Enabling Gemini (Vertex AI)
Secrets and machine-local values go in config/qwen.local.json (gitignored), which isdeep-merged over config/qwen.json at load time — so the committed config never holds a secret.
.venv/Scripts/pip install -r requirements-gemini.txtcp config/qwen.local.example.json config/qwen.local.json, then fill in your GCP project,the service-account JSON path (credentials_file), the model ids forflash/pro, andsetenabled: true.- Delegate to a tier:
delegate(..., provider="gemini", model="pro")orassign(..., provider="gemini", model="flash").
⚠️ The
assign(Aider) model ids must use litellm'svertex_ai/prefix for a serviceaccount (e.g.vertex_ai/gemini-2.5-pro), NOTgemini/(the AI-Studio API-key path). Fullwalkthrough + the two-model-id-forms gotcha: docs/CONFIGURATION.md.
Use on another project (it's project-agnostic)
Nothing in the gate/agent layer is tied to a particular codebase. To use it on another repo:
- Point
assign(repo="/path/to/your-repo", done_when="<your test/lint cmd>", …)at it. - (Optional) drop
<your-repo>/.qwen-pipeline.jsonto override per-project settings — itsagentblock merges overconfig/qwen.json → agent(repo wins). Example:{ "agent": { "max_iters": 4, "diff_excludes": [".aider*", "node_modules", "dist", "*.pyc"] } } - Gate languages (
gate.languages.*) and any batched build step are config-driven — enable/pointthem per project. The MCP surface (delegate,assign,log_correction) is unchanged.
Repository layout
qwen-pipeline/
├── README.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE # MIT
├── requirements.txt # core, PINNED (mcp, numpy)
├── requirements-gemini.txt # optional: Gemini/Vertex provider
├── requirements-aider.txt # optional: the `assign` agent (install in .aider-venv)
├── config/
│ ├── qwen.json # canonical config (committed, NO secrets)
│ ├── qwen.local.example.json # template for the gitignored local overlay
│ ├── qwen.local.json # GITIGNORED: project id, creds path, model ids, enabled flags
│ └── routing.md # what to delegate, to which provider/role/tier
├── docs/
│ ├── CONFIGURATION.md # config reference + enabling Gemini
│ └── MULTI_AGENT.md # how the boss + two-worker model works (beginner-friendly)
├── src/
│ ├── server.py # FastMCP stdio server: delegate / assign / log_correction
│ ├── providers.py # provider handlers: qwen, gemini, openai
│ ├── agent.py # the `assign` file-aware agent (Aider + disposable worktree)
│ ├── gate.py / gate_cli.py # mechanical gate (compile/lint) + worker-retry
│ ├── store.py # output-id store + unified-diff apply
│ ├── retrieval.py # embed + cosine retrieval of past corrections
│ ├── metering.py # per-delegation cost/outcome log
│ ├── host_verify.py # optional batched build/test runner (project-specific)
│ └── roles.py # role -> system-prompt map
├── tests/test_pipeline.py # deterministic, offline (stubs providers/embeddings)
└── corrections/ # GITIGNORED contents: corrections + retrieval index (local only)
Gitignored (never pushed): config/qwen.local.json, secrets/, corrections/*.jsonl, outputs/,metrics/, models/, .venv/, .aider-venv/, node_modules/.
Troubleshooting
| Symptom | Fix |
|---|---|
delegate errors "Could not reach Ollama" |
Server down. Run ollama serve (or start the desktop app). Check ollama list. |
| MCP server not connected | Run the launch command directly to see the error: .venv/Scripts/python.exe src/server.py |
| New tools not visible in a running session | They load in new sessions automatically; in a running one, reconnect (e.g. /mcp). |
| A pull fills the wrong disk | Ollama isn't using your intended path — see the model-storage gotcha above. |
| VRAM near OOM with big context | Cap num_ctx (KV cache grows with context). Prefer this over downgrading the quant. |
| Gemini "not enabled yet" | Expected until Vertex creds are configured — see Enabling Gemini. |
| Retrieval not injecting examples | Index empty/stale or embedder down. Rebuild: python src/retrieval.py reindex. |
Conventions & safety
- Pin dependencies. Never float the MCP SDK (a 2026 stdio command-injection advisory makespinning the documented mitigation). The stdio server runs with full user privileges — keep scopetight.
- Keep the tool surface small (three tools) — schemas load into the orchestrator's contextevery turn.
corrections/may contain private code — it stays local (gitignored) and is not committed.- Token generation on the worker; judgment on the boss. That's the only place the cost wincomes from — if a task type keeps coming back wrong, stop delegating it (
config/routing.md).
Documentation
- docs/MULTI_AGENT.md — how the boss + two-worker model works, inbeginner terms (what an agent is; Claude Code vs. Aider vs. Codex vs. OpenClaw; who does what).
- docs/CONFIGURATION.md — full config reference, the committed vs.local overlay, and enabling the Gemini/Vertex worker.
- CONTRIBUTING.md — dev setup, tests, and ground rules.
- CHANGELOG.md — notable changes.
License
MIT © 2026 Mohsen Mirzaei.