feat: let agents declare a plan_persona instruction#3
Draft
trungutt wants to merge 1 commit into
Draft
Conversation
In plan mode (added in PR docker#3140), the runtime filters the agent's toolset to read-only tools and injects a per-turn system reminder. For agents whose canonical instruction is heavily tuned for execution ("act now", "never ask clarifying questions", "fix files immediately"), layering a 'plan, don't act' reminder on top leaves two contradictory specs in the same context: the model can still push to an action-oriented conclusion even with the mutating tools hidden. Add an optional plan_persona block on AgentConfig that lets the agent author replace the per-turn plan-mode system reminder with a persona instruction tailored for planning. The runtime wraps the persona in the existing <system-reminder> envelope and prefixes a short guardrail line stating that only read-only tools are available, so persona authors own the workflow framing and tone while the read-only contract stays runtime-owned. Agents that don't declare plan_persona fall back to the canned reminder, preserving today's behaviour. agents: root: instruction: | You are an executor. Act now. plan_persona: instruction: | You plan. You do not execute. Iterate with the user.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Plan mode (docker#3140) is the host-facing primitive: a session flag a host (CLI, TUI, embedder) flips to put the agent into a research-and-draft posture instead of an act-now one. Two layers enforce it: the runtime strips every tool without
Annotations.ReadOnlyHint, and a per-turn<system-reminder>tells the model to plan rather than act.That works as a host primitive — but it leaves a real prompt conflict when the agent's canonical instruction is heavily tuned for execution. Many real-world coding-agent YAMLs read like the example below:
Layering a "no edits, no shell, no state changes, ask the user" reminder on top of that gives the model two contradictory specs in the same context. Even with the mutating tools hidden, the action-oriented framing leaks into behaviour: the model still pushes to a one-turn conclusion, refuses to iterate, and drops the plan in execution-completion shape. The PR title and the
<system-reminder>envelope alone aren't enough — the upstream instruction needs to change too.This PR adds the missing knob without disturbing the host-level primitive: agents can declare a
plan_personawhose instruction the runtime swaps in for the plan-mode reminder.What
flowchart LR A[session.Mode = plan] --> B{agent.PlanInstruction set?} B -- "no, today's behaviour" --> C["canned plan-mode reminder<br/>(no edits / no shell / etc.)"] B -- "yes (new)" --> D["<system-reminder><br/>guardrail line +<br/>persona instruction"] C --> E[GetMessages extras] D --> Elatest.AgentConfiggains an optionalPlanPersona *PlanPersonaConfig. The only field today isinstruction. The block is intentionally minimal so follow-ups can addmodel,toolsets, etc. additively without locking the shape.agent.Agentcarries aplanInstruction stringand exposesPlanInstruction()/WithPlanInstruction(...). The canonicalInstruction()is untouched — the persona is a per-mode override, not a replacement.runtime/plan_mode.go'splanModeReminderMessagestakes the active agent. When the agent has declared a persona, the runtime wraps it in the existing<system-reminder>envelope and prefixes a short guardrail line stating that only read-only tools are available, so persona authors own the workflow framing and tone while the read-only contract stays runtime-owned and impossible for the persona author to drop. When no persona is declared, the canned reminder is used unchanged — preserving today's behaviour for every agent that hasn't opted in.teamloaderexpands${env.X}placeholders in the persona instruction the same way it does forInstruction.agent-schema.jsonaddsplan_personato the agent properties and aPlanPersonaConfigdefinition.Example:
In build mode the agent runs the executor instruction. In plan mode the runtime sees
PlanInstruction()is non-empty, emits the persona-wrapped reminder, and the executor instruction is no longer the most recent system context — the persona is.Scope
In: schema field, runtime swap, teamloader wiring, agent-schema.json entry, unit tests at every layer (agent opt round-trip, runtime reminder selection incl. nil-agent / whitespace-only persona / canned-fallback, end-to-end teamloader YAML→
Agent.PlanInstruction()).Out (left to follow-ups):
plan_persona.modelfor per-mode model overrides.plan_persona.toolsetsfor per-mode toolset overrides.plan_exittool — orthogonal, on top of this.Dependencies
Stacked on docker#3140 (session-scoped plan/build mode). The new branch is based on that PR's commit; once docker#3140 lands this can be rebased on
mainand reviewed as a single commit.