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

16 KiB
Raw Blame History

🔌 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:

# === 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