Files
multi-agent-mux/.agents/reports/implementation_plan.md
T

250 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔌 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 2329 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 <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`):
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**: `<project>/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/<slug>/storage/`.
- **Pre-Implementation Spike (§1.5)**: Run `opencode db path` and `sqlite3 <db> ".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 `<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 `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