Clankie in OpenCode
clankie opencode --conversation ID --dry-run reviews a launch.
Remove --dry-run to open the native interactive TUI. --resume reopens only
the recorded native session and conversation. A fresh launch takes the global
chat while no live seat holds it, else (or with --new) creates its own workspace
chat; --conversation ID selects an existing chat, and --dry-run
creates nothing. Older resume records without a conversation retain the global
chat. The service resolves the workspace before launch; a changed resume
workspace fails closed. /opencode in Clankie's
console reviews the same plan. Requires the running Clankie service, a brokered
operator credential, OpenCode 1.18.0 or newer, and clankie on PATH. Capability
discovery checks the installed version and native session/server flags before
launching, and names a missing flag.
CLANKIE_OPENCODE_BIN can select an absolute native binary.
The exported planOpenCodeSeat and runOpenCodeSeat functions provide the local
launch API. This change does not add a remote HTTP terminal-launch endpoint.
Service integration uses the existing operator seat-context, prompt, memory,
MCP, outbox and transcript HTTP APIs. Native OpenCode workers use a separate
control binding (VUH-1555); shared transcript projection does not share operator
authority.
Install, settings and removal
The plugin ships with Clankie; no global OpenCode plugin install is required.
The launcher adds its file URL and the shipped skills to
OPENCODE_CONFIG_CONTENT. Existing inline settings and unrelated MCP servers
remain. The plugin replaces the clankie MCP entry with
clankie mcp --lane operator and disables inherited Linear-named/Linear-hosted
MCP connectors for this launch, so tracker work uses Clankie's connected account.
The native permission settings are unchanged. The launcher sets
autoupdate:false for its own process. /skills lists the shipped skills.
Neither installation nor launch edits owner configuration.
Identity and the current operator memory card are loaded from the service in
experimental.chat.system.transform, before each model request. The native
plugin's config, chat.message and session/message events establish identity
and projection. OpenCode's own workspace and owner skills still apply.
The launcher resolves its operator bearer from the broker; it never places it
in the native process environment, plugin, launch plan or local bridge journal.
The plugin receives only a per-launch loopback bridge token. MCP independently
uses the existing broker-backed operator bridge; no account credentials move.
Exit the native TUI to stop the seat. Nothing remains globally enabled, so no
OpenCode uninstall/config cleanup is needed. Removing the bundled Clankie plugin
files removes support; do not run opencode uninstall. Resume metadata and
receipt journals live under the Clankie state directory in opencode-seat.json
and opencode-seat-launches/. Preserve uncertain receipts until reconciled.
Delivery and failures
Delivery starts only after the bound session has loaded its operator context.
An exact resumed session is verified with the injected SDK and preflights the
service context during plugin startup. A newly created root session performs
the same read-only preflight on its native creation/binding event. Neither
requires an owner prompt or bootstrap model turn. An uncreated session cannot
bind wakes; the plugin never creates or guesses one. Every real model request,
including a native wake, still loads fresh identity and memory in the system hook.
Resuming uses the exact --session
ID; it never uses --continue. The plugin calls its injected native SDK client,
not a guessed server URL or a separately started server. Idle dispatch uses
session.promptAsync; busy sessions wait. This does not claim active-turn
steering. Owner drafts are untouched: no prompt append/submit endpoint or
terminal input is used. Native approvals remain owner decisions.
A new root session or a prompt in another session disables the binding. An unsubmitted navigation to an existing session is not proven detectable by the server plugin; delivery remains pinned to the original ID and never follows UI focus. Do not use session switching as a way to transfer the seat. Exit and launch the selected exact session instead.
Before dispatch the launcher exclusively creates a binding-scoped unresolved
receipt under opencode-seat-receipts/, in addition to its per-launch audit
journal. This fences every retry, including another launcher. Native queue
acceptance changes the audit outcome to delivered with deliveryStage: consumed; it does not prove model attention or task completion. A lost receipt
blocks dispatch until exact-session native history contains the complete
original event, including its ID, or the original native acceptance arrives.
A new session cannot reconcile an old session's event. A corrupt receipt fails
closed. This read-only reconciliation never launches a replacement turn.
The service bridge explicitly acknowledges the event ID after the launcher
persists its pending event. That receipt is delivered, not consumed, and may
precede native dispatch. Pending events retained only in a launch journal are
inspection evidence and are not automatically replayed after restart. No
exactly-once work completion guarantee is claimed. A new launcher still needs
fresh native identity and context preflight; a saved ID alone never arms it.
Missing binary/capability, absent broker credential, changed resume workspace, wrong conversation, disconnected bridge, duplicate claim, mismatched session, a deleted session and uncertain delivery all stop the seat explicitly.
Context reads. A slow or failed read of Clankie's identity and memory card is
retried three times with a short backoff. If every attempt fails, only that turn
fails, with a native toast and a context_unavailable warning in the launch
journal. Queued service events wait until context loads again, rather than
entering a turn without it. The plugin keeps retrying in the background, so
delivery resumes without an owner prompt.
Model errors. A provider error (session.error, such as an invalid key or an
unavailable model) also fails only its turn. It is recorded as a
turn_failed: … warning, and later turns still deliver.
First stop cause. The launch journal's failure and the close line keep the
first cause that stopped the seat. A later refusal, including the bridge's own
409 to a follow-on poll, never replaces it. Native transcript sync errors
are reported through native toasts and the launch journal, and retained native
records retry at later events. They do not
remove the loaded memory card. The service must support OpenCode ses_* upload
IDs; an older service rejects projection. Never restart a shared service merely
to complete an acceptance probe.
Verification status
Deterministic tests cover native busy/idle behavior, identity mismatch,
claim-before-dispatch and uncertain delivery, role boundaries, bridge auth,
config preservation, exact resume, preflight-before-ready ordering, context
failure and identity mismatch without extra turns.
opencode-seat-resilience.integration.test.ts runs the installed OpenCode (1.18
or newer; last run on 1.19.3) server with this plugin, the real seat bridge and an in-process Clankie service;
only the model provider is a fixture. It covers a stalled context read, a turn
ending in a provider error followed by a delivered app turn, and a preserved
first stop cause (evidence). Scoped native evidence and remaining
acceptance gaps are recorded in
the verification notes.
Native draft preservation and pending approval behavior were checked in an
owned scratch TUI. The complete production outbox/projection cycle remains
unverified; unit tests do not substitute for that acceptance.
Native references: plugins, server, configuration.
Hired native workers
worker-tui.mjs is a separately loaded native TUI plugin. Local macOS workers
require a direct OpenCode executable at 1.18.18 or newer and a
controller-created initial argv pane in Herdr. On load the plugin probes every TUI
and SDK call a worker makes; a missing one answers initialization with
OpenCode VERSION lacks the Clankie worker surface: … and creates no session. The controller verifies the original process, socket, cwd
and session; the plugin observes that TUI's displayed route and uses its SDKv2
client. It leaves prompt drafts, permissions and questions with the owner.
worker-server.mjs selects clankie mcp --fleet, without an operator token.
Inherited personal Linear connectors are disabled for this worker launch;
owner configuration and native permissions are preserved.
Only the first awaited TUI initializer may create and navigate to a fresh native session, before the native prompt mounts. Delivery names the exact session and message. Native queue acceptance is a receipt, not model attention or completion; completion requires the matching finished native reply. Route changes, lost control and uncertain sends never trigger another launch or resend. Native interrupt targets only the bound session.
clankie harness refresh-tools [--pane PANE] uses that same original controller
to observe the existing clankie MCP connection. The service publishes its
catalog revision while idle, and OpenCode's native tools/list_changed handler
reads definitions through the existing client. Refresh never calls native
mcp.connect or closes a transport: the pinned reconnect replaces the client
without an atomic activity guard and could interrupt a call beginning during
the request. The service requires an actual bridge tools/list observation at
the requested revision as well as the native SDK's connected status.
Running sessions (including background native sessions), owner decisions and
concurrent controller actions skip refresh. Process, account, session route and
fleet admission remain fenced before observation and after the native response.
An original native event observer holds refresh when a child becomes busy or
an owner decision appears during awaited admission, even if that activity ends
before the admission returns. Missing activity observation refuses refresh.
The operation preserves the displayed session, draft and in-flight calls and
submits no model turn. Native connected acceptance is recorded separately from exact
model-visible tool names, which OpenCode's public TUI SDK does not expose.
Registered dedicated worker SQLite history is available through
clankie agents list and clankie agents read. It is bounded stored v1 content,
not proof of the currently displayed session or control.
clankie agents resume … --conversation ID requires the original live controller
and fresh identity/cwd checks, even without a brief. General profile discovery,
restart reattachment and new-process resume remain unavailable.
Linked Mac POSIX workers use the same plugin and controller over a private
loopback SSH forward. The service stages its own helper/plugin files in an owned
private remote directory, proves both socket ends and checks the native process
and Herdr allocation afresh. The helper reuses Clankie's registered SQLite reader;
provider secrets and owner-wide profiles do not cross the link. Remote history
uses <fleet>:ses_…; live reuse never creates another writer. Node 24+, Python 3,
Herdr, native 1.18.18 or newer and the existing Clankie fleet link are required
there.
Windows is unsupported. Remote fixture coverage is not a live acceptance claim.
An owned live worker can exit through its original TUI's app.exit command;
success requires its original terminal to disappear. Cold, replaced or switched
sessions refuse this control. There is no unconditional physical pane-close
fallback. See the
worker evidence and limits
for the original deterministic checkpoint, later native persona/exit evidence
and the live checks still open; those stages are separate.
