Skip to content

proposal: make opencode harness calls explicit #960

Description

@ddbaron

Proposal: make OpenCode harness calls explicit

Summary

I’d like to propose updating the AgentField OpenCode provider to build a
per-run OPENCODE_CONFIG_CONTENT overlay from the existing harness(...)
options. Each opencode run would select a fixed agent with --agent, attach
the system prompt and model to that agent, use steps: 500, apply the supplied
reasoning variant, and run with explicit noninteractive permissions.

This would let OpenCode receive the same intent that the caller already
expressed through the harness API, without changing SWE-AF-specific workflows.
SWE-AF is one consumer of this behavior, but the change should stay generic and
live in the AgentField OpenCode provider.

Problem

The harness already accepts system_prompt, tools, model, variant, cwd,
max_turns, and permission_mode. The OpenCode provider currently uses only a
subset of those values when it builds a command similar to:

opencode run --format json --dir <CWD> -m <MODEL> <PROMPT>

The system prompt is folded into the positional prompt as ordinary text,
tools is not translated into OpenCode permissions, and
permission_mode does not establish a reliable noninteractive boundary. As a
result, a run can depend on ambient OpenCode defaults, use an unintended agent,
duplicate system instructions, or wait for a permission approval that nobody
can provide.

Proposed change

For each standalone OpenCode subprocess, the provider would generate an
OPENCODE_CONFIG_CONTENT overlay with one fixed generic agent name, such as
agentfield-harness. The deployment can continue to own the shared settings
for plugins, providers, model limits, and compaction. Meaning: if you customize your opencode deployment already, this will pickup your global mcp, plugins, etc... and only superceed what is declared.

The generated agent should have this shape:

{
  "$schema": "https://opencode.ai/config.json",
  "default_agent": "agentfield-harness",
  "agent": {
    "agentfield-harness": {
      "prompt": "<SYSTEM_PROMPT>",
      "model": "<MODEL>",
      "mode": "primary",
      "steps": 500,
      "reasoningEffort": "<VARIANT>",
      "permission": {
        "*": "allow",
        "question": "deny",
        "task": "deny"
      }
    }
  }
}

reasoningEffort would be omitted when no reasoning variant is supplied. The
AgentField variant and OpenCode reasoningEffort values represent one
logical reasoning-effort setting. When it is supplied, the provider would use
the same value for the agent's reasoningEffort and the OpenCode --variant
command option rather than treating them as independent settings.

At the process boundary, the command would look like this:

OPENCODE_CONFIG_CONTENT=<GENERATED_CONFIG> opencode run --format json --agent agentfield-harness --dir <CWD> -m <MODEL> [--variant <VARIANT>] <PROMPT>

The provider would set OPENCODE_CONFIG_CONTENT in the child process
environment. The SWE-AF caller would continue to pass normal harness options;
it would not need to construct this shell command.

The provider should pass only the task prompt positionally. It should not add a
SYSTEM INSTRUCTIONS: section because the system prompt is now attached to the
OpenCode agent.

Turn and permission semantics

One important distinction is that max_turns must not be mapped to OpenCode
steps. They are different budgets: steps is OpenCode's internal agent
iteration limit and should remain a large, fixed value of 500, while
max_turns remains an AgentField/SWE-AF loop setting.

Map recognized AgentField tools to OpenCode actions as follows:

Read  -> read
Write -> edit
Edit  -> edit
Glob  -> glob
Grep  -> grep
Bash  -> bash

For the initial integration, I suggest an allow baseline rather than turning
every omitted tool into a deny rule. Explicitly deny only question and task,
so an autonomous run never waits for an approval that cannot be supplied. This
is a compatibility baseline, not yet strict per-role authorization.

That choice is driven by AgentField's current structured-output protocol:
schema-backed runs ask the agent to write .agentfield_output.json, and
OpenCode maps both AgentField Write and Edit to its edit permission. A
strict edit denial would therefore break structured output. Path-limited output
permissions or a different output transport can be considered separately.

Why I think this is useful

  • The system prompt becomes an actual OpenCode agent prompt instead of being
    repeated in the user-facing request.
  • --agent and default_agent make the selected agent deterministic.
  • The model remains available as an agent default while -m explicitly selects
    the model for a particular run.
  • Reasoning effort is represented consistently in the agent configuration and by
    the per-run variant option.
  • Headless runs can proceed autonomously instead of blocking on permission
    prompts.
  • Per-run configuration lives in the subprocess environment, so concurrent
    runs do not need to share and mutate a temporary config file.

Strategy and scope

I’d keep the first change deliberately narrow:

  1. Add the OpenCode config-overlay construction to the AgentField OpenCode
    provider.
  2. Preserve the existing model and directory options, adding --agent and an
    optional --variant.
  3. Make variant an explicit harness option on SDK surfaces where it is not
    already typed, while preserving existing model-variant behavior.
  4. Keep max_turns outside the OpenCode configuration.
  5. Add provider tests for the generated configuration, command construction,
    prompt handling, permission defaults, fixed steps: 500, and variant
    handling. Maintain Python, Go, and TypeScript parity where those providers
    expose the same harness contract.

I would leave opencode serve/--attach, a role/profile registry, per-role MCP
authorization, and a replacement structured-output transport out of the first
patch. Attached sessions load configuration on the server and would need a
separate per-session configuration design.

Acceptance criteria

  • A standalone OpenCode run receives the generated OPENCODE_CONFIG_CONTENT.
  • The selected agent, system prompt, model, and permissions are visible in the
    effective OpenCode configuration.
  • steps is always 500 in this overlay; max_turns is never serialized as
    steps.
  • A supplied reasoning variant is represented as reasoningEffort in the
    agent configuration and as --variant on the command line.
  • question and task are denied, with no generated ask permissions.
  • The positional prompt contains the task only, not a second copy of the
    system prompt.
  • Existing caller/deployment environment and provider behavior remain
    compatible when no OpenCode-specific overrides are supplied.

Feedback requested

Before implementing this, I’d appreciate confirmation that
OPENCODE_CONFIG_CONTENT is the right extension point for per-run OpenCode
configuration, that the fixed agentfield-harness name is acceptable, and that
Python, Go, and TypeScript provider parity is expected for this change. If this
direction looks right, I plan to prepare it as a focused provider-and-tests PR.

note: gpt luna helped me cleanup the proposal and make it (hopefully) more presentable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions