Critical differences that affect PAI behavior and must be accounted for in the port.
PAI was originally built for Claude Code. When porting to OpenCode, certain platform differences require adaptation. This document catalogs those differences and how PAI-OpenCode handles them.
| Platform | Behavior |
|---|---|
| Claude Code | cd persists across bash calls within a session |
| OpenCode | Each bash() call spawns a NEW shell — cd has NO persistent effect |
Use the workdir parameter for all commands that must run in a different directory.
// WRONG in OpenCode
bash({ command: "cd /repo && git status" })
// CORRECT in OpenCode
bash({ command: "git status", workdir: "/repo" })- Algorithm: Must use
workdirwhen working outsideInstance.directory - Multi-repo workflows: Explicit directory specification required
- Plugin validation: Can detect missing
workdirfor external paths
See: ADR-008
| Platform | Mechanism | Execution |
|---|---|---|
| Claude Code | Subprocess hooks (.claude/hooks/*.hook.ts) |
External process, stdout capture |
| OpenCode | In-process plugins (~/.opencode/plugins/*.ts) |
Same process, direct API |
Migrate hooks to OpenCode plugins with event handlers.
// Claude Code hook
export default async function(context) {
// Hook logic
}
// OpenCode plugin
export default {
name: "pai-core",
onSessionStart: async (context) => { /* ... */ },
onToolCall: async (tool, args) => { /* ... */ },
}- 6 hooks migrated to plugins (context-loader, security-validator, voice-notification, etc.)
- Event-driven architecture replaces hook-based
- File-based logging to prevent TUI corruption
| Platform | Directory | Config File |
|---|---|---|
| Claude Code | ~/.claude/ |
settings.json |
| OpenCode | ~/.opencode/ |
opencode.json |
Use .opencode/ for all PAI-OpenCode files.
~/.opencode/
├── PAI/ # Core PAI system
├── skills/ # Skills (SKILL.md structure)
├── agents/ # Agent definitions
├── plugins/ # OpenCode plugins
├── MEMORY/ # Session history, learning
└── opencode.json # OpenCode config
- All paths updated from
.claude/to.opencode/ - Dual config files:
settings.json(PAI) +opencode.json(OpenCode) - Symlink support for existing OpenCode users
| Platform | Status | Feature |
|---|---|---|
| Claude Code | ✅ Released (Feb 2026) | Agent Teams, TeammateTool, shared tasks |
| OpenCode | ❌ Not implemented | GitHub issues #12661, #12711, PR #7756 (open) |
Use OpenCode's Task tool with sequential subagents.
// Claude Code: Agent Teams
TeammateTool({ team_name: "research-team", message: "..." })
// OpenCode: Sequential subagents
Task({ subagent_type: "Researcher", prompt: "..." })- No parallel agent swarms in PAI-OpenCode v3.0
- Sequential subagents via Task tool
- Monitor PR #7756 for future "subagent-to-subagent delegation"
See: EPIC-v3.0-Synthesis-Architecture.md Section 1
| Platform | Native Support | Implementation |
|---|---|---|
| Claude Code | ❌ No | Would require custom routing |
| OpenCode | ✅ Full | Vanilla install from opencode.ai |
PAI-OpenCode uses vanilla OpenCode. Each agent has exactly one model configured in opencode.json. No custom fork or build is required.
// opencode.json
{
"agent": {
"Engineer": {
"model": "opencode/kimi-k2.5"
}
}
}For cost optimization, use agent-based routing: assign lightweight agents (explore, Intern) to simple work; use heavier agents (Architect, Engineer) for complex work.
- Standard vanilla install — no custom binary required
- Cost optimization via appropriate agent selection
- Single model per agent configured in
opencode.json
(SUPERSEDED by ADR-019: PAI-OpenCode previously maintained a custom fork for runtime
model_tierselection. Removed in v3.0 — see ADR-019 for rationale.)
See: EPIC-v3.0-Synthesis-Architecture.md
| Platform | Mechanism | Context Size |
|---|---|---|
| Claude Code | Static context loading | 233KB at session start |
| OpenCode | Native skill tool |
On-demand, ~20KB bootstrap |
Use OpenCode's native skill discovery and lazy loading.
// OpenCode-native skill discovery
const skills = await skill_find({ pattern: "research" });
await skill_use({ name: "research", action: "deepResearch" });- Remove static context loader (233KB → 20KB)
- Use native skill tool for on-demand loading
- Faster session startup (<3 seconds)
See: EPIC-v3.0-Synthesis-Architecture.md WP2
| Platform | Events | Hook Points |
|---|---|---|
| Claude Code | Limited | Pre/post tool, session start/end |
| OpenCode | 20+ events | session, tool, file, message, compaction, pty, lsp, etc. |
| Event | Payload | PAI Usage |
|---|---|---|
session.created |
{ info: { id, title, directory } } |
Work session start, context load |
session.updated |
{ info: { title } } |
Title tracking |
session.error |
{ error, sessionID } |
Error diagnostics |
session.compacted |
— | 🔴 CRITICAL: Learning rescue before context loss |
message.updated |
message data | Sentiment, ISC validation, response cache |
tool.execute.before |
tool name, args | Security validation, guard checks |
tool.execute.after |
tool name, result | PRD sync, question tracking, observability |
file.edited |
filepath, diff | PRD auto-sync (WP-G planned) |
file.watcher.updated |
filepath, event | External change detection |
command.executed |
name, arguments | /command usage tracking |
permission.asked |
id, permission, patterns, tool | Full permission audit log |
permission.replied |
— | Permission response tracking |
lsp.client.diagnostics |
diagnostics | Code error detection after edits |
installation.update.available |
version | OpenCode update notification |
tui.prompt.append |
text | TUI text injection |
pty.created/updated/exited |
pty data | Terminal session events |
Use OpenCode's native event system — subscribe to all via event hook.
"event": async (input) => {
const eventType = (input.event as any)?.type;
// CRITICAL: session.compacted = last chance to save learnings
if (eventType === "session.compacted") {
await extractAndSaveLearnings(sessionID); // IMMEDIATE
}
// file.edited for event-driven PRD sync
if (eventType === "file.edited") {
const filepath = input.event?.properties?.filepath;
if (filepath?.endsWith("PRD.md")) await syncPRD(filepath);
}
}- 20+ events now covered in
pai-unified.ts(WP-A PR #42) session.compactedis critical — only chance to save before context lossfile.editedenables event-driven PRD sync (planned WP-G)permission.askedprovides full audit log of all AI permissions
See: PLUGIN-SYSTEM.md, ADR-009
| Platform | Env Handling |
|---|---|
| Claude Code | Shell session persists; export VAR=value works across calls |
| OpenCode | Fresh process per call; env vars need explicit management |
Layer 1 — .opencode/.env → Bun → process.env (TypeScript code)
Layer 2 — shell.env plugin hook → Bash child processes
Layer 1 (.env): API keys, credentials, service URLs — loaded by Bun at startup into process.env. TypeScript code reads these directly. No code needed.
Layer 2 (shell.env hook): Runtime context per bash call — session ID, working directory, + explicit passthrough of selected keys for bash scripts.
// shell.env hook in pai-unified.ts
"shell.env": async (input, output) => {
output.env["PAI_CONTEXT"] = "1";
output.env["PAI_SESSION_ID"] = input.sessionID ?? "unknown";
output.env["PAI_WORK_DIR"] = input.cwd ?? "";
// Explicit passthrough for bash scripts that need these
const PASSTHROUGH_KEYS = ["GOOGLE_API_KEY", "TTS_PROVIDER", "DA", "TIME_ZONE"];
for (const key of PASSTHROUGH_KEYS) {
if (process.env[key]) output.env[key] = process.env[key];
}
}- TypeScript plugins: Read from
process.envdirectly — no hook needed - Bash scripts: Receive
PAI_CONTEXT,PAI_SESSION_ID,PAI_WORK_DIR+ selected keys - API key inheritance:
.env→process.env→ explicit passthrough (not automatic)
See: ADR-010
| Platform | Session Storage | Growth |
|---|---|---|
| Claude Code | Files in ~/.claude/ |
Manageable |
| OpenCode | SQLite at ~/.local/share/opencode/opencode.db |
Can reach 2+ GB |
~/.local/share/opencode/
├── opencode.db ← All sessions, messages, parts (2.4 GB after 3 months)
├── opencode.db-wal ← Write-Ahead Log
└── storage/
├── migration ← Migration marker (value: 2 = SQLite mode)
├── part/ ← Legacy JSON files (obsolete after migration)
├── message/ ← Legacy JSON files (obsolete after migration)
└── session/
| Table | Records (3 months) | Size |
|---|---|---|
session |
~4,000 | small |
message |
~60,000 | medium |
part |
~235,000 | 1.4 GB (code, text, tool outputs) |
OpenCode has no automatic session retention policy. The database grows indefinitely:
- Each message part (code block, tool output) = ~6 KB
- After 3 months: 2.4 GB, 235k parts, 60k messages
- Startup-lock error: Migration check on 135k legacy JSON files blocks first start
Three-level archiving solution:
- Plugin warning:
session-cleanup.tschecks DB size after session end - CLI tool:
bun Tools/db-archive.ts [days] [--dry-run] [--vacuum] - Custom command:
/db-archivein OpenCode TUI - VACUUM: Like disk defragmentation — reclaims freed space (requires OpenCode shutdown)
# Archive sessions older than 90 days
bun Tools/db-archive.ts 90
# Dry run — shows what would be archived
bun Tools/db-archive.ts 90 --dry-run
# Archive + VACUUM (requires OpenCode to be stopped)
bun Tools/db-archive.ts 90 --vacuum- PR #42 scope: DB health warning in
session-cleanup.ts(WP-A) - PR #D scope: Full archive tool + VACUUM + Electron GUI (WP-F)
LSP Integration (automatic):
After every Write or Edit, OpenCode notifies language servers and returns syntax errors immediately. PAI gets code diagnostics for free — no additional code needed.
Git Snapshot System (automatic):
OpenCode maintains a hidden Git repository for every project. Before each AI edit, a snapshot is created. Undo = git checkout from the snapshot. Configure with "snapshot": true in opencode.json (already set).
Parcel File Watcher (automatic):
OpenCode watches the project directory using platform-native file system events (FSEvents on macOS, inotify on Linux). Plugins can subscribe to file.edited and file.watcher.updated events.
- LSP diagnostics: Automatic after every Write/Edit — no PAI code needed
- Undo system:
~/.local/share/opencode/snapshot/stores all AI edit history - PRD sync: Subscribe to
file.editedfor event-driven PRD frontmatter updates
| Feature | Claude Code | OpenCode | PAI-OpenCode Solution |
|---|---|---|---|
| Bash workdir | cd persists |
workdir param |
Use workdir always (ADR-008) |
| Hooks | Subprocess | In-process plugins | Migrated to plugins (ADR-001) |
| Directory | .claude/ |
.opencode/ |
Use .opencode/ (ADR-002) |
| Agent Swarms | ✅ Yes | ❌ No | Sequential Task tool |
| Model Config | ❌ No | ✅ Vanilla install | One model per agent in opencode.json |
| Lazy Loading | Static | Native skill tool | Use native discovery |
| Events | ~5 events | 16+ events | Use native events (ADR-009) |
| Env Variables | Shell-persistent | Fresh per call | Two-layer system (ADR-010) |
| Session DB | Files | SQLite (grows!) | WP-F archive tool |
| LSP Diagnostics | ❌ Manual | ✅ Auto after Write | Free — no code needed |
| Git Snapshots | ❌ Manual | ✅ Auto per edit | Free — snapshot: true |
| File Watching | ❌ Polling | ✅ Native events | file.edited event |
| Config Hierarchy | Flat | 6-level override | opencode.json precedence |
| Skill Loading | .claude/skills/ |
Both .claude/ + .opencode/ |
Backward compatible! |
| ACP Server | ❌ No | ✅ IDE integration | Future: IDE plugin |
When porting PAI features to OpenCode:
- Check for
cdusage in bash calls → useworkdir - Migrate hooks to plugin event handlers
- Update paths from
.claude/to.opencode/ - Use Task tool instead of Agent Teams
- Configure agent models in
opencode.json(one model per agent) - Use native skill tool for lazy loading
- Map hooks to all 16 OpenCode events
- Add
shell.envhook for bash context injection - Implement DB archive tool (WP-F, PR #D)
- Add
file.edited→ PRD sync (WP-G, PR #B)
- ADR-001: Hooks to Plugins
- ADR-002: Directory Structure
- ADR-004: Plugin Logging
- ADR-005: Dual Config
- ADR-008: Bash workdir
- ADR-009: Handler Audit
- ADR-010: Shell.env Two-Layer System
- EPIC-v3.0-Synthesis-Architecture
- OpenCode Native Research
Last updated: 2026-03-06 Status: Updated with DeepWiki research findings — Session 2026-03-06