A plugin for when you really want to make sure your agents never forget about super important things.
Here are examples of nudges I use in my workflows which improve agent code-quality ; because I remind them of this ALL the time.
TDD: YOU MUST respect the RED / GREEN principle - you ALWAYS write a FAILING test case FIRST - this will ENSURE your changes actually fix the problem.Do NOT use MOCKING, EXCEPT when calling external vendors incurs a real life $$$ COST; otherwise we must stick with REAL e2e test cases at the outer-most user-facing SEAM.You MUST define acceptance criteria in the GivenWhenThen form UNLESS the current project's style guidelines DICTATE otherwise.Be very wary of creating too many test cases. Test the inputs and the outputs of what the user will do at the seams of the application. No more, no less. We don't want to test cases that are extreme outliers.Do NOT add test docstrings, or comments, or module docstrings unless ABSOLUTELY necessary.Do NOT use the question tool at you disposal, it is extremely slow.git clone https://github.com/kevincojean/opencode-supernudge.git
cd opencode-supernudge
npm installAdd to ~/.config/opencode/opencode.jsonc plugin array, replacing <PATH_TO_SUPERNUDGE> with the absolute path to your clone:
"plugin": [
"<PATH_TO_SUPERNUDGE>"
]Create ~/.config/opencode/opencode-supernudge/supernudge-configuration.jsonc:
{
"prompts": ["~/prompts/style.md", "~/prompts/constraints.md"],
"injection.interval": 1,
"injection.alwaysOnFirstMessage": true,
"injection.resetCounterOnCompaction": true,
"injection.subagentInterval": 1,
"injection.subagentAlwaysOnFirst": true,
"injection.subagentResetOnCompaction": true,
"position.normalMessage": "start",
"position.subagent": "start",
"position.compaction": "start",
"enabled.normalMessage": true,
"enabled.subagentSystemPromptNudge": true,
"enabled.subagentAutonomousWorkNudge": true,
"enabled.compaction": true,
"wrapper.prefix": "<opencode-supernudge>",
"wrapper.suffix": "</opencode-supernudge>"
}Project-local config
A project-local config file can overlay the global config. Two global config keys control this:
| Key | Type | Default | Description |
|---|---|---|---|
currentWorkingDirectory.configFilePath |
string | "./.opencode/com.kevincojean.opencode-supernudge/supernudge-configuration.jsonc" |
Path to local config (relative to opencode cwd) |
currentWorkingDirectory.configEnabled |
boolean | true |
Master switch for local config lookup |
When enabled, the plugin looks for a local config file. If found and valid, the local config's prompts array is merged with the global config's prompts array.
| Key | Locally overridable? |
|---|---|
prompts |
YES (concatenated with global) |
**Keys overridable within prompt objects:
| Prompt-key | Overridable? |
|---|---|
injection.interval |
YES |
injection.alwaysOnFirstMessage |
YES |
injection.resetCounterOnCompaction |
NO |
position.normalMessage |
YES |
position.subagent |
YES |
position.compaction |
YES |
enabled.normalMessage |
YES |
enabled.subagentSystemPromptNudge |
YES |
enabled.subagentAutonomousWorkNudge |
YES |
enabled.compaction |
YES |
wrapper.prefix |
NO |
wrapper.suffix |
NO |
nudge.separator |
NO |
nudge.enableTitlePrefix |
NO |
nudge.trim |
NO |
injection.skipFirstMessageBelowChars |
NO |
injection.subagentInterval |
YES |
injection.subagentAlwaysOnFirst |
YES |
injection.subagentResetOnCompaction |
NO |
currentWorkingDirectory.configFilePath |
NO |
currentWorkingDirectory.configEnabled |
NO |
Example local config:
{
"prompts": [
".opencode/com.kevincojean.opencode-supernudge/project-specific-nudge.md",
{
"path": ".opencode/com.kevincojean.opencode-supernudge/project-specific-nudge.md/tdd.md",
"injection.interval": 3,
"enabled.compaction": false
}
]
}Prompt file paths in the prompts array support 3 modes:
- Home directory - paths starting with
~or containing$HOME:
{ "prompts": ["~/prompts/tdd.md", "$HOME/prompts/no-mocking.md"] }-
Relative paths - resolved against opencode's working directory:
{ "prompts": ["./prompts/tdd.md", "prompts/no-mocking.md", "../shared/constraints.md"] } -
Absolute paths - paths starting with
/are used as-is:{ "prompts": ["/home/user/prompts/tdd.md"] }
Subagent nudge paths
There are two distinct paths for injecting nudges into subagent contexts. They are gated separately and have independent defaults:
- System prompt nudge (
enabled.subagentSystemPromptNudge, defaulttrue): injects the nudge into the subagent's initial system prompt viaexperimental.chat.system.transform. Fires once per subagent session, before the subagent starts working. This is the path that gives the subagent the nudge "at the system prompt level" you observe. - Autonomous work nudge (
enabled.subagentAutonomousWorkNudge, defaulttrue): injects the nudge periodically during subagent autonomous turns (viaexperimental.text.complete+experimental.chat.messages.transform). Does NOT fire on the primary agent'schat.message- the primary path usesenabled.normalMessageinstead.
To disable periodic nudges, set:
{
"enabled.subagentAutonomousWorkNudge": false
}The legacy key
enabled.subagentis no longer recognized. If your config still has it, replace it with the twoenabled.subagent*keys above.
Per-prompt override
Any prompt entry can be an object that overrides specific global settings for that prompt only. String entries use all global defaults.
{
"prompts": [
// String: global defaults apply
"~/prompts/tdd.md",
// Object with partial overrides: only listed keys override globals
{
"path": "~/prompts/givenwhenthen.md",
"injection.interval": 3,
"injection.skipFirstMessageBelowChars": 10
},
// Object with all overrides: every global setting overridden
{
"path": "~/prompts/no-mocking.md",
"enabled.compaction": false,
"enabled.normalMessage": true,
"enabled.subagentSystemPromptNudge": false,
"enabled.subagentAutonomousWorkNudge": false,
"injection.alwaysOnFirstMessage": false,
"injection.interval": 1,
"injection.resetCounterOnCompaction": false,
"injection.skipFirstMessageBelowChars": 10,
"injection.subagentInterval": 1,
"injection.subagentAlwaysOnFirst": false,
"injection.subagentResetOnCompaction": false,
"position.compaction": "end",
"position.normalMessage": "end",
"position.subagent": "end"
}
],
"injection.interval": 1,
"injection.alwaysOnFirstMessage": true
}tdd.md- string, all global defaultsgivenwhenthen.md- partial override, injects every 3rd message onlyno-mocking.md- full override, all globals ignored, uses its own settings exclusively
Skipping injections in system contexts
You may chose for nudges to not fire using regexes.
For instance, the oh-my-openagent plugin injects prompts when sub-agents finish a task, and it's wasteful to inject in those messages.
Use injection.skipOnRegexMatch to suppress nudges when text contains markers:
{
"injection.skipOnRegexMatch": [
"<\\/?system-reminder>"
]
}| Key | Type | Default | Description |
|---|---|---|---|
prompts |
(string | object)[] |
[] |
Prompt file paths. String or { path: string, ...overrides }. Supports ~, $HOME interpolation, and relative paths (resolved against opencode's working directory). |
injection.interval |
number |
1 |
Inject every Nth user message. 1 = every message. 2 = 1st, skip 2nd, inject 3rd. |
injection.alwaysOnFirstMessage |
boolean |
true |
Force inject on first user message regardless of interval. |
injection.resetCounterOnCompaction |
boolean |
true |
Reset per-session counter to 0 when compaction fires. |
position.normalMessage |
"start" | "end" |
"start" |
Nudge placement in user message. start = before user text. end = after. |
position.subagent |
"start" | "end" |
"start" |
Nudge placement in subagent workflows. |
position.compaction |
"start" | "end" |
"start" |
Nudge placement in compaction context. |
enabled.normalMessage |
boolean |
true |
Inject nudge on user messages. |
enabled.subagentSystemPromptNudge |
boolean |
true |
Inject nudge into subagent's initial system prompt (via experimental.chat.system.transform). Fires once per subagent session. |
enabled.subagentAutonomousWorkNudge |
boolean |
true |
Inject nudge periodically during subagent autonomous turns (via experimental.text.complete + experimental.chat.messages.transform). Does NOT fire on the primary agent's chat.message. |
enabled.subagentAutonomousWorkNudge |
boolean |
true |
Inject nudge periodically during subagent autonomous turns (via experimental.text.complete + experimental.chat.messages.transform). Does NOT fire on the primary agent's chat.message. |
enabled.compaction |
boolean |
true |
Inject nudge into context on compaction. |
wrapper.prefix |
string |
"<opencode-supernudge>" |
Marker inserted above each nudge block. Empty string disables. |
wrapper.suffix |
string |
"</opencode-supernudge>" |
Marker inserted below each nudge block. Empty string disables. |
nudge.separator |
string |
"\n\n" |
Separator between multiple nudges within the same block. |
nudge.enableTitlePrefix |
boolean |
true |
Prefix each nudge with [filename] (lowercase, no extension). |
injection.skipFirstMessageBelowChars |
number |
3 |
Skip injection when user message length <= this threshold. |
injection.skipOnRegexMatch |
string[] |
[] |
Skip injection if message text matches any regex. |
injection.subagentInterval |
number |
1 |
Inject every Nth autonomous turn. 1 = every turn. 2 = 1st, skip 2nd, inject 3rd. |
injection.subagentAlwaysOnFirst |
boolean |
true |
Force inject on first autonomous turn regardless of interval. |
injection.subagentResetOnCompaction |
boolean |
true |
Reset autonomous counter to 0 when compaction fires. |
All parameters except prompts can be overridden per-prompt by including them in the prompt object.
Test
# Unit tests
node --import tsx --test src/test/supernudge.test.ts
# E2E tests (spins up opencode serve with an internal stub LLM server)
# SN_E2E_NO_BWRAP=1 is set by default in package.json to avoid bwrap process kill issues
# Remove it from package.json to use bwrap sandboxing instead
npm run test:e2eYou can see what I have in mind for next features by reading my humble kanban board.
MIT Kévin Cojean
{ "prompts": [ "~/.config/opencode/snippet/tdd.md", "~/.config/opencode/snippet/no-mocking.md", "~/.config/opencode/snippet/givenwhenthen.md", "~/.config/opencode/snippet/test-pollution.md", "~/.config/opencode/snippet/docstring-pollution.md", "~/.config/opencode/snippet/disable-question-tool.md" ] }