Feature hasn't been suggested before
Describe the enhancement you want to request
Problem
OpenCode's plugin hooks (tool.execute.before, tool.execute.after, session.idle) can intercept, block, and modify tool calls — but they cannot spawn an AI agent to perform analysis and return structured output. This makes it impossible to implement Claude Code's "type": "agent" hook pattern, where a pre-tool-use hook launches a sub-agent that uses tools (bash, read, grep) to analyze changes, then returns actionable context to the main agent.
Concrete Use Case: Documentation Sync on Commit
In Claude Code, users configure a PreToolUse hook like this:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "agent",
"if": "Bash(git commit*)",
"model": "haiku",
"timeout": 30,
"prompt": "Analyze staged changes. Check if CLAUDE.md, CHANGELOG.md, or docs/ need updates. Return specific suggestions in additionalContext."
}]
}]
}
}
When the main agent tries to git commit, the hook:
- Spawns a sub-agent (lightweight model like haiku)
- Sub-agent calls tools:
git diff --cached, reads files, analyzes changes
- Sub-agent returns structured
additionalContext to the main agent
- Main agent sees the context and updates documentation before committing
This is fundamentally different from:
The Gap
| Capability |
Claude Code "type": "agent" |
OpenCode Current |
| Block tool call |
✅ |
✅ tool.execute.before |
| Inject static text |
✅ (stderr) |
❌ (#19519 proposes this) |
| Spawn sub-agent from hook |
✅ |
❌ |
| Sub-agent uses tools |
✅ |
❌ |
| Return structured analysis to main agent |
✅ |
❌ |
| Main agent acts on returned context |
✅ |
❌ |
Proposed Solution
API Design
Extend tool.execute.before output with an agent option:
"tool.execute.before": async (input, output) => {
if (input.tool === "bash" && output.args.command?.includes("git commit")) {
output.agent = {
prompt: "Analyze staged changes and check if AGENTS.md or docs/CHANGELOG.md need updates. Return specific suggestions.",
model: "haiku", // optional, defaults to session model
timeout: 30000, // optional, defaults to 30s
tools: ["bash", "read", "grep", "glob"], // optional tool whitelist
}
}
}
When output.agent is set:
- The hook pauses the current tool execution
- A child session is created (reusing existing
Session.create({ parentID }) from TaskTool)
- The sub-agent runs with the provided prompt and tool whitelist
- The sub-agent's final text response is injected as
hookSpecificOutput.additionalContext into the main agent's conversation
- The main agent decides whether to proceed with the tool call, modify it, or abort
Hook Execution Flow
tool.execute.before hook fires
|
├── If output.agent is set:
| |
| ├── Pause current tool execution
| |
| ├── Create child session (parentID = current sessionID)
| | (reuses existing subagent infrastructure from TaskTool)
| |
| ├── Execute sub-agent prompt with restricted tools
| | (calls SessionPrompt.prompt with agent config)
| |
| ├── Collect sub-agent's final text output
| |
| ├── Inject output as additionalContext into main agent conversation
| | (as a synthetic system/user message visible to the LLM)
| |
| └── Resume main agent loop — LLM sees the context and decides action
|
└── If output.agent is NOT set: current behavior (block/modify args)
Implementation Approach
OpenCode already has all the building blocks:
-
Subagent infrastructure — TaskTool in packages/opencode/src/tool/task.ts already creates child sessions with Session.create({ parentID }) and calls SessionPrompt.prompt(). The reactive agent hook would reuse this exact pattern.
-
Plugin trigger system — Plugin.trigger() in packages/opencode/src/plugin/index.ts already handles hook dispatch. The output mutation pattern means adding output.agent is a natural extension.
-
Tool resolution — SessionPrompt.resolveTools() already builds tool maps filtered by agent permissions. The sub-agent's tool whitelist maps directly to this.
The key new code would be in SessionPrompt.resolveTools() where it wraps tools with hooks:
// In the tool wrapper (simplified from current code):
const afterHooks = await Plugin.trigger("tool.execute.before", input, output)
// NEW: check if hook requested a reactive agent
if (output.agent) {
const childSession = await Session.create({ parentID: sessionID, ... })
const result = await SessionPrompt.prompt({
sessionID: childSession.id,
agent: output.agent.model ?? "explore", // use fast agent by default
tools: output.agent.tools ?? ["bash", "read", "grep", "glob"],
parts: [{ type: "text", text: output.agent.prompt }],
timeout: output.agent.timeout ?? 30000,
})
// Inject result as context for the main agent
injectedContext = result.lastTextPart
}
// Existing: execute tool or throw if blocked
Relationship to Existing Issues
Why This Matters
The "type": "agent" pattern is the most powerful hook type in Claude Code. It enables:
- Documentation sync — Auto-check if docs need updates before commit
- Security review — Analyze staged changes for secrets/vulnerabilities before push
- Code quality gates — Lint, type-check, and suggest fixes before write operations
- Architecture compliance — Verify changes follow project patterns before allowing edits
- Test coverage checks — Verify tests exist for new code before allowing commit
Without this, OpenCode plugin hooks can only react (block/allow/modify), never analyze and advise. This is a qualitative difference in what the plugin system can achieve.
Scope
This could be implemented as:
- An extension to
tool.execute.before output (minimal API surface)
- A new dedicated hook like
"tool.execute.agent" (cleaner separation)
- A plugin-level utility function (e.g.,
client.agent.spawn())
Option 1 is the smallest change. Option 3 would be the most flexible (any hook could spawn an agent, not just tool.execute.before).
Feature hasn't been suggested before
Describe the enhancement you want to request
Problem
OpenCode's plugin hooks (
tool.execute.before,tool.execute.after,session.idle) can intercept, block, and modify tool calls — but they cannot spawn an AI agent to perform analysis and return structured output. This makes it impossible to implement Claude Code's"type": "agent"hook pattern, where a pre-tool-use hook launches a sub-agent that uses tools (bash, read, grep) to analyze changes, then returns actionable context to the main agent.Concrete Use Case: Documentation Sync on Commit
In Claude Code, users configure a
PreToolUsehook like this:{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "agent", "if": "Bash(git commit*)", "model": "haiku", "timeout": 30, "prompt": "Analyze staged changes. Check if CLAUDE.md, CHANGELOG.md, or docs/ need updates. Return specific suggestions in additionalContext." }] }] } }When the main agent tries to
git commit, the hook:git diff --cached, reads files, analyzes changesadditionalContextto the main agentThis is fundamentally different from:
tool.execute.beforeblocking — can prevent commit but can't analyze why or suggest what to updateoutput.inject()static text (feat: allow tool.execute.after hooks to inject AI-visible messages #19519) — injects a fixed string, not the result of AI analysissession.stoppingcontinuation (feat: add session.stopping hook for plugins #16598) — re-activates after stop, but doesn't help at pre-commit timeThe Gap
"type": "agent"tool.execute.beforeProposed Solution
API Design
Extend
tool.execute.beforeoutput with anagentoption:When
output.agentis set:Session.create({ parentID })fromTaskTool)hookSpecificOutput.additionalContextinto the main agent's conversationHook Execution Flow
Implementation Approach
OpenCode already has all the building blocks:
Subagent infrastructure —
TaskToolinpackages/opencode/src/tool/task.tsalready creates child sessions withSession.create({ parentID })and callsSessionPrompt.prompt(). The reactive agent hook would reuse this exact pattern.Plugin trigger system —
Plugin.trigger()inpackages/opencode/src/plugin/index.tsalready handles hook dispatch. Theoutputmutation pattern means addingoutput.agentis a natural extension.Tool resolution —
SessionPrompt.resolveTools()already builds tool maps filtered by agent permissions. The sub-agent's tool whitelist maps directly to this.The key new code would be in
SessionPrompt.resolveTools()where it wraps tools with hooks:Relationship to Existing Issues
"type": "agent"capability that Native Claude Code hooks compatibility (PreToolUse, PostToolUse, Stop) #12472 describes but doesn't have a concrete implementation plan for.tool.execute.afterinjection. Also static text injection. A reactive agent could use the inject API to deliver its output, but the agent execution itself is a separate concern.session.stoppinglifecycle event. Addresses Stop hook re-activation. This proposal addresses PreToolUse agent spawning. Together they cover all three Claude Code hook types.Why This Matters
The
"type": "agent"pattern is the most powerful hook type in Claude Code. It enables:Without this, OpenCode plugin hooks can only react (block/allow/modify), never analyze and advise. This is a qualitative difference in what the plugin system can achieve.
Scope
This could be implemented as:
tool.execute.beforeoutput (minimal API surface)"tool.execute.agent"(cleaner separation)client.agent.spawn())Option 1 is the smallest change. Option 3 would be the most flexible (any hook could spawn an agent, not just
tool.execute.before).