Skip to content

Commit 7cf4e7d

Browse files
authored
Merge pull request #14 from BaseInfinity/v0.11.1-per-agent-temps
v0.11.1: per-agent temperatures
2 parents 26f81c1 + 4b06677 commit 7cf4e7d

6 files changed

Lines changed: 275 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,79 @@
22

33
All notable changes to opencode-sdlc-wizard.
44

5+
## [0.11.1] - 2026-05-18
6+
7+
### Added — Per-agent temperatures (`--coder-temp`, `--planner-temp`, `--reviewer-temp`)
8+
9+
May-2026 community pattern (joelhooks, ppries, others): explicit
10+
temperature settings per agent. Typical split: `plan=0.1`
11+
(deterministic, focus on the right answer), `build=0.3` (some
12+
creativity for code), `review=0.1` (deterministic).
13+
14+
```bash
15+
npx opencode-sdlc-wizard pick \
16+
--tier proprietary --provider anthropic \
17+
--planner-tier hosted_oss --planner-provider groq --planner-temp 0.1 \
18+
--reviewer-tier hosted_oss --reviewer-provider cerebras --reviewer-temp 0.1 \
19+
--coder-temp 0.3
20+
```
21+
22+
Yields:
23+
24+
```json
25+
{
26+
"model": "anthropic/claude-opus-4-7",
27+
"agent": {
28+
"build": { "temperature": 0.3 },
29+
"plan": { "model": "groq/gpt-oss-120b", "temperature": 0.1 },
30+
"review":{ "model": "cerebras/gpt-oss-120b", "temperature": 0.1 }
31+
}
32+
}
33+
```
34+
35+
### Naming notes
36+
37+
- `--coder-temp` targets `agent.build.temperature` — OpenCode's default
38+
agent name for code generation is `build`, not `coder`. We expose
39+
`--coder-temp` for ergonomics (matches `--coder-*` mental model from
40+
v0.10.x) while writing the canonical JSON key.
41+
- `--planner-temp` targets `agent.plan.temperature` (mirrors
42+
`--planner-tier`/`--planner-provider` from v0.10.2).
43+
- `--reviewer-temp` targets `agent.review.temperature` (mirrors
44+
`--reviewer-tier`/`--reviewer-provider` from v0.10.0).
45+
46+
### Changed — `scripts/configure-backend.sh`
47+
48+
- New `--coder-temp T`, `--planner-temp T`, `--reviewer-temp T` flags
49+
- Values parsed as JSON numbers (so `0.3` becomes `0.3` not `"0.3"`)
50+
- Empty string = "not set" — no temperature field emitted for that agent
51+
- Deep-merges with v0.10.0 `agent.review.model`, v0.10.2
52+
`agent.plan.model`, v0.10.5 `agent.plan.tools`, v0.10.1
53+
`agent.<name>.permission.write` blocks
54+
55+
### Changed — `scripts/pick-backend.sh`
56+
57+
- Same three flags forwarded to `configure-backend.sh` unchanged
58+
- No default-model fallback (temperatures aren't tied to providers)
59+
60+
### Tests
61+
62+
- `tests/test-backend-picker.sh` adds T56–T60:
63+
- T56: `--coder-temp 0.3` writes `agent.build.temperature = 0.3` as a number
64+
- T57: `--reviewer-temp` composes with `--reviewer-*` (model + temperature on agent.review)
65+
- T58: `--planner-temp` + `--planner-*` + `--sandbox-plan` triple-compose (model + temperature + tools on agent.plan)
66+
- T59: all three temp flags emit correct values
67+
- T60: no flags → no temperature fields (opt-in regression guard)
68+
- `tests/test-pick.sh` adds T35–T38 (passthrough + regression guard)
69+
- **388 tests across 12 suites** (was 379 / 12 in v0.11.0)
70+
71+
### Compat
72+
73+
- Opt-in only. Existing configs unchanged unless `--*-temp` flags passed.
74+
- Composes with every v0.10.x and v0.11.0 flag. Full v0.11.1 stack in
75+
one call: model + small_model + 3 per-agent models + 3 sandboxes +
76+
3 per-agent temperatures.
77+
578
## [0.11.0] - 2026-05-18
679

780
### Added — Z.AI GLM Coding Plan as a first-class proprietary provider

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-sdlc-wizard",
3-
"version": "0.11.0",
3+
"version": "0.11.1",
44
"description": "SDLC enforcement for OpenCode CLI — privacy-first, any-backend portability with a four-tier backend picker plus an OSS-tier cross-model-review skill so the full SDLC loop can run with zero Anthropic+OpenAI lock-in. Ships JSON Schemas for review artifacts so any consumer (cross-model-review, ditto, CI) can validate. Install with `npx opencode-sdlc-wizard init`. Sibling of agentic-sdlc-wizard and codex-sdlc-wizard.",
55
"bin": {
66
"opencode-sdlc-wizard": "cli/bin/opencode-sdlc-wizard.js"

scripts/configure-backend.sh

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ SANDBOX_DOCS=0
5959
# modify code. Distinct from v0.10.1 permission.write blocks — those
6060
# are path-scoped allow/deny on writes; this is categorical tool disable.
6161
SANDBOX_PLAN=0
62+
# v0.11.1 per-agent temperatures. Community pattern (joelhooks + ppries):
63+
# plan=0.1 (deterministic), build=0.3 (some creativity), review=0.1
64+
# (deterministic). Empty string means "not set" → no temperature field
65+
# emitted on that agent block.
66+
CODER_TEMP=""
67+
PLANNER_TEMP=""
68+
REVIEWER_TEMP=""
6269

6370
usage() {
6471
sed -n '2,15p' "$0"
@@ -97,6 +104,12 @@ while [ $# -gt 0 ]; do
97104
--sandbox-test-writer) SANDBOX_TEST_WRITER=1 ;;
98105
--sandbox-docs) SANDBOX_DOCS=1 ;;
99106
--sandbox-plan) SANDBOX_PLAN=1 ;;
107+
--coder-temp) shift; CODER_TEMP="${1:-}" ;;
108+
--coder-temp=*) CODER_TEMP="${1#*=}" ;;
109+
--planner-temp) shift; PLANNER_TEMP="${1:-}" ;;
110+
--planner-temp=*) PLANNER_TEMP="${1#*=}" ;;
111+
--reviewer-temp) shift; REVIEWER_TEMP="${1:-}" ;;
112+
--reviewer-temp=*) REVIEWER_TEMP="${1#*=}" ;;
100113
-h|--help) usage; exit 0 ;;
101114
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
102115
esac
@@ -149,7 +162,8 @@ node - "$TIER" "$PROVIDER" "$MODEL" "$CONFIG_PATH" "$FORCE" "$PRINT_ONLY" \
149162
"$SANDBOX_TEST_WRITER" "$SANDBOX_DOCS" \
150163
"$PLANNER_TIER" "$PLANNER_PROVIDER" "$PLANNER_MODEL" \
151164
"$SMALL_TIER" "$SMALL_PROVIDER" "$SMALL_MODEL" \
152-
"$SANDBOX_PLAN" <<'NODE'
165+
"$SANDBOX_PLAN" \
166+
"$CODER_TEMP" "$PLANNER_TEMP" "$REVIEWER_TEMP" <<'NODE'
153167
const fs = require("node:fs");
154168
const [
155169
tier, providerArg, model, configPath, forceStr, printOnlyStr,
@@ -158,6 +172,7 @@ const [
158172
plannerTier, plannerProviderArg, plannerModel,
159173
smallTier, smallProviderArg, smallModel,
160174
sandboxPlanStr,
175+
coderTempStr, plannerTempStr, reviewerTempStr,
161176
] = process.argv.slice(2);
162177
const force = forceStr === "1";
163178
const printOnly = printOnlyStr === "1";
@@ -167,6 +182,12 @@ const sandboxDocs = sandboxDocsStr === "1";
167182
const plannerMode = Boolean(plannerTier && plannerProviderArg && plannerModel);
168183
const smallMode = Boolean(smallTier && smallProviderArg && smallModel);
169184
const sandboxPlan = sandboxPlanStr === "1";
185+
// Parse temperatures only if non-empty; empty string means "not set"
186+
// (no temperature field emitted). Numbers preserve as numbers in JSON.
187+
function parseTemp(s) { return s === "" ? null : Number(s); }
188+
const coderTemp = parseTemp(coderTempStr);
189+
const plannerTemp = parseTemp(plannerTempStr);
190+
const reviewerTemp = parseTemp(reviewerTempStr);
170191
171192
// Canonical provider IDs. Detector emits user-friendly aliases; we accept both
172193
// and emit the canonical OpenCode/models.dev ID in the written config so model
@@ -559,6 +580,18 @@ if (sandboxTestWriter || sandboxDocs || sandboxPlan) {
559580
merged.agent = deepMerge(merged.agent || existing.agent || {}, sandboxAdditions);
560581
}
561582
583+
// v0.11.1 per-agent temperatures. Each non-null value sets the
584+
// agent.<name>.temperature field; deep-merges with any existing model /
585+
// tools / permission siblings on that agent. --coder-temp targets the
586+
// `build` agent (OpenCode's default agent name for code generation).
587+
if (coderTemp !== null || plannerTemp !== null || reviewerTemp !== null) {
588+
const tempAdditions = {};
589+
if (coderTemp !== null) tempAdditions["build"] = { temperature: coderTemp };
590+
if (plannerTemp !== null) tempAdditions["plan"] = { temperature: plannerTemp };
591+
if (reviewerTemp !== null) tempAdditions["review"] = { temperature: reviewerTemp };
592+
merged.agent = deepMerge(merged.agent || existing.agent || {}, tempAdditions);
593+
}
594+
562595
// Canonical key ordering for deterministic output (idempotency requirement).
563596
// Top-level: $schema, model, provider, then everything else alphabetical.
564597
function sortKeysCanonical(obj, topLevel = false) {

scripts/pick-backend.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ SANDBOX_TEST_WRITER=0
7272
SANDBOX_DOCS=0
7373
# v0.10.5 plan-mode tool denial (agent.plan.tools.{write,edit,patch}=false).
7474
SANDBOX_PLAN=0
75+
# v0.11.1 per-agent temperatures.
76+
CODER_TEMP=""
77+
PLANNER_TEMP=""
78+
REVIEWER_TEMP=""
7579

7680
while [ $# -gt 0 ]; do
7781
case "$1" in
@@ -107,6 +111,12 @@ while [ $# -gt 0 ]; do
107111
--sandbox-test-writer) SANDBOX_TEST_WRITER=1 ;;
108112
--sandbox-docs) SANDBOX_DOCS=1 ;;
109113
--sandbox-plan) SANDBOX_PLAN=1 ;;
114+
--coder-temp) shift; CODER_TEMP="${1:-}" ;;
115+
--coder-temp=*) CODER_TEMP="${1#*=}" ;;
116+
--planner-temp) shift; PLANNER_TEMP="${1:-}" ;;
117+
--planner-temp=*) PLANNER_TEMP="${1#*=}" ;;
118+
--reviewer-temp) shift; REVIEWER_TEMP="${1:-}" ;;
119+
--reviewer-temp=*) REVIEWER_TEMP="${1#*=}" ;;
110120
-h|--help) usage; exit 0 ;;
111121
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
112122
esac
@@ -285,6 +295,9 @@ fi
285295
[ "$SANDBOX_TEST_WRITER" = "1" ] && CONFIGURE_ARGS+=(--sandbox-test-writer)
286296
[ "$SANDBOX_DOCS" = "1" ] && CONFIGURE_ARGS+=(--sandbox-docs)
287297
[ "$SANDBOX_PLAN" = "1" ] && CONFIGURE_ARGS+=(--sandbox-plan)
298+
[ -n "$CODER_TEMP" ] && CONFIGURE_ARGS+=(--coder-temp "$CODER_TEMP")
299+
[ -n "$PLANNER_TEMP" ] && CONFIGURE_ARGS+=(--planner-temp "$PLANNER_TEMP")
300+
[ -n "$REVIEWER_TEMP" ] && CONFIGURE_ARGS+=(--reviewer-temp "$REVIEWER_TEMP")
288301

289302
if [ -n "$REVIEWER_TIER" ]; then
290303
echo "pick: resolved coder $TIER/$PROVIDER$MODEL + reviewer $REVIEWER_TIER/$REVIEWER_PROVIDER$REVIEWER_MODEL" >&2

tests/test-backend-picker.sh

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,128 @@ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
12031203
fi
12041204
fi
12051205

1206+
# --- v0.11.1: per-agent temperatures. May-2026 community pattern (joelhooks
1207+
# + ppries gists, others): plan=0.1 (deterministic), build=0.3 (some
1208+
# creativity), review=0.1 (deterministic). Flags --planner-temp / --reviewer-
1209+
# temp / --coder-temp set agent.<name>.temperature; deep-merges with v0.10.0
1210+
# agent.review.model + v0.10.2 agent.plan.model + v0.10.5 agent.plan.tools.
1211+
1212+
# --- T56: --coder-temp writes agent.build.temperature ("build" is OpenCode's
1213+
# default agent name for code generation work)
1214+
if [ -x "$CONFIG" ]; then
1215+
T="$TMP_ROOT/t56"; mkdir -p "$T"
1216+
(cd "$T" && "$CONFIG" \
1217+
--tier private_local --provider ollama --model qwen3-coder:30b \
1218+
--coder-temp 0.3 >/dev/null 2>&1) || true
1219+
if [ -f "$T/opencode.json" ]; then
1220+
ok="$(node -e "
1221+
const j=require('$T/opencode.json');
1222+
if(!j.agent||!j.agent.build||j.agent.build.temperature!==0.3){console.log('build-temp-wrong:'+JSON.stringify(j.agent&&j.agent.build));process.exit(1)}
1223+
console.log('ok');
1224+
" 2>/dev/null || echo 'failed')"
1225+
if [ "$ok" = "ok" ]; then
1226+
pass "--coder-temp 0.3 writes agent.build.temperature = 0.3"
1227+
else
1228+
fail "T56 — $ok"
1229+
fi
1230+
fi
1231+
fi
1232+
1233+
# --- T57: --reviewer-temp + --reviewer-* compose (model AND temperature both
1234+
# present on agent.review)
1235+
if [ -x "$CONFIG" ]; then
1236+
T="$TMP_ROOT/t57"; mkdir -p "$T"
1237+
(cd "$T" && "$CONFIG" \
1238+
--tier proprietary --provider anthropic --model claude-opus-4-7 \
1239+
--reviewer-tier hosted_oss --reviewer-provider cerebras --reviewer-model gpt-oss-120b \
1240+
--reviewer-temp 0.1 >/dev/null 2>&1) || true
1241+
if [ -f "$T/opencode.json" ]; then
1242+
ok="$(node -e "
1243+
const j=require('$T/opencode.json');
1244+
if(j.agent.review.model!=='cerebras/gpt-oss-120b'){console.log('rev-model-wrong');process.exit(1)}
1245+
if(j.agent.review.temperature!==0.1){console.log('rev-temp-wrong:'+j.agent.review.temperature);process.exit(1)}
1246+
console.log('ok');
1247+
" 2>/dev/null || echo 'failed')"
1248+
if [ "$ok" = "ok" ]; then
1249+
pass "--reviewer-temp composes with --reviewer-* (agent.review has model + temperature)"
1250+
else
1251+
fail "T57 — $ok"
1252+
fi
1253+
fi
1254+
fi
1255+
1256+
# --- T58: --planner-temp + --planner-* + --sandbox-plan compose
1257+
# (agent.plan ends up with .model AND .temperature AND .tools)
1258+
if [ -x "$CONFIG" ]; then
1259+
T="$TMP_ROOT/t58"; mkdir -p "$T"
1260+
(cd "$T" && "$CONFIG" \
1261+
--tier proprietary --provider anthropic --model claude-opus-4-7 \
1262+
--planner-tier hosted_oss --planner-provider groq --planner-model gpt-oss-120b \
1263+
--planner-temp 0.1 \
1264+
--sandbox-plan >/dev/null 2>&1) || true
1265+
if [ -f "$T/opencode.json" ]; then
1266+
ok="$(node -e "
1267+
const j=require('$T/opencode.json');
1268+
if(j.agent.plan.model!=='groq/gpt-oss-120b'){console.log('plan-model-wrong');process.exit(1)}
1269+
if(j.agent.plan.temperature!==0.1){console.log('plan-temp-wrong');process.exit(1)}
1270+
if(j.agent.plan.tools.write!==false){console.log('plan-tools-wrong');process.exit(1)}
1271+
console.log('ok');
1272+
" 2>/dev/null || echo 'failed')"
1273+
if [ "$ok" = "ok" ]; then
1274+
pass "Full plan agent: --planner-* + --planner-temp + --sandbox-plan all compose"
1275+
else
1276+
fail "T58 — $ok"
1277+
fi
1278+
fi
1279+
fi
1280+
1281+
# --- T59: all three temp flags + all per-agent flags + sandboxes compose
1282+
if [ -x "$CONFIG" ]; then
1283+
T="$TMP_ROOT/t59"; mkdir -p "$T"
1284+
(cd "$T" && "$CONFIG" \
1285+
--tier proprietary --provider anthropic --model claude-opus-4-7 \
1286+
--coder-temp 0.3 \
1287+
--reviewer-tier hosted_oss --reviewer-provider cerebras --reviewer-model gpt-oss-120b \
1288+
--reviewer-temp 0.1 \
1289+
--planner-tier hosted_oss --planner-provider groq --planner-model gpt-oss-120b \
1290+
--planner-temp 0.1 >/dev/null 2>&1) || true
1291+
if [ -f "$T/opencode.json" ]; then
1292+
ok="$(node -e "
1293+
const j=require('$T/opencode.json');
1294+
if(j.agent.build.temperature!==0.3){console.log('build-temp');process.exit(1)}
1295+
if(j.agent.review.temperature!==0.1){console.log('rev-temp');process.exit(1)}
1296+
if(j.agent.plan.temperature!==0.1){console.log('plan-temp');process.exit(1)}
1297+
console.log('ok');
1298+
" 2>/dev/null || echo 'failed')"
1299+
if [ "$ok" = "ok" ]; then
1300+
pass "all three --*-temp flags emit correct agent.<name>.temperature values"
1301+
else
1302+
fail "T59 — $ok"
1303+
fi
1304+
fi
1305+
fi
1306+
1307+
# --- T60: no temp flags → no temperature fields written (opt-in regression)
1308+
if [ -x "$CONFIG" ]; then
1309+
T="$TMP_ROOT/t60"; mkdir -p "$T"
1310+
(cd "$T" && "$CONFIG" --tier private_local --provider ollama --model qwen3-coder:30b >/dev/null 2>&1) || true
1311+
if [ -f "$T/opencode.json" ]; then
1312+
ok="$(node -e "
1313+
const j=require('$T/opencode.json');
1314+
const agent=j.agent||{};
1315+
for(const k of ['build','plan','review']){
1316+
if(agent[k]&&'temperature' in agent[k]){console.log('unexpected-temp:'+k);process.exit(1)}
1317+
}
1318+
console.log('ok');
1319+
" 2>/dev/null || echo 'failed')"
1320+
if [ "$ok" = "ok" ]; then
1321+
pass "no --*-temp flags → no temperature fields (opt-in regression guard)"
1322+
else
1323+
fail "T60 — $ok"
1324+
fi
1325+
fi
1326+
fi
1327+
12061328
echo ""
12071329
echo "=== Results: $PASS passed, $FAIL failed ==="
12081330
[ "$FAIL" -eq 0 ] || exit 1

tests/test-pick.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,38 @@ if [ -x "$SCRIPT" ]; then
605605
fi
606606
fi
607607

608+
# T35-T37: v0.11.1 per-agent temperature flags pass through unchanged.
609+
for spec in "coder:0.3" "planner:0.1" "reviewer:0.1"; do
610+
flag="${spec%%:*}"
611+
val="${spec##*:}"
612+
if [ -x "$SCRIPT" ]; then
613+
T="$TMP_ROOT/t35-$flag"; make_target "$T" "private_local/ollama"
614+
DETECT_STUB_LOG="$T/detect.log"
615+
CONFIGURE_STUB_LOG="$T/configure.log"
616+
(DETECT_STUB_LOG="$DETECT_STUB_LOG" CONFIGURE_STUB_LOG="$CONFIGURE_STUB_LOG" \
617+
PATH="$T/stubs:$PATH" "$SCRIPT" --${flag}-temp "$val" >/dev/null 2>&1) || true
618+
if [ -f "$CONFIGURE_STUB_LOG" ] && grep -q -- "--${flag}-temp $val" "$CONFIGURE_STUB_LOG"; then
619+
pass "--${flag}-temp $val passes through to configure-backend"
620+
else
621+
fail "--${flag}-temp dropped"
622+
fi
623+
fi
624+
done
625+
626+
# T38: no temp flags → no temp args forwarded (regression guard)
627+
if [ -x "$SCRIPT" ]; then
628+
T="$TMP_ROOT/t38"; make_target "$T" "private_local/ollama"
629+
DETECT_STUB_LOG="$T/detect.log"
630+
CONFIGURE_STUB_LOG="$T/configure.log"
631+
(DETECT_STUB_LOG="$DETECT_STUB_LOG" CONFIGURE_STUB_LOG="$CONFIGURE_STUB_LOG" \
632+
PATH="$T/stubs:$PATH" "$SCRIPT" >/dev/null 2>&1) || true
633+
if [ -f "$CONFIGURE_STUB_LOG" ] && ! grep -q -- "-temp" "$CONFIGURE_STUB_LOG"; then
634+
pass "pick without --*-temp flags does NOT forward temp args"
635+
else
636+
fail "pick leaked --*-temp args"
637+
fi
638+
fi
639+
608640
echo ""
609641
echo "=== Results: $PASS passed, $FAIL failed ==="
610642
[ "$FAIL" -eq 0 ] || exit 1

0 commit comments

Comments
 (0)