|
| 1 | +# optio-opencode — Agent Cheatsheet |
| 2 | + |
| 3 | +Run `opencode web` as an optio task. Either a local subprocess or a |
| 4 | +remote host reached over SSH; the optio dashboard embeds opencode's UI |
| 5 | +via the widget proxy. |
| 6 | + |
| 7 | +Full design: `docs/2026-04-22-optio-opencode-design.md`. |
| 8 | + |
| 9 | +## Public API |
| 10 | + |
| 11 | +```python |
| 12 | +from optio_opencode import ( |
| 13 | + create_opencode_task, |
| 14 | + OpencodeTaskConfig, |
| 15 | + SSHConfig, |
| 16 | + DeliverableCallback, |
| 17 | +) |
| 18 | + |
| 19 | +async def on_file(path: str, text: str) -> None: |
| 20 | + ... |
| 21 | + |
| 22 | +task = create_opencode_task( |
| 23 | + process_id="my-task", |
| 24 | + name="My task", |
| 25 | + config=OpencodeTaskConfig( |
| 26 | + consumer_instructions="...", # prepended with optio-opencode's base prompt |
| 27 | + opencode_config={"model": "..."}, # passthrough to opencode.json |
| 28 | + ssh=None, # None = local subprocess |
| 29 | + on_deliverable=on_file, |
| 30 | + install_if_missing=True, |
| 31 | + ), |
| 32 | + description="optional", |
| 33 | +) |
| 34 | +``` |
| 35 | + |
| 36 | +The returned `TaskInstance` has `ui_widget="iframe"` baked in. |
| 37 | + |
| 38 | +## Log-file contract |
| 39 | + |
| 40 | +optio-opencode tells opencode (via AGENTS.md) to append one line per |
| 41 | +event to `./optio.log` with keywords: |
| 42 | + |
| 43 | +- `STATUS: [N%] <msg>` |
| 44 | +- `DELIVERABLE: <path>` |
| 45 | +- `DONE[: summary]` |
| 46 | +- `ERROR[: message]` |
| 47 | + |
| 48 | +The Python side tails the file, dispatches by keyword, SFTPs |
| 49 | +deliverable files back, decodes UTF-8, and invokes `on_deliverable`. |
| 50 | +DONE / ERROR terminate the session; other keywords flow through as |
| 51 | +progress updates / log entries. |
| 52 | + |
| 53 | +## Operating modes |
| 54 | + |
| 55 | +- **Local**: `asyncio.create_subprocess_exec("opencode", "web", ...)`. |
| 56 | + Workdir is a `tempfile.mkdtemp`. Expects opencode pre-installed |
| 57 | + (or use `OPTIO_OPENCODE_BINARY_DIR`, below). |
| 58 | +- **Remote**: single asyncssh connection multiplexes exec (install, |
| 59 | + launch, `tail -F`, teardown), SFTP, and local port forward. Workdir |
| 60 | + is `/tmp/optio-opencode-<uuid>/` on the remote. |
| 61 | + |
| 62 | +## Shipping a specific opencode binary |
| 63 | + |
| 64 | +Set `OPTIO_OPENCODE_BINARY_DIR` in the worker's environment to a |
| 65 | +directory matching opencode's build layout (i.e. containing per-target |
| 66 | +subdirs like `opencode-linux-x64/bin/opencode`, `opencode-darwin-arm64/ |
| 67 | +bin/opencode`, `opencode-linux-x64-baseline-musl/bin/opencode`, …). When |
| 68 | +set, optio-opencode: |
| 69 | + |
| 70 | +1. Detects the target host's OS / arch / libc / AVX2 via `uname`, `ldd` |
| 71 | + and `/proc/cpuinfo` (detection mirrors opencode's upstream install |
| 72 | + script so the subdir name is the one opencode's build would emit). |
| 73 | +2. Resolves the matching binary inside `OPTIO_OPENCODE_BINARY_DIR`. |
| 74 | +3. **Local mode:** runs that binary directly (bypasses any `opencode` |
| 75 | + on PATH). |
| 76 | +4. **Remote mode:** SFTP-uploads the binary to `~/.local/bin/opencode` |
| 77 | + on the remote host (atomic via temp-file + rename), skipping the |
| 78 | + upload when the existing file's SHA-256 already matches. Launches |
| 79 | + via the absolute path, so the user doesn't need `~/.local/bin` on |
| 80 | + their PATH for optio-opencode to work (though they'll benefit from |
| 81 | + having it on PATH for later manual invocations). |
| 82 | + |
| 83 | +When the env var is unset, behavior falls back to the previous scheme: |
| 84 | +local mode expects `opencode` on PATH, remote mode runs the upstream |
| 85 | +curl installer if `opencode` is missing. |
| 86 | + |
| 87 | +This is a bridge feature for shipping an iframe-embeddability fork of |
| 88 | +opencode until those fixes land upstream; once upstream ships, the env |
| 89 | +var's only remaining use is pinning to a specific build. |
| 90 | + |
| 91 | +## Testing |
| 92 | + |
| 93 | +Unit + local integration: `pytest tests/` (needs MongoDB via Docker). |
| 94 | + |
| 95 | +Remote integration: `pytest tests/test_session_remote.py` — skips on |
| 96 | +machines without Docker; brings up `linuxserver/openssh-server` on |
| 97 | +`127.0.0.1:22222`. |
| 98 | + |
| 99 | +## Known limits (MVP) |
| 100 | + |
| 101 | +- SSH auth is key-path only; no agent, inline keys, or passwords. |
| 102 | +- No host-key verification (`known_hosts=None`). |
| 103 | +- Fail-fast on SSH drop; no reconnect. |
| 104 | +- Text deliverables only (non-UTF-8 files are skipped, not delivered). |
| 105 | +- Grace-period sensitive: teardown can exceed 5 s; host apps running |
| 106 | + slow / remote sessions should call |
| 107 | + `optio_core.shutdown(grace_seconds=30)`. |
| 108 | + |
| 109 | +Deferred items live in spec Section 11. |
0 commit comments