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+ envelopeSessionEventjournal. - 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>) -> RouterOpenCodeAdapter::new(...)OpenCodeAdapterConfig(db path, replay limits, feature toggles)
Responsibilities split
sandbox-agentpackage:- Own server bootstrapping, auth, and top-level routing.
- Mount
/opencode/*by nesting adapter router.
opencode-adapterpackage:- 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)
sessions
id TEXT PRIMARY KEY(local/session ID exposed to OpenCode)agent TEXT NOT NULLagent_session_id TEXT NOT NULL(current live ACP session id)last_connection_id TEXT NOT NULLcreated_at INTEGER NOT NULLdestroyed_at INTEGER NULLsession_init_json TEXT NULL(JSON-encoded ACPsession/newinit payload)
events
id TEXT PRIMARY KEYsession_id TEXT NOT NULLcreated_at INTEGER NOT NULLconnection_id TEXT NOT NULLsender TEXT NOT NULL(clientoragent)payload_json TEXT NOT NULL(full ACP envelope JSON)- index:
(session_id, created_at, id)
opencode_session_metadata(small adjunct metadata table)
session_id TEXT PRIMARY KEYmetadata_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=WALon initialization. - Set
PRAGMA synchronous=NORMAL(orFULLif we prioritize durability over throughput; default recommendation:NORMALfor compatibility throughput). - Use
sqlx::migrate!()with versioned migrations under package-localmigrations/.
Restore Algorithm (TS-style, Rust implementation)
For each session request (lazy restore trigger path):
- Read persisted
sessionsrow. - Ensure active ACP connection for the target
agent. - If connection/session binding is still valid, continue.
- If stale:
- Collect replay source from persisted
events(lastreplay_max_events). - Build replay text (bounded by
replay_max_chars) from ACP envelopes:- include
createdAt,sender,payloadJSON line format. - append truncation marker if over limit.
- include
- Recreate ACP session (
session/neworsession/load/session/resumedepending on capabilities; fallback tosession/newwith stored init). - Update persisted
agent_session_id+last_connection_id. - Queue replay injection for next prompt.
- 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)
- ACP journal (
- Use projection reducers for:
- session status (
idle/busy/error) - message records and parts
- tool call/result linkage
- pending permission/question state
- session status (
- 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.rsinto 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
/opencodeinsandbox-agentrouter. - Re-enable CLI
opencodecommand 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/resumepath handling when available.session/forkbridging through ACP when supported.session/cancelcorrectness 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/cancelround-trip from OpenCode abort endpoint.session/load/session/resume(if runtime advertises capability).session/forkmapping 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:
- OpenCode compat parity suite passes.
- New persistence/restore tests pass.
- ACP gap tests above pass.
- No regressions in
/v1/*ACP core tests.
Implementation Checklist
- Create
server/packages/opencode-adaptercrate. - Add workspace dependency wiring (
Cargo.tomlroot + 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
/opencoderouter insandbox-agentonly after tests are green. - Re-enable CLI
opencodecommand. - 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_charsbounds.
- Mitigation: strict
- 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.