Bundle Entry:
docs/spec/HOST_RUNTIME_BUNDLE_v0.1.mdLegacy Split Doc: Prefer the bundle entrypoint for agent retrieval; this file retains detailed, topic-local semantics.
Host Bridge Protocol v0.1
This document is normative for bridge-backed host capabilities: - editor/* - gfx/* - gpu/compute::* - io/net::*, io/db::*, sys/process::*, core/crypto::* - host/plugin::*, host/ffi::*, and model-provider families
Goals
- Deterministic request/response transport for host-integrated capabilities.
- Uniform policy enforcement (
bridge_cmd,bridge_args,timeout_ms,max_bytes). - Stable behavior across native and WASI host profiles.
Invocation Contract
For a single capability request (bridge_transport = "spawn-per-op"):
- Runner resolves and executes
bridge_cmdunder opbase_dir. - Runner appends the requested op symbol as the final CLI arg.
- Runner sets env vars:
GENESIS_HOST_BRIDGE_OPGENESIS_HOST_BRIDGE_FAMILY
- Runner writes one framed request payload to bridge stdin.
- Bridge writes one framed response payload to stdout.
For persistent capability requests (bridge_transport = "persistent-stdio"):
- Runner resolves and executes
bridge_cmdunder opbase_dironce per deterministic session key within one runner execution. - Runner appends the requested op symbol as the final CLI arg.
- Runner sets env vars:
GENESIS_HOST_BRIDGE_OPGENESIS_HOST_BRIDGE_FAMILYGENESIS_HOST_BRIDGE_TRANSPORT=persistent-stdio
- Runner reuses the live bridge process and writes one framed request payload per op invocation.
- Bridge writes one framed response payload per request and remains alive for the next frame.
A persistent session is owned by the current effect runner, never by a process-global cache. Its key cannot carry a process, socket, database, GPU, graphics, plugin, FFI, or model session across runner requests. Returning success or a sealed error from the runner, unwinding the runner, cancelling its worker, or replacing/restarting the daemon drops the owner and closes every session.
stderr is reserved for diagnostics and is included in deterministic error mapping when the bridge exits non-zero.
Framing (Normative)
Request and response payloads use UTF-8 CoreForm terms with text framing:
- Header: ASCII decimal byte length of payload text.
- Delimiter: single
\n. - Body: exact payload bytes (length must match header exactly).
Format:
<len>\n<payload-bytes>
Example:
17\n{:ok true :id "x"}
Policy Enforcement
bridge_cmdis required per op.bridge_transportis optional per op:spawn-per-op(default)persistent-stdio(session reuse)
- Optional bridge identity constraints:
bridge_cmd_allowlist(array): explicit command identity allowlist. - entries may match
bridge_cmdtoken, resolved absolute path, or executable filename.
- entries may match
bridge_cmd_sha256(string): expected executable digest (64 hex, optionalsha256:prefix).
timeout_msis a hard process-tree deadline for both transports.- On hosts advertising process-tree termination support,
spawn-per-opcreates a separately killable process tree. Success, non-zero exit, protocol error, and timeout terminate residual descendants, reap the child, and join all I/O pumps before returning. persistent-stdiotimeout signals the process tree, closes the request channel, joins the sole worker that owns and reaps the child, verifies no process-group member remains, and evicts the session. It never retries the uncertain timed-out request.
- On hosts advertising process-tree termination support,
- Hard bridge timeouts require platform process-tree termination support. Current Unix hosts use a dedicated process group per bridge tree. Other hosts fail closed with
<family>/bridge-policyinstead of advertising or attempting a cooperative timeout. max_bytesapplies to both request payload size and response payload size.- Violations return deterministic sealed errors with family-scoped codes:
<family>/bridge-required<family>/bridge-identity-denied<family>/bridge-timeout<family>/bridge-payload-too-large<family>/bridge-response-too-large<family>/bridge-parse<family>/bridge-exit
Determinism
- Payload hashing and continuation hashing remain owned by the effect runner (
.gclogsemantics unchanged). - Bridge transport errors are represented as sealed ERROR values and are replay-stable.
Ownership And Teardown
HostBridgeRuntimeis the explicit owner for all persistent bridge sessions in one runner execution.- The owner contains no ambient process-global session map. Production capability dispatch must receive the owner explicitly.
- The effect runner explicitly shuts down the owner on every success and error exit before returning its public result. A shutdown failure returns
EffectsError::Cleanup { subsystem: "host-bridge", ... }; it takes result precedence while retaining the initiating execution error asprior_error. - A persistent worker exclusively owns its
Child. Teardown must signal before join, let that owner reap the leader, then perform the bounded residual-group verification. Waiting for group disappearance before joining the child owner is forbidden because it misclassifies the owner’s unreaped leader as a surviving process. - Teardown is bounded. Failure to signal, join, reap, or eliminate a live residual member returns a family-scoped
bridge-reaperror; it is never rewritten as successful cancellation. - Persistent worker completion carries the worker’s own child-reap result through the join handle. Session eviction and multi-session owner shutdown attempt every owned session, return cleanup failures in canonical session-key order, and preserve the error that initiated cleanup.
- Spawn-per-operation teardown owns every started stdin/stdout/stderr pump until join. On supported Unix hosts, pumps use nonblocking pipes and an owner-only cancellation signal so a failed process-termination attempt cannot leave
join()waiting on a live child’s open pipe. Thread-spawn, wait, timeout, and residual-verification paths first attempt process-tree termination as applicable; after a termination failure they cancel and join every pump in fixed stdin/stdout/stderr order before a separate bounded fallback termination/reap. All stop/reap/join failures are aggregated, and family-scopedbridge-reaptakes precedence over the initiating operation result while retaining that prior error. If both the process-group signal and leader kill fail, teardown returns that combined failure without entering an unbounded leader wait. - First-party network close operations remove their TCP, pending HTTP, or WebSocket handle before teardown, attempt every applicable write/flush/close-frame/shutdown action in protocol order, and aggregate failures under
net/cleanupat the public bridge boundary. A preceding operation failure is retained in:prior-error; a cleanup failure cannot be rewritten as successful close. - Recreating a runner after daemon restart creates a fresh bridge generation. Logical IDs or processes from the retired owner cannot be reused.
WASI Profile
- If bridge process execution is unavailable, runtime returns deterministic
*/bridge-not-supported. - WASI hosts that implement bridge transport must preserve the same framing and policy semantics.
Conformance
Conformance tests: - Native framing, owner lifetime, success/error descendant reap, timeout/cancellation, restart, and repeated-load tests: crates/gc_effects/src/runner_host_bridge_tests.rs. - First-party TCP, pending HTTP, and WebSocket close-error propagation after handle removal: crates/gc_cli_driver/src/host_bridge_runtime_tests.rs. - End-to-end bridge replay tests: crates/gc_effects/tests/gfx_gpu_bridge.rs, crates/gc_effects/tests/editor_bridge.rs. - Mandatory aggregate gate and machine report: scripts/check_host_bridge_fault_injection.sh and .genesis/perf/host_bridge_fault_injection_report.json.