Handbook
Granite / Qwen local-model harness
Probe, calibrate, and measure Granite-hosted Qwen models for forge-dark-factory local worker runs. Slow inference is expected; the harness uses long timeouts and records partial success (draft-only vs verify_ok).
Updated
Prerequisites
- Python venv:
bash scripts/setup.sh - Granite env file (never commit secrets). Auto-discovery order is documented in
src/forge_dark_factory/llm_env.py. Typical on this workspace:
forge-composer-workbench/project-management-certification/certification-llm.local.env
-
Copy
config/granite-env.example.envtoconfig/granite.local.envwhen you need a repo-local override. -
Optional multi-endpoint failover: add
config/llm-priority.json(see Multi-endpoint registry below). The discovered Granite env file is always priority 0; JSON entries are fallbacks with higher priority numbers.
Multi-endpoint registry
forge-dark-factory uses forge-lcdl load_llm_registry() to combine:
| Source | Priority | Role |
|---|---|---|
First discovered Granite env (load_llm_profile) |
0 | Primary endpoint (discovered-<source> id) |
config/llm-priority.json |
1+ | Ordered fallbacks (transport errors only) |
Example config/llm-priority.json:
{
"profiles": [
{
"id": "granite-backup",
"priority": 1,
"base_url": "https://backup-gateway.example/v1",
"api_key": "from-secrets",
"model": "ctx-unlim-qwen3-8b:latest",
"timeout_sec": 180
}
]
}
Failover scope: timeouts, connection errors, HTTP 5xx across profiles. JSON parse / content
errors stay inside each profile (chat_with_json_mode_then_plain); they do not advance to the
next endpoint.
Redaction: aggregated failure strings include profile id and gateway host only — never
api_key. PDCA model_report.json records profile_id and endpoint per attempt.
Canonical LCDL reference: forge-lcdl llm-priority.md.
Rollback: remove config/llm-priority.json and unset FORGE_LCDL_LLM_PRIORITY_FILE — behavior
reverts to single discovered env (same as before this feature).
Quick start
# Phase 0 only — smoke + JSON roundtrip (no worktree writes)
bash scripts/granite-harness.sh --probe-only
# Full matrix (calculator + optional readme link), long timeout
export FORGE_DARK_FACTORY_GRANITE_TIMEOUT_SEC=180
bash scripts/granite-harness.sh
# Calculator only (faster)
bash scripts/granite-harness.sh --skip-readme
# Dry run (offline CI)
bash scripts/granite-harness.sh --dry-run --out-dir /tmp/granite-harness-dry
# Save full request/response I/O for every Granite call
bash scripts/granite-harness.sh --save-raw --skip-readme
# → runs/granite-harness/<utc>/llm-io.jsonl (consolidated)
# → per-task .../calc-multiply/llm-io.jsonl and run-*/machine/llm-io.jsonl
Outputs: runs/granite-harness/<utc>/matrix_report.json, report.md, and optionally llm-io.jsonl.
Recommended models
| Model id | Role |
|---|---|
ctx-unlim-qwen3-8b:latest |
Primary cheap model (integration default) |
ctx-unlim-qwen25-coder-7b:latest |
Coder-oriented variant |
Override for a run: bash scripts/granite-harness.sh --model ctx-unlim-qwen25-coder-7b:latest
Timeouts
| Variable | Default | Used by |
|---|---|---|
FORGE_DARK_FACTORY_LLM_TIMEOUT_SEC |
45 (max 120) | Campaign sign-off (run-pdca-campaign.sh) |
FORGE_DARK_FACTORY_GRANITE_TIMEOUT_SEC |
180 (max 300) | Local worker + harness |
Campaign runs stay fast (45s) and escalate to Cursor. Harness and
campaigns/granite-calibration.yaml use
the granite timeout so Qwen can finish (calculator often needs 60–120s).
Calibration campaign
Local-only ladder (no Cursor fallback):
export FORGE_DARK_FACTORY_GRANITE_TIMEOUT_SEC=180
bash scripts/run-pdca-campaign.sh campaigns/granite-calibration.yaml
Or from the harness:
bash scripts/granite-harness.sh --run-calibration-campaign
Success: at least one task with verify_ok in matrix_report.json.
Best practices for local models
| Practice | Why |
|---|---|
Narrow allowed_files |
Reduces wrong-path edits |
| One patch unit (L1) | Matches cheap-model operating guide |
| Long granite timeout | Avoid false timeouts on slow host |
| Verify before escalate | Deterministic pytest/link checks |
| Repair loop | Driver re-drafts with LCDL repair hints + verify output |
| Optional two-phase draft | FORGE_DARK_FACTORY_LLM_TWO_PHASE=1 — plan bullets then JSON |
| JSON parse-repair | Automatic strict JSON retry on parse failure |
Inference note
forge-lcdl inference (forge_lcdl.inference.planner) is for RAG retrieval
planning in ExecutionEngine, not coding patches. forge-dark-factory does not
use it. The practical substitute for Qwen is two-phase draft + repair loops
+ JSON-mode-then-plain (via chat_with_json_mode_then_plain).
Reading matrix_report.json
{
"verify_ok_count": 1,
"probe": { "smoke_ok": true, "json_ok": true, "model": "..." },
"tasks": [
{
"task_id": "calc-multiply",
"verify_ok": true,
"draft_ok": true,
"latency_sec": 82.4,
"failure_reason": ""
}
]
}
Reliability in PDCA model_report.json:
verify_ok→ score 1.0draft_okonly → 0.5- failed draft (recorded via
abandon_draftbefore Cursor) → 0.0
Sign-off integration
Optional during campaign sign-off:
bash scripts/campaign-signoff.sh --granite-demo --granite-harness
Default sign-off does not require Granite-only wins on lenses-production.
See also
- PDCA-CAMPAIGNS.md
- forge-lcdl llm-priority registry
- forge-lcdl CHEAP-MODEL-OPERATING-GUIDE
- forge-lcdl live-granite-tests