Skip to content

Repository files navigation

CODE FORGE

A decompose → contract → test-first → red/green harness that lets a local 27B model do agentic coding — no context-window expansion, no bigger hardware, no API bill.

Thesis: for local-model coding agents, the bottleneck people reach for — "longer context" or "buy a bigger GPU" — is usually the wrong lever. The right lever is the harness: decompose the task so the parent LLM never sees raw code, pin every name in a declared interface contract, generate tests first, then let a worker loop red→green against a real pytest run. With that scaffolding, a 24 GB / 27B setup builds working programs end-to-end.

This repo is the harness, extracted and cleaned for the public. It was validated by having a local Huihui-Qwen3.6-27B (Q5_K_M) build a playable Tetris — engine, state, and pygame front-end — with a human only closing the last, un-automatable gap: does it actually feel right when you play it?

(日本語の説明は下 → 日本語)


Why this exists

Everyone building local coding agents hits the same wall and reaches for the same two levers:

  1. "Give the model more context." Then the parent that plans the whole build drowns in raw code, and the context that was supposed to help is what kills you.
  2. "Buy a DGX Spark / bigger GPU." Expensive, and for interactive coding the memory-bandwidth math often doesn't even favor it.

CODE FORGE bets on a third lever — structure — and it pays off:

  • The parent never reads raw code. It only ever sees a task, a decomposition, and a declared interface (class/method/function signatures). So the parent's context does not grow with the codebase. This is the single design choice that makes local models viable here.
  • One source of truth for names. Plan, tests, and implementation all reference the same declared interface contract, killing the classic local-model failure mode of name drift (is_collision in the plan, check_collision in the code).
  • Test-first, worker can't touch tests/. The worker may only write the files it declared it owns; it is forbidden from editing the tests it's being graded against. That's what makes "green" mean something instead of being cheated.

The verification gradient (the core idea)

The harness organizes everything around a ladder of increasingly strong checks:

compile  <  launch (smoke)  <  unit-green (pytest)  <  behaves correctly
└─ auto ──────────────────────────────────────┘         └─ human ─┘

The machine can automatically hold everything up to "launches and unit tests pass." The top rung — is the behavior/feel actually right — is the human's job, and CODE FORGE is honest about that: it will tell you "I applied the fix; the behavior you'll have to confirm by playing it," rather than falsely declaring GREEN. The human is the oracle for defects — you describe what looks wrong, not how to fix it, and the agent localizes and repairs it.

Pipeline

GOAL
 │
 ▼  PLAN            parent decomposes → ordered subtasks, each with
 │  (+plan-linter)  goal / accept-criteria / declared interface / verify-kind
 │  ── human gate: approve the plan & the interface table ──
 ▼
 ▼  TEST-FIRST      generate pytest from accept-criteria (target module
 │                  not implemented yet = red phase)
 ▼
 ▼  BUILD (red→green) worker sees the contract + tests, emits `### FILE: path`
 │                  files → real pytest run → on failure, feed the full error
 │                  back and retry (keep-best across attempts, never regress
 │                  a passing test)
 ▼
 ▼  RUN             human plays / exercises the artifact
 │  ── L3 verdict: OK / issue / broken ──  (the ground-truth signal)
 ▼
 ▼  FIX (L3 loop)   human describes the defect in plain words → targeted
                    reading (grep/read to find the spot, not read-all) →
                    minimal edit → regression + smoke → repeat

Optional levers (toggled in the GUI): boundary probes, input triage, plan-linter, arbitration on stuck plateaus.

Quickstart

1. A local OpenAI-compatible server on localhost:8000. Anything that speaks /v1/chat/completions works — this was built against llama.cpp's llama-server. Start it with tool/--jinja support and a capable model. Reference setup used Huihui-Qwen3.6-27B (Q5_K_M) on a 24 GB card.

Tip from building this: for a 27B, tool-call / instruction-following survival tracks model generation, not size (Qwen3.5+ held up where older/smaller "uncensored" coders fell apart). Pick a recent generation.

2. Install deps:

pip install -r requirements.txt   # PySide6 + requests + pytest

3. Run the GUI:

python code_forge_gui.py

Type a goal (e.g. "make a playable Tetris in pygame"), pick a working directory, and hit ▶ decompose & build. Approve the plan when prompted, watch the red→green scoreboard, then ▶ RUN to play the result and give your verdict.

Or run the headless HTTP service (stdlib only, no PySide6 needed — for a second client / automation):

python forge_service.py      # or ./start_service.sh
# POST /build {goal, workdir?, levers?, budget?} → {session_id}
# GET  /status/{id} · POST /answer|/review|/abort · GET /result/{id}

Backends & routing

forge_brain.py is a small hierarchical router. By default everything runs on your local model. Optionally, the few low-token roles where a 27B tends to hit a ceiling (PLAN / TESTGEN / CLARIFY / ESCALATE) can be routed to a "strong" model (Anthropic), while IMPL / EXPLORE / FIX stay local:

pip install anthropic
export ANTHROPIC_API_KEY=...            # a real key; placeholders auto-fall-back to local
export FORGE_ALL_LOCAL=1                 # force everything local (default behavior anyway)
export FORGE_ROLE_PLAN=strong           # per-role override: local | strong

If anthropic isn't installed or there's no real key, strong roles silently fall back to local — so it never throws 401s and the "all-local" path is always intact. That local path is the whole thesis; the strong routing is just an optional ceiling-raiser.

Project layout

forge_core/          Qt-independent engine (import this from anywhere)
  orchestrator.py    the state machine driving PLAN→TEST→BUILD→FIX
  session.py         re-entrant session (used by the HTTP service)
  workers.py         planner / test-gen / clarify workers
  impl.py            the red→green implementation loop + smoke driver
  fix.py             L3 targeted-reading fix loop
  contract.py        declared-interface contract (the single source of truth for names)
  declaration.py     interface declaration parsing
  plan_linter.py     rejects un-machine-checkable plans
  test_linter.py     rejects empty/degenerate generated tests
  probe.py           boundary probes
  triage.py          input triage
  arbitration.py     stuck-plateau arbitration
  metrics.py         JSONL event log (size-proxy vs convergence)
  prompts.py         role prompts
forge_brain.py       hierarchical local/strong router (all-local by default)
code_forge_gui.py    PySide6 GUI (thin skin over forge_core.orchestrator)
forge_probe_dialog.py  probe-confirmation dialog
forge_service.py     stdlib HTTP service (second client, zero deps)

Status

Research-grade / MVP. It builds real, playable small programs from a one-line goal on a fully local 27B, and the human-in-the-loop verification gradient is wired end to end. Comments in the source are in Japanese (this was a personal research project); the code and this README are the public extraction. PRs and forks welcome.


日本語

「分解 → インターフェース宣言(契約)→ テストファースト → 赤緑ループ」で、ローカル27B にエージェント的コーディングをやらせるハーネス。コンテキスト長の拡張も、でかいGPUも、API課金も要らない。

思想

ローカルのコーディングエージェントを作ると誰もが同じ壁にぶつかり、同じ2つのレバーに手を伸ばす — 「コンテキストを伸ばせ」「DGX Spark を買え」。CODE FORGE は 3つ目のレバー=構造 に賭ける:

  • 親(PARENT)は生コードを一切読まない。 見るのはお題・分解・宣言インターフェース(クラス/メソッド/関数のシグネチャ)だけ。だから親のコンテキストはコード量に比例して膨らまない — これがローカルモデルを成立させる唯一で最大の設計判断。
  • 名前の唯一の真実。 plan・test・実装がすべて同じ宣言インターフェース契約を参照するので、ローカルモデル定番の破綻=名前ドリフト(is_collisioncheck_collision)が消える。
  • テストファースト。ワーカーは tests/ に書けない。 ワーカーは自分が宣言して所有するファイルしか書けず、採点に使われるテストは編集不可。だから「GREEN」がチート(テスト書き潰し)でなく本物の意味を持つ。

検証勾配(核心)

compile  <  起動(smoke)  <  unit緑(pytest)  <  挙動が正しい
└─ 自動で守れる ─────────────────────────┘        └─ 人間 ─┘

機械が自動で守れるのは「起動して unit が緑」まで。一番上の段=挙動・手触りが本当に正しいか、は人間が審判で、ハーネスはそこに正直: 挙動修正で嘘の GREEN を出さず「適用した・挙動は▶RUNで確かめて」と返す。人間は欠陥のオラクル — 直し方でなく「どこがどう変か」を言えば、エージェントが場所を特定して直す。

実証

ローカル Huihui-Qwen3.6-27B (Q5_K_M) ・24GB・ctx拡張なし/新ハードなしで、遊べるテトリス(logic エンジン+GameState+pygame front-end)を一気通貫で自走生成。人間が継いだのは最後の「実際に遊んで手触りが正しいか」だけ。最初の問い「DGX Spark やコンテキスト長が要るのか?」への答え=要らない、ハーネスで届いた。

27B を選ぶときのコツ: tool-call / 指示追従の生存はサイズでなく世代で決まる(Qwen3.5 以上は耐え、旧世代/小型の "uncensored" コーダーは形式が壊れた)。新しい世代を選ぶこと。

使い方

  1. localhost:8000 に OpenAI互換サーバー(llama.cppllama-server 想定、tool/--jinja対応で起動)。
  2. pip install -r requirements.txt
  3. python code_forge_gui.py → お題を入れて ▶ 分解してビルド → PLAN承認 → 赤緑スコアボード → ▶ RUN で遊んで判定。

ヘッドレスHTTPサービス(標準ライブラリのみ・PySide6不要): python forge_service.py

バックエンド

forge_brain.py は階層ルーター。既定は全ロールがローカル。 27Bが天井に当たる少数の低トークンロール(PLAN/TESTGEN/CLARIFY/ESCALATE)だけ強モデル(Anthropic)へ逃がすことも可能(ANTHROPIC_API_KEYanthropic パッケージがある時のみ有効・無ければ静かにローカルへフォールバック)。全ローカル経路こそが主張の本体で、強モデルは任意の天井上げにすぎない。


MIT License · © 2026 muu

About

Decompose→contract→test-first→red/green harness that lets a local 27B model do agentic coding — no context expansion, no bigger GPU, no API bill.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages