graph TB
subgraph "Layer 1: Configuration"
YAML[opencode-semantic-anchors.yaml]
PRESETS[Role-based Presets]
SCHEMA[Schema Validation]
end
subgraph "Layer 2: Rule Engine"
LOADER[Config Loader]
MATCHER[Anchor/Contract Matcher]
EVAL[Verdict Evaluator]
STATE[Session State]
end
subgraph "Layer 3: Hooks"
TEB[tool.execute.before]
CM[chat.message]
AA[agent.activate]
TOOLS[/anchor bypass, status, config-reload]
end
YAML --> LOADER
PRESETS --> LOADER
SCHEMA --> LOADER
LOADER --> MATCHER
MATCHER --> EVAL
STATE --> EVAL
EVAL --> TEB
EVAL --> CM
EVAL --> AA
TOOLS --> STATE
4. Solution Strategy
Architecture Overview
The plugin follows a three-layer architecture, designed after a systematic review of existing solutions (Take before Buy before Make):
Layer 1 – Configuration: YAML file in the opencode config directory. Defines Structural Coupling Contracts as a combination of triggers (tool, message, state) and actions (BLOCK, WARN). Contains role-based presets for the 12 Semantic-Anchors roles.
Layer 2 – Rule Engine: Core logic. Validates config against schema, matches incoming events against active contracts, evaluates the verdict (ALLOW, BLOCK, WARN). Maintains session state (tool-call-count, override-count, role).
Layer 3 – Hooks & Tools: Thin adapters. tool.execute.before is the primary enforcement point. chat.message provides gentle reminders. agent.activate loads role presets. Custom tools enable bypass, status query, and config reload.
Source Anchor (source): opencode Plugin SDK documentation: https://opencode.ai/docs/plugins. See in particular the
Plugintype (function-based API) withhooksandtooldefinitions.
Key Technical Decisions
Decision 1: Enforcement via tool.execute.before (not permission.ask)
| Option | Problem |
|---|---|
|
Unstable in current opencode (Regression Issues #7006, #28066) |
|
Stable, can return allow/block, supports overrideTool |
Source Anchor (source): opencode GitHub Issues #7006 and #28066 concern regressions in the
permission.askhook. Status: June 2026. See https://github.com/opencode-ai/opencode/issues/7006 and https://github.com/opencode-ai/opencode/issues/28066. Additionally documented in our02-architecture-constraints.md.
Decision 2: Structural Coupling Contract instead of direct Anchor Enforcement
| Term | Definition | Usage in Plugin |
|---|---|---|
Semantic Anchor |
Knowledge the LLM already knows from training |
Optionally noted as source/reference in the contract ( |
Semantic Contract |
Project-local, machine-enforceable rule |
This is the actual enforcement unit |
Source Anchor (source): "Terms that don’t qualify as semantic anchors can still be useful as Semantic Contracts. A contract defines what a term means in your project — either by composing established anchors or by providing custom definitions that only exist within your team." — rejected-proposals.adoc, LLM-Coding/Semantic-Anchors Repository. https://github.com/LLM-Coding/Semantic-Anchors/blob/main/docs/rejected-proposals.adoc. Author: Ralf D. Müller (Maintainer). No author line in the file, but the commit history shows contributions by rweisleder and raifdmueller. See also Issue #370 for the evaluation table and Issue #518 (JensGrote) for the rename proposal "Structural Coupling Contract".
Decision 3: YAML Config (not JSON, not AsciiDoc)
YAML is:
- More readable than JSON for rule definitions
- Diff-friendly (one line change = one line diff)
- Already established for comparable approaches:
- agentcontract/spec uses .contract.yaml
- Kiros /steering uses YAML
- opencode itself uses opencode.jsonc (JSON with comments)
Take before Buy before Make: YAML as a format for agent-related contracts is already established in
agentcontract/spec. Source Anchor (source): AgentContract Specification. https://github.com/agentcontract/spec/blob/main/SPEC.md. Defines.contract.yamlas the standard format for Agent Contracts with pre/postconditions, invariants, and limits. Author: agentcontract/spec Contributors, MIT License. See Section 2 (Contract Format) for the YAML schema.We adopt the format, not the full schema — because
agentcontract/spectargets CI gates and framework-agnostic enforcement, while our plugin uses opencode-specific hooks.
Decision 4: Take before Buy before Make as Plugin Development Principle
Before writing code:
-
Take – Does something already exist that we can use directly? (e.g., opencode Plugin SDK)
-
Buy – Can we fork/integrate an open-source component? (e.g.,
agentcontract/specterminology, Semantic-Anchors anchor IDs) -
Make – Custom development only for the opencode-specific part (plugin hooks, YAML config loader)
Decision 5: Source Anchor as Architecture Principle
Every claim in the design document must be verifiable through a verbatim quoted source. This applies to: - Definitions (Anchor vs. Contract → rejected-proposals.adoc) - Technical limitations (permission.ask bug → opencode Issues #7006, #28066) - Architecture decisions (agentcontract/spec alignment → SPEC.md)
Source Anchor (source): This principle is itself a Semantic Anchor, defined in the LLM-Coding/Semantic-Anchors repository. https://github.com/LLM-Coding/Semantic-Anchors/docs/anchors/source-anchor.adoc (assumed path, as anchor files follow the
_template.adocnaming pattern). See also_template.adocat https://github.com/LLM-Coding/Semantic-Anchors/blob/main/docs/anchors/_template.adoc for the format. The Source Anchor states: "Every claim must be verifiable through a literally quoted, referenced source. The source (URL, title, author) must be provided."
Decision 6: Markdown for Design Documentation (not AsciiDoc)
The LLM-Coding/Semantic-Anchors repository uses AsciiDoc (.adoc) as its standard. For plugin development, we write in Markdown (.md) because:
-
Lower cognitive load during the design phase
-
Better diff readability
-
Simpler tool support (no AsciiDoc compiler required)
Conversion to .adoc occurs only at contribution time to the Semantic-Anchors repository.
Source Anchor (source): The Semantic-Anchors repository mandates: "AsciiDoc is mandatory — Do not convert to Markdown" (CLAUDE.md, LLM-Coding/Semantic-Anchors, https://github.com/LLM-Coding/Semantic-Anchors/blob/main/CLAUDE.md). This rule applies to content contributed to the repository. For local design documents, it does not apply.
Decision 7: Anchor Rules are bundled at design-time (not fetched at runtime)
Anchor rules are not fetched from GitHub at runtime. They are derived from the Semantic-Anchors repository at design time and bundled as a default YAML preset within the plugin package. Full rationale, alternatives, and consequences in ADR-013.
Quality Goal Realization
| Goal | Realized by |
|---|---|
Steering Correctness |
BLOCK mode in |
Output Determinism |
Same config → same contracts → same enforcement results. No variance through prompt interpretation. |
Workflow Continuity |
Rule Engine <50ms per check. Bypass mechanism prevents deadlock. Config-based maxOverrides. |
Configuration Clarity |
YAML with simple structure: contracts[], triggers[], mode. Role-based presets cover 90% of cases. |
Composability |
Plugin uses opencode SDK standard hooks. No global state. Coexists with other plugins. |
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.