Skip to content

ACP and headless mode does not respect default agent definition #8680

@assagman

Description

@assagman

Description

The defaultAgent() function does not properly filter agents by their mode and visibility settings. This causes subagents (like explore) and hidden agents (like compaction) to be incorrectly selected as the default agent in ACP and headless (run) modes.

Root causes:

  1. Agent.defaultAgent() returns the first agent from state() without checking if it's a subagent or hidden
  2. ACP session init does not persist the resolved default mode, causing prompt() to use stale/wrong mode

Plugins

N/A

OpenCode version

0.0.0-dev-202601151329

Steps to reproduce

fresh config -> disable build agent -> set a custom default agent -> execute opencode run "echo back" -> it uses plan agent and tries to generate a plan

example jsonc config:

{
  "$schema": "https://opencode.ai/config.json",
  "default_agent": "yoda",
  "agent": {
    "build": {
      "disable": true,
    },
    "yoda": {
      "prompt": "say YODA! before every response.",
    },
  },
}

opencode run

current version output:

$ opencode run "echo back"
|  Write    .opencode/plans/1768489599174-witty-cabin.md

fixed version output:

$ bun run dev run "echo back"

YODA!
echo back

opencode acp

// acp-run.ts
import { spawn } from "bun"

const prompt = process.argv[3] || "echo back"

const testVersion = process.argv.find((arg) => arg.startsWith("--test-version="))?.split("=")[1] || "fixed"
console.log(`Using ACP version: ${testVersion}`)
const acpCommand =
  testVersion === "current"
    ? ["opencode", "acp"]
    : ["bun", "run", "--cwd", "./packages/opencode/", "--conditions=browser", "src/index.ts", "acp"]

const acp = spawn(acpCommand, {
  stdin: "pipe",
  stdout: "pipe",
  stderr: "ignore",
})

const send = (msg: object) => acp.stdin.write(JSON.stringify(msg) + "\n")

let id = 0
const rpc = (method: string, params: object) => send({ jsonrpc: "2.0", id: ++id, method, params })

// Initialize
rpc("initialize", { protocolVersion: 1, clientCapabilities: {} })

// Create session
rpc("session/new", { cwd: process.cwd(), mcpServers: [] })

// Read responses
const reader = acp.stdout.getReader()
const decoder = new TextDecoder()
let buffer = ""
let sessionId = ""

while (true) {
  const { done, value } = await reader.read()
  if (done) break

  buffer += decoder.decode(value)
  const lines = buffer.split("\n")
  buffer = lines.pop() || ""

  for (const line of lines) {
    if (!line) continue
    const msg = JSON.parse(line)

    // Get session ID from session/new response
    if (msg.result?.sessionId) {
      sessionId = msg.result.sessionId
      rpc("session/prompt", { sessionId, prompt: [{ type: "text", text: prompt }] })
    }

    // Print agent message chunks
    if (msg.params?.update?.sessionUpdate === "agent_message_chunk") {
      process.stdout.write(msg.params.update.content.text || "")
    }

    // Exit on completion
    if (msg.result?.stopReason === "end_turn") {
      console.log()
      process.exit(0)
    }
  }
}

In the project root, checked out to the fix branch in fix(agent): default agent selection in acp and headless modes #8678, execute this script in two different modes:

current opencode version, as-is:

[/path/to/opencode/fix-default-agent-selection]
$ bun acp-run.ts --test-version=current
Using ACP version: current

I'll help you understand the codebase and create a plan. Let me start by exploring the repository structure to understand what we're working with.

I'll help you understand the overall structure of this codebase. Let me start by exploring the repository structure and key files.

Now let me explore the main packages directory to understand the architecture:

...

fixed version:

[/path/to/opencode/fix-default-agent-selection]
$ bun acp-run.ts --test-version=fixed
Using ACP version: fixed

YODA! echo back%

Screenshot and/or share link

N/A

Operating System

macOS Tahoe 26.3 arm64

Terminal

ghostty 1.2.3
tmux next-3.5

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions