16 KiB
🔌 Implementation Plan: OpenCode (anomalyco/opencode) Agent Adapter for MAM (Rev.3)
- Author:
planner-reviewer-claude-01(Planner), refined in collaboration withcreator-agy-01andreviewer-creator-grok-01 - Job:
de667f0f→ Rev.2 (c65b0081) → Rev.3 (78f332a9) - Status: Plan only — no code changed in the repo. Ready for review/refinement.
- Scope: Add
opencodeas a 5th first-class MAM agent backend (alongsideclaude,agy,hermes,grok), perdocs/NEW_AGENT_INTEGRATION_GUIDE.md's 5-step procedure. - Durable Path:
.agents/reports/planner-reviewer-claude-01/plan-de667f0f.md(and.agents/reports/implementation_plan.md)
Rev.3 Changelog (Job 78f332a9)
Reviewers planner-reviewer-claude-01 (Job 1d63595f) and reviewer-creator-grok-01 (Job 56120ad3) reviewed the Rev.2 plan and working tree. All 5 findings accepted — zero [REBUT:] filed:
- Restored Root
implementation_plan.md& Namespaced Plan Path (F1):- Root
implementation_plan.mdwas restored to its tracked HEAD content (the active MAM 메시징 백플레인 전환 실행 로드맵 NATS migration roadmap). - This OpenCode integration plan is permanently housed under
.agents/reports/planner-reviewer-claude-01/plan-de667f0f.mdand mirrored at.agents/reports/implementation_plan.mdto prevent future root-level collision.
- Root
- Extended Lockstep Touch-Points Table to 29 Items (F2):
- Added items 23–29 covering
create_session.sh(spawn()dispatch case, YAML initializationelif, fallbackCMD_FULLcase),lib.sh(send_keys_safe_sks_agentresolution case),tests/test_tier1_unit.py(CLI usage assertions), 8×SKILL.md(supported agent lists), andtests/conftest.py(mock_agentsstub binary).
- Added items 23–29 covering
- Official CLI
opencode db pathSubcommand Integration (F3):- Updated §1.5, §2, and §6 to prioritize the official
opencode db pathsubcommand for dynamic database location, dropping hardcodedstorage.dbassumptions.
- Updated §1.5, §2, and §6 to prioritize the official
- Headless Permission Architecture via
OPENCODE_PERMISSION(F4):- Documented the
OPENCODE_PERMISSION='{"*":"allow"}'process environment variable alongside--autoin §1.4, §2, and §9 DoD for unattended automated operations without global config file mutations.
- Documented the
- Sandbox-Safe
ctx.home_dirBinding inauth_okand Artifact Paths (F5):- Replaced
os.path.expanduser('~')withctx.home_dirin the adapter skeleton and test contract for hermetic sandbox execution.
- Replaced
Rev.2 Changelog (Job c65b0081)
creator-agy-01 filed a formal architectural challenge (Job ebf97869) against Rev.1, raising two blind spots. Both accepted:
reconcile.shDrift-C Non-Generic Correction: Identified thatreconcile.shdrift-C materialization consists of bespoke per-agent blocks, requiring an explicit# === drift C (opencode) ===block for post-spawn discovery agents.- Core
lib_pyConstants Added: Addedatomic_yaml.py:132(running_keys),verify_session.py:69(mam_row_own_uuid()), andworkspace_uuid.py:7-12,33(OWN_KEY) to the touch-points list.
0. Relationship to the existing new_agent_types_roadmap.md
.agents/reports/new_agent_types_roadmap.md (by creator-agy-01, 2026-08-26) sketches a generic "add any new agent" procedure and guesses at OpenCode specifics in its difficulty matrix. This plan supersedes that roadmap's OpenCode row:
| Roadmap's guess | Verified reality (this doc) | |
|---|---|---|
| Storage format | "JSON files in ~/.opencode/sessions/" |
SQLite since v1.2.0 (Feb 2026); dynamic lookup via opencode db path |
| Storage path | ~/.opencode/sessions/ |
~/.local/share/opencode/ (XDG data dir) or project storage via opencode db path |
| Ready tokens | OpenCode|Chat|Welcome |
Candidate string pending live TUI pane capture verification (§4) |
| Adapter registry | ClineAgentAdapter shown |
cline fully removed in v4.0.0. Targets current 4-adapter registry (claude, agy, hermes, grok) |
1. Verified OpenCode CLI/Runtime Facts
Source: opencode.ai/docs/{cli,config,permissions,tui}/, github.com/anomalyco/opencode, and official documentation.
1.1 Identity & Install
- Upstream Repository:
github.com/anomalyco/opencode(distinct from the archivedopencode-ai/opencode/ "Crush"). - Install:
curl -fsSL https://opencode.ai/install | bash(also npm/Homebrew).
1.2 Execution Modes
opencode [project]— launches the TUI (default).opencode run [message..]— non-interactive one-shot CLI execution.opencode db path— prints the active database file path on stdout.
1.3 Relevant CLI Flags & Environment
| Flag / Variable | Purpose |
|---|---|
--continue / -c |
Resume the most recent session |
--session <id> / -s <id> |
Continue a specific session ID (cannot pre-assign a new UUID at spawn) |
--agent <name> |
Mode selection: build (default, write access) or plan (read-only) |
--auto |
Auto-approves tools that prompt ("ask"); explicit "deny" rules still enforced |
OPENCODE_PERMISSION |
Process environment variable for inline JSON permission overrides (e.g. '{"*":"allow"}') |
Spawn / Discovery Consequence: OpenCode has no --session-id <new-uuid> pre-assignment flag. It falls strictly in the hermes / agy architectural bucket: spawn_spec() ignores session_uuid on initial creation, and MAM learns the assigned session ID post-spawn via discover() and reconcile.sh Drift-C materialization.
1.4 Permissions (Headless & Non-Interactive Configuration)
MAM configures headless unattended execution at the process boundary without mutating user-global config files (~/.config/opencode/opencode.json):
- CLI Flag:
--autopassed inspawn_spec/resume_spec. - Process Environment Variable:
OPENCODE_PERMISSION='{"*":"allow"}'exported in the spawn subshell when full unattended bypass is required. - Optional Project/Global Config:
<project>/opencode.jsonor~/.config/opencode/opencode.jsoncan define granular policies if configured by the user.
1.5 Session & Artifact Storage
- Format: Single SQLite database (since v1.2.0).
- Dynamic Path Resolution:
opencode db pathprints the exact database location. - Default Storage Roots:
~/.local/share/opencode/(global) orproject/<slug>/storage/. - Pre-Implementation Spike (§1.5): Run
opencode db pathandsqlite3 <db> ".schema"on a live install to verifysession,message, andparttable column definitions (cwd,created_attimestamps) before writing SQL queries.
1.6 Authentication
- Interactive:
opencode auth login. - Multi-provider environment variables:
ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY,GROQ_API_KEY. - Credentials cache:
~/.local/share/opencode/auth.json. auth_ok()checks forauth.jsoninctx.home_dirOR any supported provider environment variable.
1.7 Exit Key
- Slash command
/exit(aliases/quit,/q).
2. Adapter Contract Decisions (BaseAgentAdapter)
| Property/Method | Value | Rationale |
|---|---|---|
name |
'opencode' |
Lowercase identifier |
own_key |
'opencode_session_id_own' |
Matches <agent>_session_id_own convention |
ready_tokens |
'OpenCode|Chat' (candidate) |
Pending live TUI capture verification (§4) |
exit_key |
'/exit' |
Confirmed in CLI docs |
delegate_agent_key |
'opencode-cli' |
Descriptive CLI identifier |
identity_cache_fields |
('session_id',) |
Single-field cache |
spawn_spec(binary, session_uuid, use_wrapper) |
f"{binary} --auto --agent build" |
Non-interactive build mode; ignores session_uuid at spawn |
resume_spec(binary, session_uuid, materialized) |
f"{binary} --session {session_uuid} --auto --agent build" (if materialized) |
Passes --session <id> once discovered |
auth_ok(run_cmd) |
os.path.exists(f"{ctx.home_dir}/.local/share/opencode/auth.json") or any(env) |
Multi-provider fallback + sandbox-safe path |
discover(ctx) |
Queries SQLite DB resolved via opencode db path / default root |
Discovers new session IDs created since ctx.epoch |
verify_artifact(uuid, ctx) |
Checks row existence in SQLite session table |
Validates session persistence |
purge_artifacts(uuid, ctx) |
Executes parameterized DELETE FROM session/message/part WHERE session_id=? |
Cleans SQLite rows on stop --purge-conversation |
3. Complete 29-Point Wiring & Lockstep Table
All 29 locations below must be updated in lockstep:
| # | Component / File | Specific Edit Required |
|---|---|---|
| 1 | .agents/skills/lib_py/agents/adapters/opencode.py |
Create new adapter class OpenCodeAgentAdapter |
| 2 | .agents/skills/lib_py/agents/registry.py |
Import OpenCodeAgentAdapter and register in _ADAPTERS['opencode'] |
| 3 | .agents/skills/lib.sh (kind mapping, ~L606-609) |
*-creator-opencode|*-planner-opencode|*-reviewer-opencode) kind="opencode" ;; |
| 4 | .agents/skills/lib.sh (fallback grep chain, ~L610-619) |
Add elif echo "$name" | grep -qi "opencode"; then kind="opencode" |
| 5 | .agents/skills/lib.sh (binary dedup tuple, ~L632) |
Add 'opencode' to ('claude', 'agy', 'hermes', 'grok', 'opencode') |
| 6 | create_session.sh (line 93 --agent whitelist) |
Add opencode to accepted values |
| 7 | resume_session.sh (line 45 --agent whitelist) |
Add opencode to accepted values |
| 8 | resolve_session_id.sh (line 39 --agent whitelist) |
Add opencode to accepted values |
| 9 | update_yaml_resumed.sh (usage string) |
Add opencode to usage documentation |
| 10 | stop_session.sh (line 94 --agent whitelist) |
Add opencode to accepted values |
| 11 | orc_onboard.sh (line 106 case) |
Add opencode to accepted values |
| 12 | tests/test_a4_adapter_contract.py |
Add opencode to EXPECTED_OWN_KEYS, expected, expected_modal (None), facts_bridge, and delegate bash snippets |
| 13 | tests/test_a4_adapter_contract.py |
Add adapter-specific unit tests (spawn_spec, resume_spec, auth_ok, purge_artifacts, discover) |
| 14 | tests/test_c1_tui_readiness.py |
Add opencode to facts-bridge and SKS session resolution loops |
| 15 | tests/test_tier2_component.py |
Add OpenCode lifecycle tests (create → YAML → stop → resume) |
| 16 | .mam.env.example |
Document OpenCode multi-provider API keys and OPENCODE_PERMISSION |
| 17 | docs/NEW_AGENT_INTEGRATION_GUIDE.md |
Update difficulty matrix with OpenCode SQLite storage |
| 18 | .agents/reports/new_agent_types_roadmap.md |
Add cross-link noting OpenCode details are superseded by this plan |
| 19 | .agents/skills/lib_py/atomic_yaml.py:132 |
Add 'opencode_session_id_own' to running_keys |
| 20 | .agents/skills/lib_py/verify_session.py:69 |
Add 'opencode_session_id_own' to mam_row_own_uuid() |
| 21 | .agents/skills/lib_py/workspace_uuid.py:7-12, 33 |
Add 'opencode' to OWN_KEY dict and running_ids list |
| 22 | reconcile.sh (auto-reg & own keys) |
Add opencode to loops (521, 540), entry initialization elif (584-601), and OWN_KEY_BY_AGENT (611) |
| 23 | create_session.sh (spawn dispatch, ~L204) |
Add opencode to agy|hermes|grok|opencode) case |
| 24 | create_session.sh (YAML init, ~L399-419) |
Add elif agent == 'opencode': entry['opencode_session_id_own'] = None |
| 25 | create_session.sh (fallback CMD_FULL, ~L180-184) |
Add opencode) CMD_FULL="$BINARY --auto --agent build" ;; |
| 26 | lib.sh (send_keys_safe agent resolution, ~L2058-2061) |
Add *-opencode|*-opencode-[0-9]*|opencode|opencode-[0-9]*) _sks_agent="opencode" ;; |
| 27 | tests/test_tier1_unit.py (~L969, 974) |
Update expected CLI error usage assertions to include opencode |
| 28 | 8× SKILL.md supported-agent lists |
Update skill documentation to list opencode |
| 29 | tests/conftest.py (mock_agents) |
Add mock opencode binary fixture and sandbox state support |
4. TUI Readiness & Empirical Verification
- Initial candidate pattern:
OpenCode|Chat - Empirical Capture Gate: Before finalizing the PR, capture rendered text from a live
opencodeTUI pane usingherdr pane captureto confirm the exact ready token and prompt glyphs (input_prompt,input_placeholder).
5. Drift Detection Integration & Dedicated Drift-C Block
In multi-agent-mux-monitor/scripts/reconcile.sh, add the dedicated OpenCode materialization block:
# === drift C (opencode): opencode 새 session id materialize (per-row own id) ===
for s in d.get('herdr_sessions', []):
if row_agent(s) != 'opencode':
continue
if s.get('status') != 'running':
continue
if s.get('opencode_session_id_own'):
continue
cwd = (s.get('pane') or {}).get('cwd', '')
if not cwd:
continue
# Resolve DB path via opencode db path or default XDG root
db = _resolve_opencode_db(home, cwd)
if not os.path.exists(db):
continue
epoch_threshold = s.get('herdr_session_epoch', 0)
sibling_claimed = [
other.get('opencode_session_id_own')
for other in d.get('herdr_sessions', [])
if other is not s
and (other.get('pane') or {}).get('cwd') == cwd
and other.get('status') not in ('stopped', 'terminated')
and other.get('opencode_session_id_own')
]
s_eval = dict(s)
s_eval['_sibling_claimed_uuids'] = sibling_claimed
valid_candidates = []
try:
conn = sqlite3.connect(db)
rows = conn.execute(
"SELECT id FROM session WHERE cwd=? AND created_at >= ? ORDER BY created_at DESC LIMIT 20",
(cwd, epoch_threshold)
).fetchall()
conn.close()
for (uuid,) in rows:
if uuid in (sibling_claimed or []):
continue
if verify_session_uuid(cwd, 'opencode', uuid, s_eval, mode="discover"):
valid_candidates.append(uuid)
except Exception:
pass
if len(valid_candidates) > 1:
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
actions.append(f"ambiguous candidates: {s['name']}")
elif len(valid_candidates) == 1:
uuid = valid_candidates[0]
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "opencode" "{cwd}"']
rc = subprocess.run(cmd).returncode
if rc == 0:
_pin_and_verify_resume(s, 'opencode', cwd, uuid, degraded=False)
elif rc == 1:
drifts.append({'class': 'C-warn', 'name': s['name'], 'msg': f"{s['name']}: TUI viewport mismatch for candidate {uuid} — not pinned, will retry next cycle"})
else:
_pin_and_verify_resume(s, 'opencode', cwd, uuid, degraded=True)
6. Definition of Done Checklist
- Execute
opencode db pathand SQLite.schemadump spike on a live install. - Confirm TUI ready tokens and input prompt glyphs via empirical pane capture.
- Verify headless execution under
--auto+OPENCODE_PERMISSION='{"*":"allow"}'. - Implement
lib_py/agents/adapters/opencode.pywith sandbox-safectx.home_dir. - Apply all 29 lockstep wiring points across
registry.py,lib.sh, skill scripts,lib_pymodules, and test fixtures. - Add dedicated
reconcile.shDrift-C materialization block and corresponding integration test. - Verify 100% test pass on full suite (
pytest tests/). - Perform manual end-to-end smoke test (create → prompt → discover → stop/purge).
7. Effort Estimate
- Total Estimate: ~1.5 – 2.0 Days
- ½ day: SQLite schema spike (
opencode db path) & TUI token live capture - ½ day: Adapter implementation & 29-point mechanical wiring
- ½ day: Dedicated
reconcile.shDrift-C materialization block & integration tests - ½ day: End-to-end manual smoke tests & regression verification
- ½ day: SQLite schema spike (