|
| 1 | +--- |
| 2 | +title: Custom Feedback Messages |
| 3 | +description: "How to customize the messages Plannotator sends to your agent when you approve, deny, or annotate plans and documents." |
| 4 | +sidebar: |
| 5 | + order: 29 |
| 6 | +section: "Guides" |
| 7 | +--- |
| 8 | + |
| 9 | +Every time you approve a plan, deny it with feedback, annotate a file, or finish a code review, Plannotator sends a message to the agent. These messages are what the agent actually sees and acts on. By default they work well, but you can change any of them to match how you want your agent to behave. |
| 10 | + |
| 11 | +All customization happens in `~/.plannotator/config.json` under the `prompts` key. No restart needed. Changes take effect the next time a feedback message is generated. You can set overrides that apply globally, or target a specific agent runtime (Claude Code, OpenCode, Pi, etc.) with [runtime-specific overrides](#runtime-specific-overrides). |
| 12 | + |
| 13 | +## Quick example |
| 14 | + |
| 15 | +Say you want a shorter, more direct plan denial message. Add a `prompts.plan.denied` override to your config file (`~/.plannotator/config.json`): |
| 16 | + |
| 17 | +```json |
| 18 | +{ |
| 19 | + "prompts": { |
| 20 | + "plan": { |
| 21 | + "denied": "PLAN REJECTED.\n\nFix these issues and resubmit via {{toolName}}:\n\n{{feedback}}" |
| 22 | + } |
| 23 | + } |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +Next time you deny a plan, the agent will see your custom message instead of the default. The `{{toolName}}` and `{{feedback}}` placeholders get filled in automatically. |
| 28 | + |
| 29 | +## What you can customize |
| 30 | + |
| 31 | +There are three sections: `plan`, `annotate`, and `review`. Each has its own set of message types. |
| 32 | + |
| 33 | +### Plan feedback |
| 34 | + |
| 35 | +These are sent when you approve or deny a plan in the review UI. |
| 36 | + |
| 37 | +| Key | When it's used | Available variables | |
| 38 | +|-----|---------------|-------------------| |
| 39 | +| `denied` | You deny a plan (with or without annotations) | `{{toolName}}`, `{{feedback}}`, `{{planFileRule}}` | |
| 40 | +| `approved` | You approve a plan without notes | `{{planFilePath}}`, `{{doneMsg}}` | |
| 41 | +| `approvedWithNotes` | You approve but include annotation notes | `{{planFilePath}}`, `{{doneMsg}}`, `{{feedback}}` | |
| 42 | +| `autoApproved` | Plan is auto-approved in non-interactive mode | none | |
| 43 | + |
| 44 | +### Annotation feedback |
| 45 | + |
| 46 | +These are sent when you annotate a file (`/plannotator-annotate`) or the last assistant message (`/plannotator-last`). |
| 47 | + |
| 48 | +| Key | When it's used | Available variables | |
| 49 | +|-----|---------------|-------------------| |
| 50 | +| `fileFeedback` | You annotate a file or folder | `{{fileHeader}}`, `{{filePath}}`, `{{feedback}}` | |
| 51 | +| `messageFeedback` | You annotate the last assistant message | `{{feedback}}` | |
| 52 | + |
| 53 | +### Review feedback |
| 54 | + |
| 55 | +These are sent during code review (`/plannotator-review`). |
| 56 | + |
| 57 | +| Key | When it's used | Available variables | |
| 58 | +|-----|---------------|-------------------| |
| 59 | +| `approved` | You approve a code review with no feedback | none | |
| 60 | +| `denied` | Appended after your review feedback | none | |
| 61 | + |
| 62 | +## Template variables |
| 63 | + |
| 64 | +Templates use `{{variable}}` placeholders. Here's what each one contains: |
| 65 | + |
| 66 | +| Variable | Description | |
| 67 | +|----------|-------------| |
| 68 | +| `{{feedback}}` | Your annotations, exported as structured text. This is the main content. | |
| 69 | +| `{{toolName}}` | The tool the agent needs to call to resubmit (`ExitPlanMode`, `submit_plan`, etc.). Varies by runtime. | |
| 70 | +| `{{planFileRule}}` | A conditional line telling the agent where the plan file is saved and how to edit it. Empty string when there's no file path. | |
| 71 | +| `{{planFilePath}}` | Path to the plan file being reviewed. | |
| 72 | +| `{{doneMsg}}` | Optional checklist instruction or save-path info, depending on the runtime. | |
| 73 | +| `{{fileHeader}}` | Either `"File"` or `"Folder"`, depending on what was annotated. | |
| 74 | +| `{{filePath}}` | Path to the annotated file or folder. | |
| 75 | + |
| 76 | +If you use a `{{variable}}` that doesn't exist for that message type, it stays in the output as-is. This means you can include literal `{{text}}` in your templates without worrying about it being stripped. |
| 77 | + |
| 78 | +## Runtime-specific overrides |
| 79 | + |
| 80 | +Different agent runtimes (Claude Code, OpenCode, Pi, Gemini CLI, etc.) sometimes need different messages. For example, OpenCode's plan approval is shorter because the agent already knows it has tool access. |
| 81 | + |
| 82 | +You can override a message for a specific runtime using the `runtimes` key: |
| 83 | + |
| 84 | +```json |
| 85 | +{ |
| 86 | + "prompts": { |
| 87 | + "plan": { |
| 88 | + "denied": "PLAN REJECTED.\n\n{{feedback}}", |
| 89 | + "runtimes": { |
| 90 | + "claude-code": { |
| 91 | + "denied": "Your plan was not approved. Address ALL feedback below, then call {{toolName}} again.\n\n{{feedback}}" |
| 92 | + }, |
| 93 | + "opencode": { |
| 94 | + "denied": "Plan rejected. Fix the following and call {{toolName}}:\n\n{{feedback}}" |
| 95 | + } |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +The resolution order is: |
| 103 | + |
| 104 | +1. Runtime-specific config override (e.g., `prompts.plan.runtimes.opencode.denied`) |
| 105 | +2. Generic config override (e.g., `prompts.plan.denied`) |
| 106 | +3. Built-in default (some prompts have runtime-specific built-in defaults, like OpenCode's shorter plan approval) |
| 107 | + |
| 108 | +Blank or whitespace-only values are treated as "not set" and fall through to the next level. This means you can clear a runtime override by setting it to `""` without affecting others. |
| 109 | + |
| 110 | +Valid runtime keys: `claude-code`, `opencode`, `copilot-cli`, `pi`, `codex`, `gemini-cli`. |
| 111 | + |
| 112 | +## Full config example |
| 113 | + |
| 114 | +Here's a config that customizes several messages at once: |
| 115 | + |
| 116 | +```json |
| 117 | +{ |
| 118 | + "prompts": { |
| 119 | + "plan": { |
| 120 | + "denied": "PLAN NOT APPROVED.\n\nRevise your plan based on this feedback, then resubmit via {{toolName}}.\n\n{{feedback}}", |
| 121 | + "approved": "Plan approved. Begin implementation.", |
| 122 | + "approvedWithNotes": "Plan approved with the following notes:\n\n{{feedback}}\n\nKeep these in mind as you implement." |
| 123 | + }, |
| 124 | + "annotate": { |
| 125 | + "fileFeedback": "# Annotations for {{filePath}}\n\n{{feedback}}\n\nPlease address these.", |
| 126 | + "messageFeedback": "{{feedback}}\n\nRevise your response based on these notes." |
| 127 | + }, |
| 128 | + "review": { |
| 129 | + "approved": "Code review passed. No changes needed." |
| 130 | + } |
| 131 | + } |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +## Defaults |
| 136 | + |
| 137 | +If you don't set any `prompts` config, everything works the same as it always has. The built-in defaults are the exact messages Plannotator has always sent. Here are the key ones for reference: |
| 138 | + |
| 139 | +**Plan denied (default):** |
| 140 | +``` |
| 141 | +YOUR PLAN WAS NOT APPROVED. |
| 142 | +
|
| 143 | +You MUST revise the plan to address ALL of the feedback below |
| 144 | +before calling {{toolName}} again. |
| 145 | +
|
| 146 | +Rules: |
| 147 | +{{planFileRule}}- Do not resubmit the same plan unchanged. |
| 148 | +- Do NOT change the plan title (first # heading) unless the |
| 149 | + user explicitly asks you to. |
| 150 | +
|
| 151 | +{{feedback}} |
| 152 | +``` |
| 153 | + |
| 154 | +**Plan approved (default, Pi runtime):** |
| 155 | +``` |
| 156 | +Plan approved. You now have full tool access (read, bash, edit, |
| 157 | +write). Execute the plan in {{planFilePath}}. {{doneMsg}} |
| 158 | +``` |
| 159 | + |
| 160 | +**Annotate file feedback (default):** |
| 161 | +``` |
| 162 | +# Markdown Annotations |
| 163 | +
|
| 164 | +{{fileHeader}}: {{filePath}} |
| 165 | +
|
| 166 | +{{feedback}} |
| 167 | +
|
| 168 | +Please address the annotation feedback above. |
| 169 | +``` |
| 170 | + |
| 171 | +## Example: context anchoring with a Decisions Log |
| 172 | + |
| 173 | +When you deny a plan multiple times, the agent only sees the current round's feedback. It can re-propose the same rejected approach without realizing it was already rejected. Martin Fowler's [context anchoring](https://martinfowler.com/articles/reduce-friction-ai/context-anchoring.html) pattern solves this by having the agent maintain a running log of rejected decisions directly in the plan document. |
| 174 | + |
| 175 | +You can implement this entirely through feedback customization. Add a `## Context Anchoring` section to your denial template that instructs the agent to keep a `## Decisions Log` in the plan itself: |
| 176 | + |
| 177 | +```json |
| 178 | +{ |
| 179 | + "prompts": { |
| 180 | + "plan": { |
| 181 | + "denied": "YOUR PLAN WAS NOT APPROVED.\n\nYou MUST revise the plan to address ALL of the feedback below before calling {{toolName}} again.\n\nRules:\n{{planFileRule}}- Do not resubmit the same plan unchanged.\n- Do NOT change the plan title (first # heading) unless the user explicitly asks you to.\n\n## Context Anchoring\n\nBefore revising your plan:\n1. Add (or update) a `## Decisions Log` section at the bottom of the plan.\n2. For each rejected approach from this feedback, add an entry:\n - **Rejected:** [brief description] **Why:** [reason from this feedback]\n3. Do NOT re-propose approaches already in the Decisions Log.\n\n{{feedback}}", |
| 182 | + "approved": "Plan approved. Begin implementation.\n\nIf your plan contains a `## Decisions Log`, keep it as a reference during implementation. It documents the rejected alternatives that shaped this design.", |
| 183 | + "approvedWithNotes": "Plan approved with the following notes:\n\n{{feedback}}\n\nKeep these in mind as you implement.\n\nIf your plan contains a `## Decisions Log`, keep it as a reference during implementation. It documents the rejected alternatives that shaped this design." |
| 184 | + } |
| 185 | + } |
| 186 | +} |
| 187 | +``` |
| 188 | + |
| 189 | +This works because the plan is already a persistent artifact (saved to version history on every submission). The Decisions Log travels with every revision, is visible in the plan diff view, and survives context window resets since it lives in the document, not just the conversation. |
| 190 | + |
| 191 | +*This example is based on a contribution by [Aviad Shiber](https://github.com/aviadshiber).* |
| 192 | + |
| 193 | +## Tips |
| 194 | + |
| 195 | +- Start by customizing `plan.denied` since that's the message agents see most often during iterative planning. |
| 196 | +- Keep the `{{feedback}}` variable in your templates. Without it, the agent won't see your annotations. |
| 197 | +- The denial message framing matters. Claude was tested to respond better to strong, directive language ("You MUST revise") than softer phrasing. If you soften the tone too much, you may notice the agent ignoring feedback or resubmitting unchanged plans. |
| 198 | +- You can test your templates by denying a plan and checking what the agent receives. The full message shows up in the agent's conversation. |
0 commit comments