Skip to content

Design additive prompt layering for app agent personas #34774

Open

Description

@opencode-agent

Summary

OpenCode V2 currently treats a configured agent system prompt as a replacement for the model-family OpenCode system prompt. That is surprising for embedded/chat products such as OpenCode Agent: a short Discord/Slack persona can accidentally remove the baseline coding-agent behavior.

We should design a clearer prompt layering model before changing behavior.

Current observed behavior

In V2 request assembly, a non-empty agent system replaces the provider/model-family prompt:

system: [
  agent.info?.system ? agent.info.system : SessionRunnerSystemPrompt.provider(model),
  system.baseline,
]

So a Discord persona like “be a concise teammate in chat” becomes the privileged system prompt instead of being added after the standard OpenCode prompt.

Design directions to gang-grill

  1. Additive app/persona instructions by default

    • Always include SessionRunnerSystemPrompt.provider(model).
    • Add app/channel persona as a separate later system part.
  2. Split replacement from additions

    • Example fields: systemOverride vs instructions / persona.
    • Replacement stays possible, but has to be explicit.
  3. Named prompt stack

    • Model/provider base prompt
    • Selected agent behavior
    • App/channel persona
    • Durable session baseline/context
    • UI/debug surfaces can show each layer separately.

Why this matters

  • Embedded agents need product/chat persona without losing core OpenCode behavior.
  • Debugging “why did the agent behave this way?” needs visibility into the effective prompt stack.
  • Future app integrations should not have to copy/paste or manually preserve the base coding prompt.

Source discussion

Discord thread: https://discord.com/channels/1391832426048651334/1521895759694270584

This issue is intentionally tagged gang-grill so we can collect design topics and later run a focused team discussion without pinging everyone immediately.

Activity

  1. kitlangton commented on Jul 4, 2026

    @kitlangton
    Contributor

    Replacement semantics are still present and covered by tests, so the design question remains open. #34941 now gives embedders an additive per-session context seam, while #34721 tracks the concrete agent-system bug.

    The remaining scope here is the agent configuration contract: explicit override versus additive instructions, ordering relative to provider and durable context, and whether the effective prompt stack needs inspection/debug visibility.

  2. opencode-agent commented on Jul 5, 2026

    @opencode-agent
    ContributorAuthor

    Consolidated #34721 here. Preserve its concrete requirement: hosted/custom agents need a supported additive persona/instruction path without dropping the stock provider/model prompt. Verify both V2 and any still-applicable current/V1 behavior after the design lands.

  3. geril07 commented on Oct 17, 2026

    @geril07
    Contributor

    Drafted with an AI assistant (opencode). The code references and captured request output were checked against this repository; the reasoning and wording are mine to defend.

    Update: the fallback changed after this issue was opened, and it changes the tradeoff

    This was going to be a separate issue, but this is the right place for it.

    #42638 (2026-08-14) replaced the fallback that this discussion assumes. At the time this issue was filed, the fallback was per-family persona — gpt.txt, anthropic.txt, gemini.txt at 100–155 lines each. That is why "replacement" was defensible: an app persona and a 150-line model prompt are two competing answers to the same question, and the author should pick one.

    That is no longer true. The fallback is now session/runner/prompt/system.txt, 14 lines, plus per-tool rules derived from the tools the request actually advertises:

    // packages/core/src/session/model-request.ts
    system: [
      input.agent.system
        ? input.agent.system
        : SessionSystemPrompt.make(input.tools.definitions.map((tool) => tool.name)),
      input.initial,
    ]

    The fallback is not persona anymore

    This is the part I would add to direction 1 in the summary. The rules SessionSystemPrompt.make splices in are tool contracts, not style:

    - Use the edit tool for targeted changes to existing text files. It replaces the exact text
      in `oldString` with `newString`, and the values must differ. By default, `oldString` must
      occur exactly once. ... set `replaceAll` to true to replace every occurrence.
    - Prefer dedicated tools over shell commands; fall back to the shell when a tool cannot do what you need.
    - Use the write tool to create files or completely replace their content. Prefer using the edit tool for targeted changes.
    

    These describe how the tools behave. An author adding a persona is not expressing a competing preference about how to write code — they are adding information the model does not otherwise have. Dropping them is a correctness loss, not a tradeoff.

    Evidence

    Two agents in ~/.config/opencode/agents/, same location, same tools advertised, same model. auditor has a two-line body, runner is frontmatter only.

    LLMRequest.system[0] for auditor:

    You are an auditor. You inspect code and report findings. You never edit anything.
    

    LLMRequest.system[0] for runner:

    You are an AI agent running in OpenCode, a coding agent harness. Help the user accomplish their goals using the tools you have available.
    
    # Harness
    - Responses are rendered as GitHub-flavored Markdown.
    - `<system-reminder>` blocks are harness instructions, not user-authored content. Read and follow them.
    - Prefer parallelizing independent tool calls.
    - Prefer dedicated tools over shell commands; fall back to the shell when a tool cannot do what you need.
    - Do not chain shell commands with separators like `echo "====";` or `printf '---';` ...
    - Use the write tool to create files or completely replace their content. Prefer using the edit tool for targeted changes.
    - Use the edit tool for targeted changes to existing text files. It replaces the exact text in `oldString` with `newString`, ...
    
    # Communication
    ...
    # Working in codebases
    ...
    

    A subagent that writes code has no description of how edit resolves an ambiguous match.

    Additive insertion already ships

    Direction 1 in the summary — "add app/channel persona as a separate later system part" — has precedent one slot over. packages/core/src/plugin/identity.ts:

    event.system.splice(1, 0, SystemPart.make(identity({ name: model.name, ref: event.model })))

    Model identity is layered on top of whatever the agent contributed. Agent role can use the same insertion without new machinery.

    Ordering, since the summary lists it as open scope

    Recommended:

    [0] harness + tool guidance    derived from the tool snapshot
    [1] agent role                 agent.system
    [2] model identity             existing splice
    [3] durable baseline           AGENTS.md, skills, date, <env>
    [4] plugin extras
    

    Two reasons:

    • Cache. With the harness at [0], agents that share a tool snapshot share the prefix. Today a custom agent's system[0] is unique, so the shared prefix diverges at byte zero per agent.
    • Stable before volatile. The role is stable; the baseline churns daily. Role above baseline means role changes invalidate cache instead of baseline changes invalidating everything below it.

    Tool guidance stays folded into [0] rather than becoming its own layer, because it is conditional on the advertised tools. Telling a model about edit while the permission set denies edit would make the prompt lie.

    On direction 2, two notes

    • The opt-out does not need an enum. Replacement is the non-default, so systemMode: "replace" | undefined is enough — "prepend" is the default behavior and naming it adds surface to argue about.
    • title, summary and compaction are the real consumers of full replacement. They deny all tools, so the tool guidance is already empty for them, but harness prose about "working in codebases" and parallelizing tool calls is noise in a title generator. They would set the opt-out explicitly.

    One case that dissolves under additive

    An empty body is currently indistinguishable from a missing one. packages/core/src/config/plugin/agent.ts assigns markdown.content.trim() unconditionally, so a frontmatter-only agent file yields system: "", which is falsy at the ternary above — the agent looks configured and silently runs the stock prompt. Under an additive default, "no role" is just a valid empty layer and this needs no separate fix.


    Filed separately as #56402 and closed as a duplicate of this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

2.0gang-grillDesign topics queued for later group grilling

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions