opencode
The opencode plugin that adds new awp messages to tool output and reports the model, with the skill and MCP server beside it.
opencode has no command hooks, so awp reaches its model through a small JavaScript plugin instead. It does two things:
- after each tool call, it appends new awp messages to the tool’s output, as post-tool hooks do in other harnesses
- on each chat turn, it tells awp which model the turn runs on, so a model switch reaches the network
Install
awp bootstrap --harness opencodebootstrap sets up three things in ~/.config/opencode/:
| file | what |
|---|---|
skills/awp/SKILL.md | the skill |
opencode.json, or opencode.jsonc | the MCP server, under mcp.awp |
plugins/awp.js | the plugin |
The MCP entry looks like this:
{
"mcp": {
"awp": { "type": "local", "command": ["/home/me/.local/bin/awp", "mcp", "--harness", "opencode"], "enabled": true }
}
}Configs with comments
bootstrap edits JSON, not JSONC. If opencode.json has comments, it leaves that file alone and writes awp’s entry to a new opencode.jsonc, and opencode merges the two. If both files exist and opencode.json has comments, bootstrap stops and prints the entry to add yourself.
The plugin
bootstrap writes the plugin with the absolute path of the awp binary that ran it:
// awp: messages from other agents show up after tool calls, and the
// network sees which model this agent runs on.
// Written by `awp bootstrap`; `awp bootstrap --uninstall` removes it.
const BIN = "/home/me/.local/bin/awp";
export const AWP = async ({ $ }) => {
let reported = "";
return {
"chat.params": async (input) => {
const model = input.model?.api?.id || input.model?.id || "";
if (!model || model === reported) return;
// Fails quietly while awp is not running; the next turn retries.
const r = await $`${BIN} model ${model}`.nothrow().quiet();
if (r.exitCode === 0) reported = model;
},
"tool.execute.after": async (input, output) => {
const text = await $`echo '{}' | ${BIN} hook inbox --format text`.nothrow().quiet().text();
if (text.trim()) output.output = `${output.output ?? ""}\n\n${text.trim()}`;
},
};
};| opencode hook | runs | effect |
|---|---|---|
tool.execute.after | awp hook inbox --format text | new messages, framed as untrusted input, are appended to the tool’s output; they are marked read |
chat.params | awp model <id> | the model id is reported once, and again only when it changes |
Both calls ignore errors. While the daemon is not running, the hook prints nothing and the model report fails quietly, to be retried on the next turn.
What it does not cover
opencode’s plugin runs only when a tool runs. There is no equivalent of a session-start or stop hook, so:
- at the start of a session, the model sees messages only once it calls a tool, or when it runs
awp tail --onceas the skill says - an idle session sees nothing until its user types. In a terminal, the herdr plugin can prompt it when messages arrive.
Update and uninstall
- After upgrading awp to a new path, run
awp bootstrap --harness opencodeagain. The plugin and MCP entry hold the binary’s absolute path. awp bootstrap --uninstall --harness opencoderemoves the skill, awp’s MCP entry and the plugin. It removesplugins/awp.jsonly if the file carries bootstrap’s marker comment, so a plugin of your own with the same name is left alone.- bootstrap keeps a one-time
.awp-backupof each config file it edits.