Skip to content

Let plugins give standing instructions to every agent turn - #3285

Merged
loganj merged 4 commits into
mainfrom
loganj/plugin-turn-instructions
Nov 11, 2026
Merged

loganj merged 4 commits into
mainfrom
loganj/plugin-turn-instructions

Conversation

@loganj

@loganj loganj commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

🤖

Summary

  • Plugins could already add tools for agents, but they had no way to tell agents how or when to use them, or to set other standing rules. Tool descriptions are the only place that guidance can go, and a one-time note for the next turn is treated as data, not instructions.
  • Plugins can now give standing instructions: ctx.agents.addInstructions({ id, text, when? }). While the plugin is on, every turn of the agents this copy of the app runs includes the text in a section named after the plugin. The agent follows it like its own instructions, but it cannot override Buzz's base prompt or the owner's requests.
  • Every instruction is auditable. Each plugin's text gets its own section, labeled with that plugin's id (<plugin-instructions plugin="…">), so an agent's transcript shows exactly which plugin told it what, on which turn. A plugin's text cannot open or close a section, so it cannot add unlabeled instructions or pose as another plugin.
  • Turning the plugin off, or a when that no longer matches, removes the instructions from the next turn. The base prompt tells agents that each turn carries the complete current set of plugin instructions, so a long-running session stops following instructions it saw in earlier turns.

Details

  • Where it shows. The section sits at the end of each turn, before the end-of-turn attention note, in Claude, Codex, goose and Pi turns. It also appears in turns that installed harness plugins build with ctx.agents.harness.turnPrompt, in watch and timer turns, and in messages steered into a running turn. Every harness already ends its turns through one shared helper, so the change lives in that one place. Mesh agents build their own prompt and do not get the section.
  • Why per turn, not the system prompt. Instructions in the system prompt would apply only to new sessions. Changing them would mean restarting sessions (Claude keeps one process per conversation). Per-turn text applies on the next turn with no restart. It costs tokens on every turn, so the docs ask authors to keep it short. Harness-native prompt hooks (registerHook with UserPromptSubmit) work for only one harness each, and only the first plugin that answers is used.
  • Scoping. when(agent) is asked every time a turn's prompt is built. Only a literal true admits the instructions. A when that throws, or that returns a Promise, leaves out only that plugin's instructions. Sections are sorted by plugin and id, so the order plugins start in does not change the prompt.
  • Framing. The plugin's text cannot open a section, or close its own section, the turn's event section, or the steering section. This keeps the prompt well formed. It is not a trust boundary: plugins are trusted code, and the docs say the text should be the plugin's own, not content from others.
  • Types. AgentInstructions is exported from @buzz/author. AgentHandle.instructions() returns the set that is current for a turn.

🤖 Generated with Claude Code

@loganj
loganj marked this pull request as ready for review November 11, 2026 22:48
@loganj
loganj requested a review from a team as a code owner November 11, 2026 22:48

@johnmatthewtennant johnmatthewtennant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed d806283 and found no correctness blockers in the completed review. Approving at John’s explicit request. Automated coverage remains incomplete at 22/24 outcomes because Builderbot failed to resolve the PR from a detached checkout.

loganj and others added 4 commits November 11, 2026 18:26
ctx.agents.addInstructions({ id, text, when? }) adds a
<plugin-instructions plugin="..."> section to every turn of the agents
this copy runs, or of those `when` accepts, while the plugin is on.
Every harness already ends each turn with turnNote, so the section is
added there; standalone watch and timer turns add it in wakePrompt.
The base prompt tells agents to follow these like their own
instructions, below the base prompt and the owner's requests.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
A session keeps its history, so instructions from a plugin that was
turned off stayed in earlier turns with nothing saying they had ended.
The base prompt now says each turn carries the complete current set,
and a turn without sections means none apply.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
An async `when` returned a Promise, which passed the truthy check and
gave every agent the instructions; only `true` admits them now. A
plugin's text could also close the turn's event or steer section,
which made steering wrap the whole turn; those closing tags are escaped
too. The docs now name the harnesses that show the section (not Mesh),
and tests cover Codex and the anchored/standalone wake split.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
A plugin's text could open a section that looked like another plugin's.
Section openers are now escaped too, so each section in a transcript
names the plugin that gave it. The docs paragraph is cut to the size of
its siblings.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
@loganj
loganj force-pushed the loganj/plugin-turn-instructions branch from d806283 to 32702df Compare November 11, 2026 23:30
@loganj
loganj merged commit e807781 into main Nov 11, 2026
64 checks passed
@loganj
loganj deleted the loganj/plugin-turn-instructions branch November 11, 2026 23:47
joahg added a commit to joahg/buzz-app that referenced this pull request Nov 12, 2026
* origin/main: (38 commits)
  Fix reminder counts with compact dock corner tabs (block#3126)
  Add conditional undo for explicit reads (block#3291)
  fix(composer): edit channel links as atomic chips (block#3312)
  fix(agents): show remote instruction limit (block#3325)
  NEXUS-4033: Offer session skills in the core task composer (block#2790)
  feat(github): name pasted repository and commit links (block#3268)
  Let plugins give standing instructions to every agent turn (block#3285)
  fix(relay): read a channel's creation once, not per metadata version (block#3244)
  fix(messages): remove unconfirmed sends from this device (block#3317)
  fix: expose proven non-delivery for artifacts (block#3300)
  feat(plugins): let plugins mark a person's name in @mention suggestions (block#3260)
  fix(mcp): strip metadata from images agents attach (block#3311)
  fix(identity): name a linked key that isn't here, and keep Restore after a failed backup check (block#3238)
  feat(changesets): show the files a local agent changed on its reply (block#3214)
  fix(identity): show a backed-up key as backed up, and drop dead retries (block#3223)
  fix(browser): open Builderlab account connections in the system browser (block#3287)
  Expose exact captured-message reads (block#3264)
  docs(agents): keep internal discussion out of public repo content (block#3258)
  fix(identity): clear a copied private key, name restore failures, fix storage copy (block#3236)
  fix(channels): list workspaces that load after `#` opens (block#3277)
  ...
loganj added a commit that referenced this pull request Nov 12, 2026
🤖
## Summary
- Plugins can give standing instructions to every agent turn
(#3285). That text goes with
**every** turn, so this PR bounds the size of each one:
`ctx.agents.addInstructions` now throws when its text is over 4096 UTF-8
bytes after trimming, the same way it already throws on empty text.
- 4 KB matches the limit on a watch classifier's true/false text. It is
about four times the attention note, which is the only other text the
app sends on every turn. A turn still carries every instruction that
applies; there is no total limit per turn.

## Details
- `addInstructions` now registers the `id`, `text` and `when` it
checked, not the object the plugin passed in.
- The docs (`docs/plugin-architecture.md`,
`docs/plugin-authoring-guide.md`) give the limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Logan Johnson <[email protected]>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants