Repository navigation
feat(core): add agent instructions and use them for Plan mode - #63669
rekram1-node wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Works as described. In the TUI with the simulated model, switching build → Plan → build now sends the Plan rules once before the next prompt and the generic "no longer selected" note once after leaving. The system prompt stays byte-identical across switches. On the base, the old <system-reminder> user messages are sent instead. plan.test.ts and session-runner.test.ts pass (267/267), and tsc -b passes for core and its test config.
Public API: adds an optional instructions?: string to Agent.Info. This is in the schema, the plugin agent editor, the generated client AgentInfo and the HTTP agent responses. It is backwards compatible: existing plugins and clients are unaffected, and agent config files don't accept it yet. The Plan reminders change from user-role <system-reminder> synthetics to instruction updates; anything matching the old reminder text will no longer find it.
One thing to fix before merging: packages/protocol/openapi.json wasn't regenerated, so bun run check:generated fails (see inline).
I couldn't run the mock-model repro from #62628 here, because the source CLI's run doesn't start in this sandbox. The context hook that caused that loop is removed by this change, though.
| request: Provider.Request, | ||
| system: Schema.String.pipe(optional), | ||
| /** Added to the default prompt while the agent is selected, without replacing it like `system`. */ | ||
| instructions: Schema.String.pipe(optional), |
There was a problem hiding this comment.
This field changes Agent.Info in the HTTP API, but packages/protocol/openapi.json wasn't regenerated: bun run check:generated in packages/protocol fails with "Generated OpenAPI document is stale" (it passes on dev). Running bun run generate there adds the 3-line instructions property.
There was a problem hiding this comment.
The new commit regenerates openapi.json, so the stale-spec point is fixed: check:generated now passes.
I also ran the #62628 mock-model repro from source. On the base, opencode run --agent plan with a 3,000-token context limit never exits (1,873 requests in 40s, alternating compaction and answer). With this PR it exits after 3 requests: title, one compaction, the answer.
Public API impact is unchanged since my last review: it adds an optional instructions field to Agent.Info (schema, plugin agent editor, client AgentInfo, OpenAPI spec), and that is backwards compatible. No new concerns.
822403d to
52681c3
Compare
There was a problem hiding this comment.
The new push is a rebase onto dev now that #63667 is merged; the PR's own commits are unchanged. The openapi.json point was already fixed by the regenerated spec, and check:generated passes.
On the rebased head I ran the #62628 mock-model repro again: the base still loops between compaction and answer, and this PR exits after 3 requests. Plan and runner tests pass (267/267), and the core typecheck passes.
Public API impact is unchanged: it adds an optional instructions field to Agent.Info (schema, plugin agent editor, client AgentInfo, OpenAPI spec), and that is backwards compatible. No new concerns.
There was a problem hiding this comment.
The new commit works as described. In a TUI run with the simulated model (build → Plan → build), Plan's rules are sent once before the next prompt and its own "You are NO LONGER in Plan mode…" text once after switching away. The #62628 repro still exits after one compaction. check:generated, the Plan and runner tests (269/269) and the core typecheck all pass.
Public API: Agent.Info.instructions (new in this PR) changes from string to string | { active: string; inactive?: string } in the schema, the plugin agent editor, the client AgentInfo and the OpenAPI spec. That is backwards compatible, since the field hasn't shipped yet. Anything reading the field now has to handle both shapes.
No new concerns.


Issue for this PR
Closes #62628. That loop came from Plan re-adding its reminder as a synthetic after each compaction, which woke the session again. This PR removes that code path, and after compaction the rules sit in the system prompt with no new message. The review bot ran the issue's mock-model repro with a 3,000-token context limit: on
devit never exits (1,873 requests in 40s), and with this PR it exits after 3 requests (title, one compaction, the answer).Related: #62554 (this removes the one-off Plan reminder it lists; the other one-off prompts are unchanged).
The revert case below relies on #63667, which is merged.
Type of change
What does this PR do?
Agents get an
instructionsfield: text that's added to the default prompt while the agent is selected, without replacing the prompt likesystemdoes. It's either a string or{ active, inactive? }:activeis a state: it's shown whenever the agent is selected, including in the system prompt after compaction.inactiveis a transition: it's sent once when the session switches away and never again, so after compaction on another agent neither text appears. Plan mode now uses it in place of its own reminder machinery.Today a plugin can't add rules for one agent without trouble. Plan injects "you are in Plan mode" reminders as synthetic messages and repairs them on every request after compaction or a revert (#42652). The internal orchestrator agent adds its rules to the system prompt in a context hook, so every agent switch rewrites the start of the prompt and loses the cache, and the rules aren't stored with the session.
Core already has the right mechanism: instruction sources, which are stored per session, shown once in the system prompt, and sent as an update at the next step when they change. This adds one source,
core/agent, built from the selected agent:Compaction, reverts, forks and storage all come from the existing instruction state. The source is last in the list because it changes most often.
plan.tsnow setsinstructionsand drops the event subscription, the per-request repair hook and the reminder bookkeeping (about 60 lines). Its permissions and the error rewrite for blocked edits stay.What changes for Plan, beyond the plumbing:
<system-reminder>wrapper, since they're now system content.The field is plugin-only for now. Agent config files have their own schema, and config already uses
instructionsfor a list of file paths, so exposing this there needs a naming decision first.How did you verify your code works?
New runner scenarios under
agent instructionsinsession-runner.test.tsuse real agents andswitchAgent: starting in an agent, switching in, switching to an agent without instructions, switching away and back, switching between two agents, a plugin editing the text, a custom inactive text (including that it isn't shown after compaction), editing only the inactive text (sends nothing), compaction, reverting past the switch in, and reverting between switching in and out. All pass, and the system prompt stays byte-identical across switches.bun run checkfrom the root.devon my machine.AgentInfo.instructions).Screenshots / recordings
Not a UI change; the timeline row is the existing instruction notice.
Checklist
Added by the opencode bot (AI-generated).
Fixes #62628
Related: #62554
Public API: adds optional
instructions(string or { active, inactive? }) to Agent.Info (schema, plugin agent editor, client AgentInfo, OpenAPI); backwards compatible