# ๐Ÿ”Œ Implementation Plan: OpenCode (`anomalyco/opencode`) Agent Adapter for MAM (Rev.3) - **Author**: `planner-reviewer-claude-01` (Planner), refined in collaboration with `creator-agy-01` and `reviewer-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 `opencode` as a 5th first-class MAM agent backend (alongside `claude`, `agy`, `hermes`, `grok`), per `docs/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**: 1. **Restored Root `implementation_plan.md` & Namespaced Plan Path (F1)**: - Root `implementation_plan.md` was 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.md` and mirrored at `.agents/reports/implementation_plan.md` to prevent future root-level collision. 2. **Extended Lockstep Touch-Points Table to 29 Items (F2)**: - Added items 23โ€“29 covering `create_session.sh` (`spawn()` dispatch case, YAML initialization `elif`, fallback `CMD_FULL` case), `lib.sh` (`send_keys_safe` `_sks_agent` resolution case), `tests/test_tier1_unit.py` (CLI usage assertions), 8ร— `SKILL.md` (supported agent lists), and `tests/conftest.py` (`mock_agents` stub binary). 3. **Official CLI `opencode db path` Subcommand Integration (F3)**: - Updated ยง1.5, ยง2, and ยง6 to prioritize the official `opencode db path` subcommand for dynamic database location, dropping hardcoded `storage.db` assumptions. 4. **Headless Permission Architecture via `OPENCODE_PERMISSION` (F4)**: - Documented the `OPENCODE_PERMISSION='{"*":"allow"}'` process environment variable alongside `--auto` in ยง1.4, ยง2, and ยง9 DoD for unattended automated operations without global config file mutations. 5. **Sandbox-Safe `ctx.home_dir` Binding in `auth_ok` and Artifact Paths (F5)**: - Replaced `os.path.expanduser('~')` with `ctx.home_dir` in the adapter skeleton and test contract for hermetic sandbox execution. --- ## Rev.2 Changelog (Job `c65b0081`) `creator-agy-01` filed a formal architectural challenge (Job `ebf97869`) against Rev.1, raising two blind spots. Both accepted: 1. **`reconcile.sh` Drift-C Non-Generic Correction**: Identified that `reconcile.sh` drift-C materialization consists of bespoke per-agent blocks, requiring an explicit `# === drift C (opencode) ===` block for post-spawn discovery agents. 2. **Core `lib_py` Constants Added**: Added `atomic_yaml.py:132` (`running_keys`), `verify_session.py:69` (`mam_row_own_uuid()`), and `workspace_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 archived `opencode-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 ` / `-s ` | Continue a specific session ID (cannot pre-assign a new UUID at spawn) | | `--agent ` | 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 ` 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`): 1. **CLI Flag**: `--auto` passed in `spawn_spec` / `resume_spec`. 2. **Process Environment Variable**: `OPENCODE_PERMISSION='{"*":"allow"}'` exported in the spawn subshell when full unattended bypass is required. 3. **Optional Project/Global Config**: `/opencode.json` or `~/.config/opencode/opencode.json` can 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 path` prints the exact database location. - **Default Storage Roots**: `~/.local/share/opencode/` (global) or `project//storage/`. - **Pre-Implementation Spike (ยง1.5)**: Run `opencode db path` and `sqlite3 ".schema"` on a live install to verify `session`, `message`, and `part` table column definitions (`cwd`, `created_at` timestamps) 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 for `auth.json` in `ctx.home_dir` OR 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 `_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 ` 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 `opencode` TUI pane using `herdr pane capture` to 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: ```python # === 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 path` and SQLite `.schema` dump 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.py` with sandbox-safe `ctx.home_dir`. - [ ] Apply all 29 lockstep wiring points across `registry.py`, `lib.sh`, skill scripts, `lib_py` modules, and test fixtures. - [ ] Add dedicated `reconcile.sh` Drift-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.sh` Drift-C materialization block & integration tests - ยฝ day: End-to-end manual smoke tests & regression verification