Architecture
Systematic is designed as a lightweight yet powerful extension for OpenCode. It leverages native plugin hooks to inject structured workflows and specialized agents into your development environment.
Overview
Section titled “Overview”The plugin is divided into two main parts:
- TypeScript Source (
src/): The core logic that handles plugin registration, asset discovery, config validation, and model availability resolution. - Bundled Assets (
skills/andagents/): Structured engineering workflows and specialized agent definitions that are automatically discovered and registered by the plugin.
How It Works
Section titled “How It Works”On OpenCode v1 (1.18.29 and later), Systematic registers these plugin hooks. OpenCode v2 works differently; see OpenCode v2 below.
1. config Hook
Section titled “1. config Hook”The config hook is responsible for discovery and merging. It recursively walks the bundled asset directories (skills/ and agents/) and merges their definitions into your active OpenCode configuration. This ensures that agents like @architecture-strategist are available without manual setup.
2. tool Hook
Section titled “2. tool Hook”The plugin registers a custom tool called systematic_skill. This tool is used by the AI to load the full content of bundled skills on demand. While OpenCode has a native skill tool, systematic_skill provides optimized access to the plugin’s bundled content.
systematic_skill takes an optional arguments string. If the skill text contains $ARGUMENTS, that gets replaced with the whole string. If it contains $1, $2, and so on, those get replaced with positional values (quoted text counts as one value, and the highest-numbered placeholder takes the rest of the string) — the same rules OpenCode uses for its own slash commands. If the skill has none of these placeholders, the argument text is appended after the skill body instead. Leaving arguments out entirely leaves any placeholders in the skill text untouched. This is text substitution only — it never executes anything.
3. tool.execute.after Hook
Section titled “3. tool.execute.after Hook”OpenCode truncates plugin tool output at 2,000 lines or 50 KB by default. This hook restores the full skill text after that cut so large skills still arrive complete. If your own config sets tool_output.max_lines or tool_output.max_bytes, Systematic honors your limit and skips the restore.
4. messages.transform Hook
Section titled “4. messages.transform Hook”OpenCode’s opt-in compaction.prune setting replaces older tool results with a placeholder, and it exempts only its own built-in skill tool. This hook clears that placeholder flag on completed systematic_skill results in the request being assembled, so the model receives the stored body again. What this covers:
- With
compaction.pruneon, a loaded skill’s body survives pruning and the retained tail, but not full-session summarization. - The edit applies only to the request. Storage still marks the result as pruned.
- Prune still counts the body toward its protect window, which it skips for the native
skilltool. - The full-session summarizer sees a 2,000-character slice of the body.
- A host that predates the hook never calls it, and keeps the placeholder.
5. system.transform Hook
Section titled “5. system.transform Hook”This hook allows Systematic to inject a “bootstrap” instruction into the AI’s system prompt at the start of every conversation. This instruction (from the using-systematic skill) teaches the AI how to:
- Discover available workflows.
- Use the
systematic_skilltool. - Follow the “invoke before response” discipline.
- Create git worktrees at
.worktrees/<branch>under the main repository root with thegit-worktreeskill, unless you or a project instruction name a different location or tool. This rule is left out whengit-worktreeis indisabled_skills.
OpenCode v2
Section titled “OpenCode v2”The same npm package runs on OpenCode v2 (2.2.5 and later). v2 has no config hook and no string-keyed hooks. Instead, the plugin’s setup function loads your configuration once and registers four things through v2’s own APIs:
- Agents. The bundled agents are created with the same names, descriptions, prompts and modes as on v1. Systematic’s permission rules are added after OpenCode’s default rules for the agent, so a tool the rules do not mention keeps the host default. A skill list that limits an agent to certain skills still refuses every other skill, including inside subagents.
- Skills and the
systematic_skilltool. Each enabled bundled skill is added to OpenCode, so the nativeskilltool lists and loads it.systematic_skillis still registered, because the native tool cannot pass arguments. OpenCode cuts tool output at 2,000 lines or 50 KB after the plugin returns it, so Systematic keeps the full text and puts it back in the request. Three bundled skills (ce:review,ce:planandce:compound-refresh) are over that limit. - Commands. Each command keeps its
systematic:name and(Systematic)description. Systematic expands$ARGUMENTSand$1,$2, and so on itself and sends one prompt on your session, as it is. A plugin command on v2 cannot choose an agent or a model, so a bundled Systematic command that declaresagent,modelorsubtaskis refused with an error that says so; define such a command as an OpenCode command in Markdown or JSON instead. No bundled skill declares these fields. A discovered skill that declares them is never refused: Systematic leaves the fields off, writes one note, and runs the skill on your current session. A skill discovered in your project or home directory registers as a command under its bare name, unless that name belongs to a built-in OpenCode command. - Bootstrap. The
using-systematicinstructions are stored once per session as an instruction entry that OpenCode adds to the system prompt. Title requests do not carry it.
What differs from v1:
| Behavior | OpenCode v1 | OpenCode v2 |
|---|---|---|
| Config changes | Re-read each time OpenCode loads its config | Read once when the plugin loads; restart OpenCode to apply a change |
Invalid systematic.json | The plugin fails to load | The same: the plugin fails to load, OpenCode logs it and keeps running |
! followed by a backtick block in a command | Run by OpenCode before the prompt is sent | Never run; the text stays in the prompt as written |
| Skill names that are whole numbers | Move ahead of the * deny rule | Keep their listed order |
| Bootstrap | Added to the system prompt on each request | Stored with the session; stays after the plugin is removed, and a changed bootstrap adds a <system-update> message to sessions that already have the old one |
| Compaction pruning placeholder | Cleared for systematic_skill results | Not applicable; v2 has no such placeholder, so nothing is cleared |
tool_output limits in your config | Honored: Systematic skips the restore | Systematic always restores the full skill text |
Color tokens such as info | Passed through | Left off; v2 accepts only #RRGGBB |
temperature and top_p | Applied | Stored; v2 does not send them yet |
Invalid configuration is the one failure that stops the plugin. Any other failure inside a v2 callback is logged to the OpenCode server’s standard error with a systematic: prefix and does not stop the session.
Code Map
Section titled “Code Map”For developers looking to contribute or understand the implementation, here are the key modules:
| Symbol | Role | Location |
|---|---|---|
| default export | Plugin object with id, setup (OpenCode v2) and server (OpenCode v1) | src/index.ts |
createOpenCodeV1Hooks | Registers the v1 hooks | src/lib/opencode-v1.ts |
setupOpenCodeV2 | Loads config once and runs the v2 installers (agents, skills, commands, bootstrap) | src/lib/opencode-v2.ts and src/lib/opencode-v2-*.ts |
buildAssetPlan | Host-neutral agents, commands, skills and permission rules | src/lib/asset-plan.ts |
createConfigHandler | Implements the config hook logic | src/lib/config-handler.ts |
createSkillTool | Implements the systematic_skill tool | src/lib/skill-tool.ts |
getBootstrapContent | Handles system prompt injection | src/lib/bootstrap.ts |
findSkillsInDir | Discovers bundled skills | src/lib/skills.ts |