Native OpenCode worker control checkpoint
VUH-1640 lifecycle follow-up
Prepared hires now register their chosen stable Herdr name through the existing
agent.rename path after native session binding. Later census uses that same
subject instead of creating a persona from the pane's native title.
opencode-fleet-lifecycle.integration.test.ts crosses the real hire, prepared
host, WebSocket controller, native worker runtime, dedicated SQLite history,
census, persona/conversation stores and roster. Only OS/Herdr and injected native
SDK boundaries use fixtures derived from the pinned live format. It varies native
titles, reads a running then completed child without opening another chat, keeps
an unrelated seat unaddressed, confirms native exit even when its reply drops,
and refuses a switched session. A runtime test also refuses an observed A→B→A
route change during exit authorization.
The SSH history/follow-up case models pickup when its injected SDK accepts the
third prompt: native session status becomes busy, and the fixture applies the
real controller's pane.report_agent state to Herdr. Its message_seat receipt
must report working while retaining consumed delivery and one native dispatch.
Keeping either boundary permanently idle forces the production pickup loop to
spend its full ten-second wait, which can exhaust the unchanged 30-second test
budget under load. The fixture changes neither that wait nor the timeout.
A separate private-service native E2E used OpenCode 1.18.18 and the existing
openai/gpt-6.1-sol provider. All 19 fleet reads retained Oriana Vale's hire
persona and conversation despite the distinct native OC | oriana-vale-6303
title. A task child reported running then done with its call ID and source
timestamps, without opening another chat. close_seat returned closed: true;
the owned pane disappeared without physical-close fallback. The private service
was stopped and the shared discovery descriptor remained byte-identical. Native
title variation and owner-switch races were exercised in the integration
fixtures; the live check did not synthesize those races.
Original VUH-1555 checkpoint
VUH-1555, 2026-10-04. The original source and deterministic-fixture checkpoint
below predates live OpenCode acceptance. The selected API is OpenCode 1.18.18, upstream
commit 4643e65ad6334de3e4e68dedc201d5fbb828c9fe. Other versions are refused.
No OpenCode process, provider request, model turn, owner database or remote
fleet was used for these checks.
Implemented local control
The service registers a prepared OpenCode adapter on macOS. The existing hire
request supplies the harness, working directory, role, optional provider/model
and native variant. Project role/cap and conversation admission still precede
allocation. The native provider/model/variant catalog is checked before creating
a native session; unsupported account, skill, Chrome and extra-argv options fail
explicitly. This control path does not grant tools or change fleet tool policy.
Preparation writes private per-launch native plugin configuration. It preserves
other native configuration and permissions, disables inherited personal Linear
MCP entries, and selects the worker clankie mcp --fleet projection. It does not
write the owner's profiles or answer permission/question prompts. The separate
TUI plugin uses the SDKv2 client inside the one interactive OpenCode process.
The operator plugin's credentials and SDKv1 calling convention are not reused.
A single Herdr layout.apply creates a new tab with an initial argv command.
There is no pane run, agent start, terminal typing, second server, or fallback
launch. Unknown allocation keeps the hire uncertain and its capacity reserved.
Refused preparation disposes only its controller and temporary configuration.
The controller admits one held loopback connection only after independently
checking its OS socket owner against the allocated pane's original native root.
The root must remain the foreground process and canonical installed executable,
with the expected actual cwd, Herdr instance, terminal, and microsecond kernel
birth time. The public macOS libproc ABI helper validates the struct and exact
return lengths, including the PID's cwd through PROC_PIDVNODEPATHINFO. Cwd
proof does not launch lsof's blocking filesystem metadata probes. Process facts
are read before and after each validation; the original socket and root are
rechecked around admission awaits. The real-process/Unix-socket golden test also
revokes proof after a native cwd change or process exit, without extending its
30-second timeout. A secret in the private launch config routes the connection but cannot
substitute for those observations. Loss or replacement retires control; there is
no reconnect or process adoption. Retirement closes the owned listener and all owned sockets, evicts only the
matching adapter control, and removes only temporary launch config. Uncertain
delivery records survive. Cleanup is one-shot; late admission and repeated close
cannot reactivate it. The first project-hire observation stores the
actual verified lifetime so later metadata cannot retroactively prove a hire.
Only the first awaited TUI plugin initialization may create and navigate to a
fresh native session. Pinned native source waits for that initialization before
mounting Home or Session prompts. The native initializer supports exact --session
without create/navigation, but the adapter refuses a new resume process until
original exit can be independently proven. Existing live control can be reused.
Reload/reattach cannot initialize again or acquire a replacement controller.
Every send checks the displayed route, native session, native status and pending
permissions/questions across authority awaits. The service saves a delivery
claim before the native promptAsync call, which includes an explicit session
and message ID. Receipt loss or a route/connection change after submission stays
unconfirmed and is never retried. Accepted delivery means native queue
acceptance. Completion additionally requires the exact parent message, completed
native timestamp and final finish/error; idle alone is insufficient. Native
interrupt targets only the selected session and never answers an owner decision.
Limits that remain
- Deterministic native API/loader fixtures do not prove the installed native binary or real owner draft/busy races. Live compatibility and model delivery remain owner-run acceptance.
- Solid observation detects observed route generations. Native Solid batching can coalesce intermediate A→B→A changes. Snapshot checks are not an atomic transaction with server acceptance; exact session IDs prevent retargeting and detected post-dispatch changes produce uncertainty.
- Physical pane close remains unavailable for controller-prepared OpenCode: the
selected Herdr API has only
pane_id, without a root-lifetime compare-and-close condition. An owned live worker can exit itself through the original process-bound TUI'sapp.exitcommand. Controller authorization and the native route generation are checked before dispatch;closed: truerequires the original terminal to disappear. A dropped exit reply is observed, never retried. Cold, replaced or switched sessions retain refusal. Native exact-session interrupt remains available. A deny-only record in the existing watch state retains the allocated pane and original terminal across replacement/restart; it never grants authority. Unmanaged legacy close behavior is unchanged where no prepared-launch fact exists. An unknown allocation has no proven pane address and is never closed as compensation. Controller disposal neither terminates the native process nor deletes its history. - Restart does not restore live control. Durable unresolved delivery claims survive; a saved session label cannot authorize reattachment or resend.
- Remote configured-fleet control and new-process saved-session continuation remain unimplemented. Native history discovery covers only registered dedicated local worker profiles, not arbitrary installed profiles/channels or attached servers. Live SDK history uses the existing normalized projection, bounded to 100 native messages. It is not a parallel persisted transcript store or complete history.
- Native executable discovery is PATH-based and requires a direct executable;
script wrappers, other operating systems and unsupported versions fail closed.
The Python/libproc helper is a required macOS dependency, not emulated on
another platform. Even
--versionimports modules that initialize native filesystem paths, so capability discovery uses a disposable cwd/HOME/XDG/DB/ config/temp environment without inherited owner configuration or credentials.
Native stored history
Each new prepared worker receives its own absolute OPENCODE_DB in
captain/opencode-workers/profiles/<random>/opencode.db. OpenCode creates and
migrates that native database. Clankie only records a metadata address after
fresh original controller/root/session checks and an exact matching database
session row. Native data survives refused/uncertain allocation and controller
cleanup. No transcript mirror is written, and descriptors grant no control.
clankie agents list --host local and clankie agents read local:ses_… --tail 50
use the existing owner-authenticated API. The TUI identifies stored native history
and surfaces unavailable profile errors. agents resume … --conversation ID can
reuse the same live controller; missing control or unproven original exit refuses
without creating another native process. The TUI asks for the hiring conversation
when reusing a native OpenCode seat. Existing file-backed sessions retain their
own resume behavior. Saved Pi live reuse also requires its original prepared
controller verification; metadata-only reuse is refused, including without a brief.
Live native reuse checks the exact pane, terminal, session and process lifetime
before and after conversation/project admission awaits. Cwd must equal the saved
canonical directory. Pi additionally requires herdr:pi with the exact canonical
saved transcript path; an ID-only report or the same UUID at another path cannot
be adopted. This prepared reuse is local only. It retains the original
project-hire process proof and refuses replacement without another allocation.
Loss after accepted dispatch stays uncertain.
The reader validates the pinned native v1/export schema and migration revision.
It bounds discovery to 100 profiles, messages to 500 (default 50), parts to 2,000
and payload to 4 MiB before JSON allocation. Full native text/output/error is
redacted before chunking, and published entries use the existing strict transcript
parser. Tool refs/names are bounded; unsupported object outputs/errors refuse
rather than being stringified. Malformed JSON does not echo record fragments. Unknown schema, foreign/aliased or
replaced files, redirected WAL sidecars and v2-only history report unavailable.
It uses SQL read-only mode that refuses a missing database, trusted_schema=OFF, normal locking and a
short transaction. In dedicated worker profiles only, SQLite may update SHM reader marks or recreate
WAL/SHM files as normal native reader coordination. Fixture checks distinguish
that coordination from unchanged DB and existing WAL content; a closed-writer
fixture checks recreated sidecars against unchanged DB bytes and logical rows.
Filesystem preflight is not an atomic no-create guarantee. This is not a claim
of zero filesystem writes. No owner/global
database is read or repaired, and no native CLI, history write, migration,
checkpoint, disabled lock or immutable-live-DB shortcut is invoked. This matches
SQLite read-only WAL behavior
and the native WAL index lifecycle.
History is stored v1 export content, which can differ from the TUI's staged revert view. Cursors bind the exact local profile/database identity/session/schema and bounded content/revert fingerprint; mutation or rewind resets the page. A native summary has a discriminated SQLite source and projection byte count, not a fake JSONL file or the whole database size attributed to one session.
Verification and primary source
The focused tests cover the actual prepared-hire flow, first-proof retention, role/project retargeting, wrong pane/session/harness, same-PID birth changes, wrong socket/cwd/executable/Herdr binding, unknown allocation, no duplicate hire, controller restart uncertainty, owner questions/permissions, exact resume, unsupported model/variant, and no terminal fallback. A real loopback fixture exercises the actual TUI loader/protocol with a mocked native API and host Solid adapter; it does not invoke OpenCode. A separate read-only native ABI fixture observed only its own short-lived non-agent Python process, then rejected the exited process and invalid PID. Full repository validation is held for root review of this immutable checkpoint.
Selected upstream sources:
- TUI plugin API: readonly route/status/permission/question state and native SDKv2 client.
- TUI startup: awaited plugin initialization before ready-gated prompt mounting.
- SDKv2: flattened exact-session arguments, messages limit, provider/model variants.
- Native final-message semantics:
final finish excludes
tool-callsandunknown. - OpenTUI shared Solid support: runtime plugin aliases use the host's Solid instance.
Herdr source was inspected at 4812c9054cfce3e294a300c60d30d78d2a447d38:
src/app/api/layouts.rs and src/pane.rs implement native initial argv;
src/api/schema/common.rs and src/app/api/panes.rs confirm the close limitation.
Source inspection and fixture results do not establish owner deployment.
The history fixture DDL is a licensed excerpt of the pinned native
schema.gen.ts;
the reader checks its participating table/index shapes and exact
migration.gen.ts
revision without executing either upstream module.
