Repository navigation
Feature: Add Declarative Forced Evaluation Hooks (Markdown/Bash-Only) for Reliable Skill Activation #38
Description
Motivation
... skills are supposed to activate autonomously based on their descriptions. ... In practice? Nah. ... Forced hook never completely failed a category (most consistent).
Problem Summary
The OpenCode-Skills plugin enables dynamic discovery and registration of skills as tools, but activation relies on agent-initiated tool calls, resulting in inconsistent usage rates—approximately 20% in benchmark scenarios, as observed in ClaudeCode environments. The referenced blog post demonstrates that ClaudeCode's Forced-Eval Hooks achieve up to 84% activation rates by enforcing a structured evaluation and activation sequence prior to implementation.
However, replicating this in OpenCode requires users to extend the plugin with TypeScript event handlers, which imposes an undue development burden and deviates from the declarative, low-code ethos of skill-based workflows.
Proposed Solution
Extend the OpenCode-Skills plugin to natively support declarative forced-evaluation hooks, allowing users to inject evaluation logic without writing or modifying TypeScript code.
Introduce a dedicated directory, .opencode/skills/.hooks/, where users can place simple markdown files (e.g., 00-some.md, 02-things.md). Upon plugin initialization or session start, the plugin scans this directory, processes the files (inserting markdown content directly as noReply: true system messages), and injects the content at an early lifecycle point—such as before skill tool registration or the first tool execution—to enforce the blog's three-step process:
- Evaluate available skills,
- Activate relevant ones via tool calls, and
- Proceed to implementation only after activation.
The injection logic must adapt the prompt to OpenCode conventions, dynamically substituting placeholders for discovered skills and mapping activations to skills_{name} tool calls (e.g., replacing Skill(svelte5-runes) with skills_svelte5_runes).
This approach mirrors ClaudeCode's hook simplicity while leveraging OpenCode's message insertion pattern for seamless integration.
Implementation Considerations
- Detection and Injection: During plugin load (in
src/index.ts), scan.opencode/skills/.hooks/*.md|.shrecursively, then sort files. For.mdfiles, read and inject content verbatim as a system message withnoReply: true. For.shfiles, execute via$.spawn(with sanitized environment to prevent side effects) and capture stdout for injection. Prioritize markdown for static prompts; fallback to bash for dynamic generation (e.g., querying available skills via plugin APIs). - Prompt Adaptation: Implement a template engine in the plugin to replace placeholders like
<available_skills>with a comma-separated list of discovered skills (e.g., from theskillsmap) and<activation_prefix>withskills_. Ensure the injected prompt includes the full three-step sequence with aggressive commitment phrasing to maximize adherence. - Configuration: Add an opt-in mechanism in
opencode.json, such as"skills_hooks": { "enabled": true, "primary_hook": "forced-eval.md" }, allowing users to specify a default hook or disable entirely. Support multiple hooks with execution order based on filename (e.g., alphabetical). - Benefits: Provides a ClaudeCode-equivalent user experience with 80%+ activation uplift, as benchmarked; promotes declarative configuration; eliminates TypeScript barriers, enabling non-developers (e.g., AI prompt engineers) to enhance reliability.
- Potential Challenges: Injection may introduce token overhead (mitigate with concise templates and optional truncation); bash execution poses security risks (address via allowlisting commands and input sanitization); injection ordering could conflict with other plugins (use a configurable priority in the event system). Defaults should disable hooks unless explicitly enabled, with logging for debugging.
User Adoption Example
To replicate the blog post's .claude/hooks/skill-forced-eval-hook.sh scenario, users need only create a single markdown file, forced-eval.md, in the .opencode/skills/.hooks/ directory (or enable via opencode.json). This file's content is injected as a system message early in the session, enforcing the evaluation sequence adapted for OpenCode tools. No other changes are required.
Complete content of forced-eval.md:
INSTRUCTION: MANDATORY SKILL ACTIVATION SEQUENCE FOR OPENCODE
Step 1 - EVALUATE (do this in your response):
For each skill in <available_skills>, state: [skill-name] - YES/NO - [reason]
Step 2 - ACTIVATE (do this immediately after Step 1):
IF any skills are YES → Use skills_[skill-name] tool for EACH relevant skill NOW
IF no skills are YES → State "No skills needed" and proceed
Step 3 - IMPLEMENT:
Only after Step 2 is complete, proceed with implementation.
CRITICAL: You MUST call skills_[skill-name] tool in Step 2. Do NOT skip to implementation.
The evaluation (Step 1) is WORTHLESS unless you ACTIVATE (Step 2) the skills.
Example of correct sequence:
- research: NO - not a research task
- svelte5-runes: YES - need reactive state
- sveltekit-structure: YES - creating routes
[Then IMMEDIATELY use skills_ tool:]
> skills_svelte5-runes
> skills_sveltekit-structure
[THEN and ONLY THEN start implementation]
The plugin automatically replaces <available_skills> with the list of discovered skills (e.g., "research, svelte5-runes, sveltekit-structure") during injection, ensuring the prompt is tailored to the project context. For dynamic needs (e.g., conditional logic), users can opt for a bash equivalent like forced-eval.sh, which echoes the above template after generating the skills list via ls .opencode/skills/ | sed 's|/||g' | tr '\n' ','.
Acceptance Criteria
- Users can enable forced evaluation by placing only a
forced-eval.mdfile in.opencode/skills-hooks/and setting"skillsHooks": { "enabled": true }inopencode.json; no TypeScript modifications required. - The plugin injects the hook content as a
noReply: truesystem message before the first tool execution, with placeholders correctly substituted (e.g., tool names prefixed withskills_). - In blog-style benchmarks (e.g., skill-relevant prompts), activation rates for
skills_{name}tools reach ≥80%, verifiable via session logs or tool call traces. - Bash hooks execute securely (e.g., no arbitrary code injection) and produce equivalent prompts when markdown is unavailable.
- Hook injection is configurable and skippable, with no regressions in baseline skill discovery/registration.
- Documentation updated with the user example, including setup steps and troubleshooting for token limits.
Additional Context
- Related Resources:
- Suggested Labels: enhancement, user-experience, declarative-config, ai-integration
- Priority: High (improves core usability for skill-based agents)
- Estimated Effort: Medium (leverages existing message insertion; requires directory scanning and template logic)
Activity
malhashemi commentedon Dec 10, 2025
Thanks for the detailed proposal and the research behind it! Let me explain why I think this falls outside the scope of opencode-skills, and offer some alternatives that might work better for you.
1. Architectural Mismatch
<available_skills>is per-agent, and OpenCode allows users to swap primary agents mid-session. A session-start injection would be stale the moment someone switches agents. This makes the proposed hook mechanism fundamentally incompatible with OpenCode's multi-agent architecture. ThenoReplyapi also send user messages only, I do not think there is an already made API for the plugins to manipulate system prompts, but would have to confirm.Additionally, OpenCode already injects available tools into each agent's system message automatically. The information is already there, the question is whether the agent uses it.
2. This Is a Generic Prompt Injection Plugin
What you're describing: reading markdown files and injecting them at session start, is general-purpose functionality that has nothing to do with skills specifically. OpenCode's plugin system already supports this pattern. Here's a minimal implementation:
That's it. Put your
forced-eval.mdin.opencode/prompt-hooks/and you're done. This keeps concerns separated: opencode-skills handles tool registration, your injection plugin handles prompting strategy.3. The Real Lever Is Skill Descriptions
The blog's results are compelling, but I'd argue they're solving the wrong problem. Forced evaluation is a mechanical workaround for poorly described skills. The model doesn't activate skills because it doesn't understand when they're relevant.
I'd challenge you to test this hypothesis. Create a skill with two different descriptions:
Boring description:
Engaging description:
Run identical prompts against both. I'd bet the second version approaches that 80% activation rate without any injection hooks, because you're giving the model the context it needs to make good decisions, rather than forcing a mechanical checklist.
4. Why I'm Resistant to Baking This In
The philosophy of this plugin is feature parity with Claude Code's skills system, exposing skills as callable tools. Prompt injection strategies are orthogonal to that. They're also:
I'd rather keep opencode-skills focused and let users compose their own prompting strategies.
Happy to help you refine descriptions or debug your injection plugin if you go that route. Would be curious to see your results if you try the description experiment! I am also keeping this issue open for further discussions
taqtiqa-mark commentedon Dec 10, 2025
Without this Claude Code skills feature, would the plugin have feature parity? IIUC the author does not extend CC or Skills to respect the hook. It is a native feature available by default.
I believe this can be addressed by choosing the OC event that gives you as-close-to-CC-parity as possible.
I think you have a point, but limited in the following way. Unfortunately the LLM is not autonomous. It is conditioned on the user input. Hence the efficacy of the skill description will always be conditional on the same user input. The forced eval-hook should rarely lower hit rates, but how much it improves will always be a conditional value.
You may be right that the author has inflated the hit-rate improvement by using skill descriptions that are tangential (weakly correlated) to the user input (and what the LLM interpolates and extrapolates from that input).
No complaints there. I agree no default hook should be distributed until CC does that.
Hmmm, I use linux. But I could start by bootstrapping the kernel, then iterating over: "putting x in y" until I have a functional system..... or I could install a distro and use my time on things like this feature request.
When i find the time, or have a urgent need i'll re-open.