txrxcode

SRE with touch of AI

Syncing Opencode and Claude Code Skills for Mandatory Agentic Workflows

A structured guide to aligning skill definitions, tool protocols, and shared context between two AI coding agents — so they amplify each other instead of colliding.

Why sync at all?

Opencode and Claude Code are both terminal-first AI coding agents, but they live in different runtime contexts. Opencode (by SST) is an open-source, model-agnostic CLI that reads project-level context files. Claude Code is Anthropic’s agentic environment that interprets CLAUDE.md files and skill definitions injected via system prompts or MCP servers.

When you run both in the same monorepo or multi-agent pipeline, skill drift is the first thing to kill your workflow. One agent enforces a TypeScript strict rule; the other ignores it. One uses a bash tool to scaffold; the other regenerates the same file. Synchronisation isn’t optional — it’s the load-bearing wall of any serious agentic setup.

Core thesis: Treat skills not as agent-specific configs but as shared contracts. Both agents read from and write to the same skill layer, mediated by a canonical skill directory and an MCP bridge.

The canonical skill tree

The simplest synchronisation strategy is a shared skills directory at repo root. Both agents are pointed at the same tree — opencode via its opencode.config.ts context paths, Claude Code via its CLAUDE.md skill references.

project-root/
├── .skills/                       # canonical skill store (shared)
│   ├── SKILL_INDEX.md             # master registry, read by both agents
│   ├── core/
│   │   ├── typescript-strict.md       # oc: enforced on all .ts files
│   │   ├── error-handling.md          # cc: wrap with Result types
│   │   └── naming-conventions.md
│   ├── agents/
│   │   ├── opencode-manifest.yaml     # oc-specific overrides
│   │   ├── claudecode-manifest.yaml   # cc-specific overrides
│   │   └── sync-bridge.yaml           # shared invariants
│   ├── tools/
│   │   ├── bash/
│   │   │   ├── scaffold.md
│   │   │   └── test-runner.md
│   │   ├── mcp/
│   │   │   ├── filesystem-skill.md
│   │   │   ├── github-skill.md
│   │   │   └── postgres-skill.md
│   │   └── search/
│   │       └── web-search-skill.md
│   └── workflows/
│       ├── pr-review.md
│       ├── feature-scaffold.md
│       └── bug-triage.md
├── CLAUDE.md                          # cc entry: imports .skills/
├── opencode.config.ts                 # oc entry: mounts .skills/
├── mcp.json                           # shared MCP server registry
└── src/
    ├── agents/
    │   ├── opencode-runner.ts
    │   └── claudecode-runner.ts
    └── ...

What a skill file must contain

For both agents to parse a skill consistently, every .md skill file must follow a shared frontmatter schema. Neither agent should rely on agent-specific fields in the canonical layer — those belong in the agent manifests.

--- # required YAML frontmatter ---
name:        typescript-strict
version:     1.2.0
triggers:
  - *.ts
  - *.tsx
scope:       file           # file | project | session
priority:    mandatory      # mandatory | recommended | optional
agents:
  - opencode
  - claude-code
conflicts:   []
depends:
  - naming-conventions
---

# body: human + machine readable instructions
## trigger
Always enabled when editing TypeScript files.

## behaviour
- noImplicitAny: true
- strictNullChecks: true
- never use `any` unless wrapped in a branded type

## verification
Run `tsc --noEmit` before marking task complete.

Wiring each agent to the shared tree

opencode

opencode.config.ts

Use the context array to mount .skills/**/*.md. Opencode prepends all matched files into the model context window automatically.

claude code

CLAUDE.md

Use the @path import syntax to pull skill files. Claude Code’s skill loader respects the frontmatter priority field — mandatory skills are injected first.

// opencode.config.ts
export default defineConfig({
  context: [
    '.skills/SKILL_INDEX.md',
    '.skills/core/**/*.md',
    '.skills/agents/opencode-manifest.yaml',
    '.skills/tools/mcp/*.md',
    '.skills/workflows/*.md',
  ],
  model: 'anthropic:claude-sonnet-4-5',
  mcpServers: import('./mcp.json'),
})

// CLAUDE.md (top of file)
# project skills
@.skills/SKILL_INDEX.md
@.skills/core/typescript-strict.md
@.skills/core/error-handling.md
@.skills/agents/claudecode-manifest.yaml
@.skills/tools/mcp/filesystem-skill.md
@.skills/workflows/feature-scaffold.md

The six mandatory skills for agentic workflows

Not every skill is optional. These six classes must be loaded by both agents on every session. Without them, agents make conflicting assumptions that compound across long tasks.

skill class purpose key invariant agent
code-conventions Shared style rules, naming patterns, file layout Neither agent renames or restructures without reading this first
tool-registry Which tools are available, their call signatures Agents never invent tool calls outside the registry
file-authority Which agent owns which paths No two agents write to the same file without a handoff protocol
test-contract Test coverage rules, assertion style, test runner commands Task is never “done” until tests pass per this contract
error-taxonomy Canonical error types, Result wrapping, logging format Agents never swallow errors silently; always propagate per taxonomy
handoff-protocol How agents transfer state, context, and partial work All inter-agent hand-offs use a structured HANDOFF.md file

The handoff skill: structure of inter-agent state

The most overlooked mandatory skill is the handoff protocol. When Opencode delegates to Claude Code (or vice versa), it must write a machine-parseable HANDOFF.md to a well-known path. The receiving agent reads this before acting.

// .skills/workflows/handoff-protocol.md
---
name:     handoff-protocol
priority: mandatory
agents:   [opencode, claude-code]
---

## HANDOFF.md schema (written by delegating agent)
task_id:        string          # unique task identifier
from_agent:     opencode|cc     # sender
to_agent:       opencode|cc     # receiver
status:         partial|blocked # state at delegation
completed:
  - action: "created src/auth/jwt.ts"
  - action: "wrote 14 unit tests"
remaining:
  - action: "integrate with express middleware"
  - action: "run e2e suite"
context_files:
  - src/auth/jwt.ts
  - src/middleware/auth.ts
skills_active:
  - typescript-strict
  - error-taxonomy
blockers:       []              # list or empty

## receiving agent behaviour
1. Read HANDOFF.md before any file edits
2. Load every file in context_files
3. Activate every skill in skills_active
4. Continue from "remaining" list only
5. Overwrite HANDOFF.md with updated status on done

MCP as the synchronisation bus

Model Context Protocol servers are the cleanest synchronisation bus available to both agents. Both Opencode and Claude Code can mount the same mcp.json file. Shared tools — filesystem, GitHub, search — are declared once and available to either agent without duplication.

// mcp.json (project root — loaded by both agents)
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
    },
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres",
               "${DATABASE_URL}"]
    },
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "env": { "BRAVE_API_KEY": "${BRAVE_API_KEY}" }
    }
  }
}
Rule: Every MCP tool must have a corresponding skill file in .skills/tools/mcp/ that describes its usage contract — when to call it, what to do if it fails, and which agent is allowed to call it destructively (write vs. read-only access).

Full agentic workflow: feature scaffold example

Here is the complete orchestration loop for a typical “scaffold a new API feature” task, with both agents participating and all mandatory skills active throughout.

step 01
Opencode parses task
step 02
Loads mandatory skills
step 03
Scaffolds files via MCP
step 04
Writes HANDOFF.md
step 05
Claude Code reads handoff
step 06
Implements logic + tests
step 07
Runs test-contract check
step 08
Updates HANDOFF → done
// .skills/workflows/feature-scaffold.md
---
name:     feature-scaffold
priority: mandatory
agents:   [opencode, claude-code]
---

## opencode responsibilities (steps 1–3)
- Parse task description; extract entity name, route, method
- Load: typescript-strict, naming-conventions, file-authority
- Create directory structure under src/{feature}/
  ├── {feature}.router.ts      # route declarations only
  ├── {feature}.service.ts     # business logic stub
  ├── {feature}.schema.ts      # Zod validation schema
  └── {feature}.test.ts        # test file shell
- Write HANDOFF.md to .agent/handoff/{task_id}.md

## claude code responsibilities (steps 5–7)
- Read HANDOFF.md; load context_files
- Implement service methods with full error-taxonomy wrapping
- Fill test file using test-contract skill (vitest, 80% branch cov)
- Run: pnpm test --run src/{feature}
- Verify: tsc --noEmit (typescript-strict compliance)

## shared invariants (both agents)
- Never import from outside src/{feature}/ without file-authority check
- Never use console.log — use structured logger per error-taxonomy
- Git commit message must reference task_id from HANDOFF

Keeping skills in sync: versioning and drift prevention

Skills drift the same way code does — silently, across commits, until two agents have fundamentally different understandings of the same rule. Version your skills like you version your API.

drift risk

Common drift causes

Agent-specific manifests overriding shared skills without bumping the version field. Skill files edited without updating dependents listed in depends:.

prevention

Lint the skill tree

Add a pre-commit hook that validates all frontmatter fields, checks version bumps, and verifies that no agent manifest references a skill version older than what’s in SKILL_INDEX.md.

// scripts/lint-skills.ts (run in pre-commit)
const index = parseSkillIndex('.skills/SKILL_INDEX.md')
const manifests = [
  '.skills/agents/opencode-manifest.yaml',
  '.skills/agents/claudecode-manifest.yaml',
]
for (const m of manifests) {
  const manifest = parseManifest(m)
  for (const ref of manifest.skills) {
    const canonical = index[ref.name]
    if (!canonical)
      throw new Error(`unknown skill: ${ref.name}`)
    if (semver.lt(ref.version, canonical.version))
      throw new Error(`${m} uses stale ${ref.name}@${ref.version}`)
  }
}
console.log('skill tree: all agents in sync')

The minimal viable sync checklist

Before you ship an agentic pipeline that uses both agents, verify every item below. This checklist lives at .skills/SKILL_INDEX.md and is read by both agents as their first loaded file on every session start.

SKILL_INDEX.md — mandatory sync checklist

[ ] shared skill directory       .skills/ exists at repo root
[ ] canonical frontmatter         all skills have name/version/agents
[ ] opencode mount               context[] in opencode.config.ts
[ ] claude code import            @path imports in CLAUDE.md
    [ ] mcp.json at root            single MCP registry for both agents
    [ ] skill lint hook             pre-commit checks version drift
[ ] code-conventions loaded       priority: mandatory, agents: both
[ ] tool-registry loaded          priority: mandatory, agents: both
    [ ] file-authority loaded       defines write ownership per path
[ ] test-contract loaded          defines done condition for both
    [ ] error-taxonomy loaded       propagation rules for both agents
    [ ] handoff-protocol loaded     HANDOFF.md schema enforced