Howto: Learning Capture for opencode
What it does
opencode automatically captures failed bash commands and stores them as learnings. When you run the same failing command again, you get a warning with the previous error and any recorded correction. Both opencode and Claude Code share the same learning store.
Architecture
opencode plugin (~/.config/opencode/plugin/terraphim-hooks.js)
|
|--- tool.execute.before
| |---> terraphim-agent guard --json (safety, can throw)
| |---> terraphim-agent learn hook ...pre-tool-use (warn on past failures)
| |---> terraphim-agent replace --role ... --json (rewrite git commits / npm->bun)
|
|--- tool.execute.after
| |---> terraphim-agent learn hook --format opencode
|
|
v
Shared learning store
(~/.config/terraphim/learnings/)Prerequisites
terraphim-agentinstalled at~/.cargo/bin/terraphim-agent- opencode v1.4.6+ installed (
bun install -g opencode-ai)
Setup
The plugin is already installed at ~/.config/opencode/plugin/terraphim-hooks.js. No additional configuration needed — opencode auto-loads all .js files from ~/.config/opencode/plugin/ and ~/.config/opencode/plugins/.
Verify it works
# 1. Trigger a failing command inside opencode
# 2. Check the learning was captured
Expected output:
Learnings matching 'nonexistent-test-command-xyz'.
[G] [cmd] nonexistent-test-command-xyz (exit: 127)How learning capture works
Capture (automatic)
When any bash command fails (non-zero exit code) inside opencode, the tool.execute.after hook fires and sends the command, output, and exit code to terraphim-agent learn hook --format opencode.
The hook:
- Checks
input.toolis"bash"(lowercase) - Extracts the command from
input.args.command - Gets the raw output from
output.output(string) - Parses exit code from output text (opencode doesn't provide exit code directly)
- Constructs structured JSON and pipes to
terraphim-agent learn hook
Pre-tool-use warnings (automatic)
When you run a command that previously failed, the tool.execute.before hook queries past learnings. Instead of printing warnings to the terminal (which interrupts the user), hints are silently written to ~/.local/share/terraphim/session-hints.txt so they can be consumed by the LLM in subsequent turns without cluttering the UI.
This design keeps the user interface clean while still surfacing past failures and corrections to the model.
Correction (manual)
Add corrections to teach the system what to do instead:
# Record that 'npm install' should be 'bun install'
# Record that a specific command has a known fix
Query (manual)
Search past learnings:
# Search by keyword
# Search exactly
# List recent learnings
Safety guard (automatic)
The tool.execute.before hook runs terraphim-agent guard --json on every bash command before it executes. If the command matches a destructive pattern (git reset --hard, rm -rf, docker rm -f, etc.) the guard returns {"decision":"block","reason":"..."} and the plugin throws, preventing opencode from executing it.
To bypass a legitimate block, run the command directly in your terminal outside opencode.
Command rewriting (automatic)
The tool.execute.before hook also runs terraphim-agent replace to apply knowledge-graph-driven rewrites:
- Git commit messages are rewritten using the role's thesaurus.
- Package manager commands (
npm,yarn,pnpm,pip,pip3,pipx) are rewritten if the knowledge graph has a mapping (e.g.npm installtobun add).
Rewrite mode is controlled by TERRAPHIM_REWRITE_MODE:
suggest(default): logs the rewrite to~/Library/Application Support/terraphim/rewrites.log; only git commits are applied.apply: all rewrites are applied automatically.
Rewrite role is set by TERRAPHIM_REWRITE_ROLE (default: "Terraphim Engineer").
Key implementation details
Why Bun.spawnSync, not Bun $ shell
The plugin MUST use Bun.spawnSync() for all terraphim-agent calls, NOT Bun's $ shell template. The $ shell corrupts bash tool execution in opencode, causing every bash command to fail with "expected a command or assignment but got: Redirect".
Case sensitivity
opencode passes input.tool as "bash" (lowercase). The plugin uses input.tool?.toLowerCase() !== "bash" for matching.
Exit code extraction
opencode's tool.execute.after hook provides output.output as a raw string (no structured exit code). The plugin extracts exit codes by:
- Checking
output.metadata?.exitCodeoroutput.metadata?.exit_code - Matching
exit code: Npatterns in output text - Falling back to heuristic (error indicators like
command not found,Error:,FAILEDin output)
Disabled plugins
Plugin files ending in .disabled (e.g. terraphim-hooks.js.disabled, learning-capture.js.disabled) are not loaded by opencode. Rename to .js to re-enable.
Plugin file structure
~/.config/opencode/
plugin/
terraphim-hooks.js # Main plugin: safety + learning + KG replacement
terraphim-hooks.js.disabled # Previous version (disabled)
subagent-start.js # Injects .docs/summary.md into subagent context
plugins/
advisory-guard.js # Advisory-level safety guard (non-blocking warnings)
fff.js # Fast file finder
safety-guard.js # Additional safety guard
learning-capture.js.disabled # Earlier standalone learning capture (superseded)Troubleshooting
Learnings not being captured
- Check terraphim-agent is installed:
- Check the plugin loads without errors:
Expected: [ "TerraphimHooks", "default" ]
- Verify with a diagnostic plugin:
# Run opencode, trigger a bash command, then check:
# Clean up when done:
All bash commands fail with "Redirect" error
This means a plugin is using Bun's $ shell template, which corrupts bash tool execution. Ensure all terraphim-agent calls use Bun.spawnSync() instead.
See also
- Learning Capture for Claude Code — same learning store, different hook registration (shell scripts vs Bun plugins)
- Command Rewriting How-To — deeper walkthrough of the knowledge-graph-driven
replacepipeline