sandbox-agent/research/specs/opencode-adapter-package-plan.md
2026-02-11 07:57:02 -08:00

9.3 KiB

Plan: OpenCode Adapter Package + ACP Journal Persistence

Decisions Locked

  • Move OpenCode support into its own Rust package: yes.
  • Re-enable /opencode/* only after tests pass: yes.
  • Persist ACP-side representation like the TypeScript SDK; convert to OpenCode response/event shapes at runtime: yes.
  • Use sqlx + SQLite with WAL + migrations: yes.
  • Session restoration strategy should be lazy (TS SDK-style): yes.
  • Validation target is parity with current OpenCode compat suite, plus ACP-protocol gaps not currently covered: yes.
  • Docs/behavior must clearly state: auto-restore handles runtime/session loss, but cannot recover if persistence storage is wiped: yes.

Context

Current OpenCode compatibility logic lives in a large monolith (server/packages/sandbox-agent/src/opencode_compat.rs) and session bridge (server/packages/sandbox-agent/src/opencode_session_manager.rs). In the active ACP-v1 baseline, /opencode/* is disabled/unmounted and sandbox-agent opencode is disabled.

The TypeScript SDK already defines a stable persistence+restore model:

  • Persist SessionRecord + envelope SessionEvent journal.
  • Recreate stale sessions lazily.
  • Inject replay context on the next prompt.

This plan ports that model into the Rust OpenCode adapter runtime.

Goals

  • Extract OpenCode compatibility into a dedicated package with clean boundaries.
  • Rebuild session tracking/restore using a persisted ACP journal via SQLite (sqlx).
  • Keep OpenCode-specific JSON shapes as runtime projections derived from persisted ACP records.
  • Re-enable /opencode/* when parity tests + new ACP coverage pass.

Non-Goals

  • Full redesign of all OpenCode compat endpoint semantics in one pass.
  • Replacing ACP core runtime behavior.
  • Introducing Postgres in this phase (SQLite is canonical backend for adapter persistence).

Package Architecture

New package

  • Add server/packages/opencode-adapter (crate name suggestion: sandbox-agent-opencode-adapter).
  • Public entry points:
    • build_opencode_router(state: Arc<AdapterAppState>) -> Router
    • OpenCodeAdapter::new(...)
    • OpenCodeAdapterConfig (db path, replay limits, feature toggles)

Responsibilities split

  • sandbox-agent package:
    • Own server bootstrapping, auth, and top-level routing.
    • Mount /opencode/* by nesting adapter router.
  • opencode-adapter package:
    • OpenCode HTTP handlers.
    • ACP bridge client/session orchestration.
    • ACP journal persistence (SQLite/sqlx).
    • Runtime projection from ACP journal -> OpenCode model/session/message/event views.

Persistence Model (ACP Journal First)

Mirror TS SDK semantics in Rust.

Tables (initial)

  1. sessions
  • id TEXT PRIMARY KEY (local/session ID exposed to OpenCode)
  • agent TEXT NOT NULL
  • agent_session_id TEXT NOT NULL (current live ACP session id)
  • last_connection_id TEXT NOT NULL
  • created_at INTEGER NOT NULL
  • destroyed_at INTEGER NULL
  • session_init_json TEXT NULL (JSON-encoded ACP session/new init payload)
  1. events
  • id TEXT PRIMARY KEY
  • session_id TEXT NOT NULL
  • created_at INTEGER NOT NULL
  • connection_id TEXT NOT NULL
  • sender TEXT NOT NULL (client or agent)
  • payload_json TEXT NOT NULL (full ACP envelope JSON)
  • index: (session_id, created_at, id)
  1. opencode_session_metadata (small adjunct metadata table)
  • session_id TEXT PRIMARY KEY
  • metadata_json TEXT NOT NULL
  • Stores OpenCode-specific metadata not guaranteed to be derivable from ACP envelope stream alone (title, parent, directory, project id, version, share url, permission mode).

Note: the canonical event history remains ACP envelopes; this metadata table is a projection cache/anchor for OpenCode-specific fields.

SQLite settings

  • Set PRAGMA journal_mode=WAL on initialization.
  • Set PRAGMA synchronous=NORMAL (or FULL if we prioritize durability over throughput; default recommendation: NORMAL for compatibility throughput).
  • Use sqlx::migrate!() with versioned migrations under package-local migrations/.

Restore Algorithm (TS-style, Rust implementation)

For each session request (lazy restore trigger path):

  1. Read persisted sessions row.
  2. Ensure active ACP connection for the target agent.
  3. If connection/session binding is still valid, continue.
  4. If stale:
  • Collect replay source from persisted events (last replay_max_events).
  • Build replay text (bounded by replay_max_chars) from ACP envelopes:
    • include createdAt, sender, payload JSON line format.
    • append truncation marker if over limit.
  • Recreate ACP session (session/new or session/load/session/resume depending on capabilities; fallback to session/new with stored init).
  • Update persisted agent_session_id + last_connection_id.
  • Queue replay injection for next prompt.
  1. On first post-restore prompt, prepend replay text as text part (same pattern as TS SDK).

Runtime Projection Strategy

Do not persist OpenCode wire payloads as source of truth.

  • Build OpenCode session/message/state views from:
    • ACP journal (events)
    • session row (sessions)
    • metadata adjunct (opencode_session_metadata)
  • Use projection reducers for:
    • session status (idle/busy/error)
    • message records and parts
    • tool call/result linkage
    • pending permission/question state
  • Keep hot in-memory projection cache for active sessions; rebuild on process start from persisted rows/events.

API + Routing Re-enable Plan

Phase A: extraction scaffolding

  • Create new adapter crate with minimal router + state wiring.
  • Move code from opencode_compat.rs / opencode_session_manager.rs into adapter modules.
  • Keep /opencode/* disabled while extraction is in progress.

Phase B: persistence integration

  • Add sqlx SQLite store + migrations.
  • Replace in-memory canonical session/event tracking with persisted journal.
  • Keep in-memory structures only as transient caches.

Phase C: restore + replay

  • Implement lazy restore path for stale bindings.
  • Implement replay text generation + prompt injection.
  • Ensure OpenCode message/prompt endpoints use restored session path automatically.

Phase D: re-enable

  • Mount adapter router under /opencode in sandbox-agent router.
  • Re-enable CLI opencode command path after tests are green.

ACP Coverage Additions (Beyond Current OpenCode Compat Parity)

Add adapter support/tests for ACP capabilities that are relevant but not fully exercised by current compat suite:

  • session/load / session/resume path handling when available.
  • session/fork bridging through ACP when supported.
  • session/cancel correctness from OpenCode abort flows.
  • Proper handling of ACP notifications and request/response ordering through reconnect/restore windows.
  • Preservation of _sandboxagent/... extension method naming (no /v1/ in method prefix).

Test Plan

1) Existing parity suite (must pass)

  • server/packages/sandbox-agent/tests/opencode-compat/*
  • Keep compatibility behavior for session, messaging, events, models, permissions, questions, tools.

2) New persistence/restore tests (required)

  • Restart test: create session + message, restart server, verify /opencode/session + messages restore.
  • Lazy restore test: stale connection forces restore on first prompt and succeeds.
  • Replay injection test: confirm replay preamble is injected once after restore.
  • Storage loss test: wiping DB causes non-recoverable sessions (expected behavior, explicit assertion).

3) ACP gap tests (required)

  • session/cancel round-trip from OpenCode abort endpoint.
  • session/load/session/resume (if runtime advertises capability).
  • session/fork mapping with parent linkage preserved in metadata projection.

4) Migration and WAL tests

  • Migration bootstrap from empty DB succeeds.
  • WAL mode enabled at runtime.
  • Projection rebuild from persisted events produces deterministic session/message views.

Rollout / Gate

Only re-enable /opencode/* and CLI opencode command after:

  1. OpenCode compat parity suite passes.
  2. New persistence/restore tests pass.
  3. ACP gap tests above pass.
  4. No regressions in /v1/* ACP core tests.

Implementation Checklist

  • Create server/packages/opencode-adapter crate.
  • Add workspace dependency wiring (Cargo.toml root + package Cargo files).
  • Port OpenCode handlers/session bridge into adapter modules.
  • Add sqlx dependency/features and migration framework.
  • Implement SQLite journal store (sessions, events, metadata table).
  • Implement lazy restore + replay injection.
  • Implement runtime projection reducer from ACP events.
  • Mount /opencode router in sandbox-agent only after tests are green.
  • Re-enable CLI opencode command.
  • Update docs (OpenCode compatibility + session restoration semantics).

Risks / Mitigations

  • Risk: Projection bugs produce subtle OpenCode shape mismatches.
    • Mitigation: golden/event-sequence tests + parity SDK tests.
  • Risk: replay payload growth impacts prompt quality.
    • Mitigation: strict replay_max_events + replay_max_chars bounds.
  • Risk: process restart while turn in-flight causes partial state.
    • Mitigation: projection derives terminal/active status from event phases; explicit stale-turn reconciliation rule.

Explicit Behavior Contract

  • Sessions are automatically restorable when live runtime/session state is lost, as long as persisted storage remains.
  • If persistence storage is lost/wiped, prior sessions cannot be restored.