fix(sister-agent): trivial tier ghost pipeline + openclaw model override 거부

## Bug 1: trivial tier 가 산출물 없는 유령 파이프라인 생성

planner.ts 의 trivial 케이스가 strategy="direct" + spawn=[] 였음. 그런데
spawn.ts 의 maybeExtractFiles() 는 role !== "junior" 면 파일을 저장하지
않고, manager 의 프롬프트는 "본인이 직접 코드를 짜지 않는다" 로 박혀 있어,
trivial 태스크는 아무도 코드를 안 쓰는 상태로 done 처리됨.

수정: trivial tier 도 single-junior 로 강제. junior 1 명이 무조건 코드를
생성하게 함. 이전 "direct" 전략은 의도적으로 사용 안 함.

재현: "간단한 todo 앱 만들어" → score 10 → tier=trivial → ghost pipeline
검증: smoke-todo-v3 파이프라인이 implement/files/frontend/sprints/SPRINT-AUTO/
      index.html (115 줄) 을 실제로 생성함

## Bug 2: openclaw 어댑터가 모델 override 로 OpenClaw 거부됨

LLM 어댑터 리팩터 시 spawn.ts 에서 callLlm 에 model: ROLES[role].primaryModel
을 명시하게 했는데, 그 모델 이름 (gpt-5.4 / glm-5-turbo 등) 이 OpenClaw
agent="main" 의 allowlist 에 없어서 "Model override not allowed" 로 거부됨.

수정: openclaw 어댑터는 --model 플래그를 더 이상 넘기지 않음. OpenClaw 의
자체 라우팅에 모델 선택을 위임. 다른 어댑터 (openai/anthropic/ollama) 는
그대로 req.model 을 honor 함.

검증: smoke-todo-v3 의 narang junior 가 LLM 호출 성공, 실제 HTML 생성
This commit is contained in:
2026-04-10 23:41:12 +09:00
parent e4f8e6ef53
commit 4a77f43a32
2 changed files with 21 additions and 4 deletions

View File

@@ -21,8 +21,13 @@ export class OpenClawAdapter implements LlmAdapter {
}
async infer(req: LlmRequest): Promise<LlmResult> {
// Note: we intentionally do NOT pass `--model` to openclaw. OpenClaw has
// its own per-agent model allowlist and routing logic, and overriding it
// with rails-side role names like `gpt-5.4` / `glm-5-turbo` causes
// "Model override not allowed for agent main" errors. Other adapters
// (openai/anthropic/ollama) still honor req.model — only this adapter
// delegates model selection back to the runtime.
const args = ["infer", "model", "run", "--prompt", req.prompt, "--json"];
if (req.model) args.push("--model", req.model);
return new Promise((resolveFn) => {
const child = spawn(this.bin, args, {

View File

@@ -32,13 +32,25 @@ export function planDecomposition(complexity: ComplexityScore): DecompositionPla
switch (tier) {
case "trivial":
// Even trivial tasks need a junior to actually produce code. Managers
// are planner-only by role definition and maybeExtractFiles() in
// spawn.ts only saves files from juniors. Without a junior the
// pipeline completes "successfully" with zero output — the classic
// ghost-pipeline bug. Spawn 1 junior to guarantee something lands.
return {
tier,
score,
strategy: "direct",
spawn: [],
strategy: "single-junior",
spawn: [
{
role: "junior",
count: 1,
rationale:
"Trivial task still needs one junior to produce actual output. Manager can't write code per role definition.",
},
],
notes: [
"Manager handles directly — no team needed for trivial tasks.",
"Even trivial tasks spawn one junior so the pipeline actually produces files.",
],
};