Skip to content

Conversation

@1orZero
Copy link

@1orZero 1orZero commented Jan 19, 2026

Summary

  • Add model resolution to bootstrap injection following OpenCode's documented priority
  • Ensures bootstrap uses the same model the session is configured to use

Problem

The Superpowers plugin injects a bootstrap prompt via session.prompt() without specifying a model or agent. This means OpenCode falls back to its internal default, which may differ from the model the user actually selected or configured.

Example of the issue

User selects GPT-5.2 Codex in the TUI:

image

But the bootstrap is processed by a different model (gemini-3-pro-preview):

image

The bootstrap injection used OpenCode's internal default instead of the user's selected model, causing the skill context to be processed by a completely different model than expected.

Solution

Added getBootstrapTarget() that resolves the expected model following OpenCode's documented priority:

  1. config.agent[default_agent].model — Agent-specific model (highest priority)
  2. Markdown-defined agent model — Via client.app.agents() for agents defined in .md files
  3. config.model — Global model setting
  4. Last used model — Fallback from ~/.local/state/opencode/model.json

Now passes both agent and model parameters to session.prompt() for proper alignment.

References

Previously getPreferredModel() only read from ~/.local/state/opencode/model.json
(last used model), ignoring user-configured agent and global models.

Now getBootstrapTarget() follows OpenCode's documented priority:
1. default_agent's configured model (config.agent[name].model)
2. Agent model from markdown-defined agents (via client.app.agents())
3. Global model (config.model)
4. Last used model (fallback)

Also passes agent parameter to session.prompt() for proper context.
@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

The pull request adds model and session state resolution capabilities to the bootstrap flow. It introduces helper functions to parse model references, read persisted model state, and resolve bootstrap targets from configuration or fallback sources, enabling the system to remember and reuse selected models across sessions.

Changes

Cohort / File(s) Summary
State Resolution & Model Persistence
.opencode/plugin/superpowers.js
Adds stateModelFile path configuration, introduces helper functions (parseModelRef, getLastUsedModel, getBootstrapTarget) to resolve bootstrap target from config hierarchy (default_agent → agent..model → agents list → global model → last used), persists model selection across sessions, and integrates resolved agent/model into injectBootstrap via await pattern with payload augmentation. Retains existing injection points at session.created and session.compacted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A model remembers its favorite hop,
Across sessions, the choice won't drop!
State files whisper what came before,
Bootstrap roots deeper than ever before,
Persistence hops on, forevermore!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main change: implementing proper model resolution priority in bootstrap injection by respecting OpenCode's documented hierarchy.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.opencode/plugin/superpowers.js:
- Around line 60-97: The top comment above getBootstrapTarget no longer matches
the actual resolution order—update it to reflect the current logic: first check
default_agent.model from config (config.agent[agentName].model parsed via
parseModelRef), then fallback to agents returned by client.app.agents
(markdown-defined agents) and parseModelRef on the matched agent, then fallback
to global config.model (parseModelRef), and finally use getLastUsedModel(); also
reflect the outer try/catch behavior that returns { agentName: 'build', model:
lastUsed } on errors.

Comment on lines +60 to +97
// Resolve the model the session is expected to use:
// default_agent.model (if set) -> global model -> last used.
const getBootstrapTarget = async () => {
const lastUsed = getLastUsedModel();

try {
const configRes = await client.config.get();
const config = configRes?.data ?? configRes;

const agentName =
typeof config?.default_agent === 'string' && config.default_agent.trim()
? config.default_agent.trim()
: 'build';

const agentModelFromConfig = parseModelRef(config?.agent?.[agentName]?.model);
if (agentModelFromConfig) return { agentName, model: agentModelFromConfig };

// Agents defined via markdown files might not appear under config.agent.
try {
const agentsRes = await client.app.agents();
const agents = agentsRes?.data ?? agentsRes;
if (Array.isArray(agents)) {
const agent = agents.find((a) => a?.id === agentName || a?.name === agentName);
const agentModelFromList = parseModelRef(agent?.model);
if (agentModelFromList) return { agentName, model: agentModelFromList };
}
} catch {
// ignore; fall back to other sources
}

const globalModel = parseModelRef(config?.model);
if (globalModel) return { agentName, model: globalModel };

return { agentName, model: lastUsed };
} catch {
return { agentName: 'build', model: lastUsed };
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update the resolution-order comment to match the code.

The inline comment omits the markdown-defined agent model check now present in the logic.

📝 Suggested comment fix
-  // Resolve the model the session is expected to use:
-  // default_agent.model (if set) -> global model -> last used.
+  // Resolve the model the session is expected to use:
+  // default_agent.model -> markdown-defined agent model -> global model -> last used.
🤖 Prompt for AI Agents
In @.opencode/plugin/superpowers.js around lines 60 - 97, The top comment above
getBootstrapTarget no longer matches the actual resolution order—update it to
reflect the current logic: first check default_agent.model from config
(config.agent[agentName].model parsed via parseModelRef), then fallback to
agents returned by client.app.agents (markdown-defined agents) and parseModelRef
on the matched agent, then fallback to global config.model (parseModelRef), and
finally use getLastUsedModel(); also reflect the outer try/catch behavior that
returns { agentName: 'build', model: lastUsed } on errors.

@obra
Copy link
Owner

obra commented Jan 22, 2026

Reproduction Attempt

I tried to reproduce this issue on OpenCode v1.1.31 (macOS) with the superpowers plugin installed via the documented symlink method.

Test Performed

cd /tmp
opencode run -m opencode/gpt-5-nano "What skills do you have? Use find_skills tool."

Result

The model specified (opencode/gpt-5-nano) was used correctly throughout the session. The bootstrap injection via session.prompt({ noReply: true }) did not cause any model or agent switching behavior.

Logs showed:

service=llm providerID=opencode modelID=gpt-5-nano sessionID=... agent=build mode=primary stream

Questions

To help reproduce this:

  1. What OpenCode version are you using?
  2. Are you using the TUI or CLI?
  3. What model/agent configuration do you have in opencode.json?
  4. Can you provide exact steps to reproduce the model switching?
  5. Does this happen on every session, or intermittently?

The 81 lines of model resolution code is substantial - I'd like to confirm the issue exists before merging a fix that might not be necessary.

@obra obra added the opencode label Jan 22, 2026
@obra
Copy link
Owner

obra commented Jan 22, 2026

Thank you for this contribution! However, this PR has been superseded by PR #330.

Context: PR #330 completely rewrote the bootstrap injection mechanism. Instead of using session.prompt() (which had model resolution issues as you correctly identified), it now uses the experimental.chat.system.transform hook:

'experimental.chat.system.transform': async (_input, output) => {
  const bootstrap = getBootstrapContent();
  if (bootstrap) {
    (output.system ||= []).push(bootstrap);
  }
}

This approach:

Since session.prompt() is no longer used for bootstrap injection, the model resolution logic in this PR is no longer necessary.

Closing as superseded by #330.

@obra
Copy link
Owner

obra commented Jan 22, 2026

Superseded by PR #330

@obra obra closed this Jan 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants