HOMELAB-1548: fix(subagents): honor explicit Agent model overrides, default fleet to deepseek-v4-flash #15

Merged
aaron merged 1 commit from plane/HOMELAB-1548-model-override into main 2026-09-14 16:11:32 +00:00
Owner

Fixes HOMELAB-1548.

Root cause

resolveAgentInvocationConfig() resolved the spawn model as agentConfig?.model ?? params.model — the agent type's frontmatter pin beat the explicit call param. Every agent in ~/.pi/agent/agents/*.md pins model: deepseek/deepseek-flash, so Agent({ model: "opencode-go/glm-5.3-flash" }) (and any other override) was silently ignored and ran deepseek-flash. This is also why the 2026-09-14 fleet contract documented "model overrides are NOT honored" — it was a precedence bug, not a harness limitation.

Changes

  • src/core/invocation-config.ts — precedence flipped: params.model ?? agentConfig.model. modelFromParams is true whenever the caller passed a model, so unresolvable/out-of-scope overrides now hard-error instead of silently falling back to the pin.
  • src/core/agent-runner.tsresolveDefaultModel() fallback chain is now frontmatter → machine default (defaultModel) → parent session model; exported for tests. getDefaultModel/setDefaultModel module state added.
  • src/core/settings.ts + src/suite-settings.ts — new core defaultModel knob (subagents.json, global+project merged; empty = inherit parent). Surfaced on the unified settings page.
  • src/core/index.ts — tool-layer resolution applies the same chain (so scope validation + UI model tag see the effective model); model: param description updated.
  • src/swarm/tiers.tssimple/balanced/premium default tier models → deepseek/deepseek-flash, so an omitted model:/tier: on agent_swarm never silently spawns the kimi fleet. Tiers remain configurable per settings.
  • README.md — documented the resolution chain + new knob + updated tier defaults.
  • Tests — new tests/core/invocation-config.test.ts (precedence both directions, loud failures), tests/core/default-model.test.ts (fallback chain, unavailable/slashless specs, parent last resort, state trimming), tier fixture updates.

Verification

  • npm run typecheck clean; npm test50 files / 403 tests passed.
  • Live headless pi against this working tree (scratch agent dir, -e <worktree>/pi-swarm-suite/src/index.ts):
    1. general-purpose (frontmatter pin deepseek/deepseek-flash) + model: "opencode-go/glm-5.3-flash" → transcript details.modelName: "glm-5.3-flash". Override honored (before: deepseek, modelName absent).
    2. Same agent + model: "bogus/nope-123"Model not found: "bogus/nope-123" returned, no spawn. Loud failure (before: silently ran the pin).
    3. verify-nomodel agent (no frontmatter pin) on a opencode-go/glm-5.3-flash parent, no param → ran deepseek v4.1 flash via defaultModel. Machine default engaged (before: inherited glm).

Machine default is set in ~/.pi/agent/subagents.json (defaultModel: "deepseek/deepseek-flash") — out-of-repo config, applied alongside this PR.

Fixes HOMELAB-1548. ## Root cause `resolveAgentInvocationConfig()` resolved the spawn model as `agentConfig?.model ?? params.model` — the agent type's **frontmatter pin beat the explicit call param**. Every agent in `~/.pi/agent/agents/*.md` pins `model: deepseek/deepseek-flash`, so `Agent({ model: "opencode-go/glm-5.3-flash" })` (and any other override) was silently ignored and ran deepseek-flash. This is also why the 2026-09-14 fleet contract documented "model overrides are NOT honored" — it was a precedence bug, not a harness limitation. ## Changes - **`src/core/invocation-config.ts`** — precedence flipped: `params.model ?? agentConfig.model`. `modelFromParams` is true whenever the caller passed a model, so unresolvable/out-of-scope overrides now **hard-error** instead of silently falling back to the pin. - **`src/core/agent-runner.ts`** — `resolveDefaultModel()` fallback chain is now **frontmatter → machine default (`defaultModel`) → parent session model**; exported for tests. `getDefaultModel`/`setDefaultModel` module state added. - **`src/core/settings.ts` + `src/suite-settings.ts`** — new core `defaultModel` knob (`subagents.json`, global+project merged; empty = inherit parent). Surfaced on the unified settings page. - **`src/core/index.ts`** — tool-layer resolution applies the same chain (so scope validation + UI model tag see the effective model); `model:` param description updated. - **`src/swarm/tiers.ts`** — `simple`/`balanced`/`premium` default tier models → `deepseek/deepseek-flash`, so an omitted `model:`/`tier:` on `agent_swarm` never silently spawns the kimi fleet. Tiers remain configurable per settings. - **`README.md`** — documented the resolution chain + new knob + updated tier defaults. - **Tests** — new `tests/core/invocation-config.test.ts` (precedence both directions, loud failures), `tests/core/default-model.test.ts` (fallback chain, unavailable/slashless specs, parent last resort, state trimming), tier fixture updates. ## Verification - `npm run typecheck` clean; `npm test` → **50 files / 403 tests passed**. - **Live headless pi against this working tree** (scratch agent dir, `-e <worktree>/pi-swarm-suite/src/index.ts`): 1. `general-purpose` (frontmatter pin `deepseek/deepseek-flash`) + `model: "opencode-go/glm-5.3-flash"` → transcript `details.modelName: "glm-5.3-flash"`. **Override honored** (before: deepseek, modelName absent). 2. Same agent + `model: "bogus/nope-123"` → `Model not found: "bogus/nope-123"` returned, no spawn. **Loud failure** (before: silently ran the pin). 3. `verify-nomodel` agent (no frontmatter pin) on a `opencode-go/glm-5.3-flash` parent, no param → ran `deepseek v4.1 flash` via `defaultModel`. **Machine default engaged** (before: inherited glm). Machine default is set in `~/.pi/agent/subagents.json` (`defaultModel: "deepseek/deepseek-flash"`) — out-of-repo config, applied alongside this PR.
- invocation-config: explicit Agent({model}) now wins over the agent type's
  frontmatter pin (was agentConfig.model ?? params.model); modelFromParams is
  set whenever the param is passed, so bad/out-of-scope overrides hard-error
  instead of silently running the pin.
- agent-runner: resolveDefaultModel() chain is now frontmatter → machine
  default (`defaultModel` in subagents.json) → parent; exported for tests.
- settings/suite page: new core `defaultModel` knob (empty = inherit parent).
- tiers: simple/balanced/premium defaults → deepseek/deepseek-flash so an
  omitted model/tier never silently spawns the kimi fleet.
- tests: precedence, fallback chain, fixtures; 403 tests green.

Verified live (headless pi, this tree): general-purpose (pin deepseek-flash)
with model opencode-go/glm-5.3-flash ran on glm-5.3-flash; bogus model
returned "Model not found" before spawning; a no-pin agent on a glm parent
ran on deepseek v4.1 flash via defaultModel.
aaron merged commit 142a8660ad into main 2026-09-14 16:11:32 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Homelab/pi-extensions!15
No description provided.