Files
buzz/docs/host-execution.md
2026-08-31 16:41:18 -04:00

18 KiB

Host execution foundations (not yet an enabled remote feature)

The owner-operated native Start outbox, receiver and Hosts picker are implemented behind the non-default remote-start-preview Cargo feature. Default builds advertise accepts_start: false and reject queuing. Even a preview build advertises Start only with a successful same-owner/community receiver pump in the last 15s and at least one destination-local provisioned, compatible configuration with an available agent key (the same availability refusal as launch). A runtime catalog entry alone is not launch capability. This is not a release or physical-host/provider certification.

Kinds 50001/50002 are admitted only through the owner's global transport with an exact nondeleted registration. Commands are owner-signed, receipts host-signed; host keys receive no general login privilege. Query/result gates remain owner-only and additive migration 0042 excludes both ciphertext kinds from FTS (heap rewrite; plan deployment maintenance). Private profile v3 carries only agent public key, runtime catalog ID and opaque configuration revision. No source keys, environment, workspace or files are transferred.

Native seams

  • inspect_local_execution_config: owner/community-scoped inspection of an agent already provisioned on this Desktop. Returns only Rust catalog runtime ID and a destination configuration digest; never a launch payload/key/environment.
  • execute_host_command: verifies an owner-signed NIP-44 command addressed to this host, reads the exact nondeleted registration using the existing owner's selected-community authority, checks freshness, then runs the native transition. No independent host login or owner credentials export is introduced.
  • Start uses the existing launch machinery and destination configuration. Its revision is rechecked against the actual resolved inputs at the spawn boundary. Unknown/missing runtime or authentication, setup mode, provider-backed records, unsupported mesh preflight, prior receipt or live destination peer fail closed. There is no arbitrary received command/env/path or source-host loopback endpoint. This does not yet provision a source-only agent onto a second Desktop.
  • The Start operation's random ID governs the launcher start_nonce, durable receipt run_id, authenticated lifecycle correlation and public ACP run ID. Ordinary local starts get a fresh generation as before. Legacy receipts deserialize with no generation and cannot establish selected-run Stop authority.

Durable semantics

A secret-free journal is keyed by local owner plus canonical agent/community placement. An OS file lock serializes controllers sharing the store. Atomic restricted writes and directory sync persist intent before side effects. The immutable signed command event ID and request are retained with outcomes. Retries reuse the same event/operation, return the recorded observation and never repeat launch. A crash after intent but before a recorded result is unknown, not an invitation to spawn again. A different command reusing an operation ID is rejected. Journal corruption/unreadability and retention saturation fail closed. There is no automatic garbage collection or recovery override.

The placement remains fenced through Stop and unknown outcomes. Ordinary/config- driven starts consult that fence too. A stale Stop checks the tracked generation under the transition locks before writing a new fence, so it neither signals nor takes control of a successor. Other agent/community placements are untouched.

spawned means a child was actually created, not listening, ready, a model turn or conversational presence. The protocol reserves listening/ready observations; the native executor currently records spawn only, not asynchronous receipt updates. Results are host-signed, encrypted to the owner, and correlated to the exact signed command, request, host registration and generation. Expired presence is never an execution result. Authorization is rechecked even on retry. Historical bytes authenticate at their signed timestamp, but the native transition checks actual wall-clock expiry after immutable ledger replay and before creating any new intent or side effect. Future timestamps are rejected. Expiration never creates a replacement operation.

The native outbox uses restricted atomic writes, directory fsync and OS file locks. It retains signed commands and receipt ciphertext through ACK loss/reconnect/restart. Before retrying a durable receipt, the destination checks accepted history. If the result never reached the relay and its envelope is at least 10 minutes old, it re-signs the same encrypted observation bytes and routing tags with a fresh transport timestamp and fsyncs before sending. Original command ID, operation, run, outcome and observation time do not change. Recent retries reuse exact signed bytes; accepted history of any earlier envelope resolves ACK loss without creating another observation. Only a saved, authenticated result may be renewed; this never executes an expired intent or opens a new ledger operation. Current registration and unlocked owner/community authority are still required.

Owner-private host registration/inventory and execution history reads use the same native no-redirect client as Start publication. Their 15-second per-request deadline includes response-body consumption (rate-limit admission wait is separate). Redirects are errors, including 307/308: neither private filter bodies nor authentication are sent to a redirect target. Unrelated query transports retain their existing policy.

Publication errors are recorded per entry, remain visible and retry independently; a revoked old registration does not starve another operation. Corrupt bindings, missing receipts and invalid/cyclic supersession chains fail closed. Transport ACK means only relay acceptance. The app-scoped pump runs independently of the Hosts page, and generation-matched public run presence supplies the actual location.

queue_host_start without new_attempt_after always reuses the saved current intent, even if completed or rejected. A genuinely new user intent supplies that exact previous operation ID. It requires either its signed rejected outcome or a host-signed stopped receipt correlated to an owner-signed Stop of that exact run. Neither root_exited, missing presence nor expiry qualifies. A persisted supersession chain selects the current intent without timestamp tie ambiguity. Destination placement fences still revalidate admission; no Start bypasses a successor/unknown placement fence.

Exact Stop: supported owned-work completion

Native Stop signals only the retained selected-generation ACP owner and allows 90 seconds for nested cleanup. It never signals a reaped/recycled root or a newer run. Deadline escalation is unknown, not successful Stop. The legacy PID-only best-effort path remains separate.

Root/group exit alone remains root_exited, blocking replacement. stopped now additionally requires a verified agent-signed local owned-work proof for the same agent, canonical community and existing launcher start_nonce, plus a successfully reaped root. No binary-name, timing, goodbye/log, presence or exit-code heuristic can produce this proof. The host subsequently issues the existing host-signed encrypted execution Receipt for the immutable Stop command.

Existing Desktop Stop and explicit restart

Existing agent, profile, sidebar and pair controls pass the clicked runtime nonce and community to the same native selected-run execution/proof/ledger authority. They do not look up a replacement generation at click execution time. Missing nonce/community is unsupported; stale selections cannot signal a successor. RootExited and Unknown return an error and keep replacement fenced. A repeated ordinary Stop reuses its original immutable local request; a live successor cannot be reported as stopped by replaying that earlier success.

An explicit ordinary Start after confirmed exact Stop, and the existing pair Restart action, enter the same ledger for a fresh generation. Automatic reconcile remains fenced. Spawn is not Ready: an immediate Stop during ACP pool initialization can return RootExited, even when partial children were drained. It cannot certify replacement; the actual tracer preserves this negative case rather than forcing Stopped. Provider !shutdown behavior is unchanged. Config-edit, delete and application cleanup retain their separate best-effort behavior and are not certified Stop claims. No new run authority or provider control channel is added.

Minimal supported capability chain

  1. ACP installs SIGTERM/SIGINT handlers before spawning. The same sticky watch cancels eager/lazy initialization and respawn/backoff; startup errors explicitly drain partial pools. Stop cancels checked-out channel and heartbeat work before joining it. Bounded abort/escalation never clears incomplete-child evidence.
  2. buzz-agent advertises _meta.buzzOwnedWorkShutdown: 1 at ACP initialize. _buzz/shutdown_v1 closes request admission, cancels sessions and joins all session/new and prompt tasks, including partial initialization. It returns {v:1, ownedWorkStopped:true} only when all lifetime MCP children completed. EOF/broken output still performs cleanup, but cannot issue this acknowledgement.
  3. MCP discovery of _buzz_shutdown_v1 negotiates the supervisor-only capability (underscore tools are hidden from the model). It closes shell admission, cancels and drains shell owners, and returns exactly buzz.owned-work.stopped.v1 only when owned shell roots were reaped and their groups disappeared. Dropped/aborted/unobserved shell work is sticky uncertainty.
  4. The agent retains each actual MCP child through an rmcp Transport adapter: explicit work acknowledgement and successful child reap are both required. rmcp 1.8's default transport masks timeout-kill/nonzero exits, so waiting for its cancel() alone was insufficient. Failed initialization, restart, borrowed clients, noncooperative tools and forced exits cannot certify completion.
  5. ACP requires the negotiated agent acknowledgement and successful agent reap. A process-lifetime incomplete-child count includes failed/aborted respawns. Only zero incomplete children permits writing the final local proof, using the agent key already entrusted to this runtime. No new run ID/key/kill authority or relay kind is introduced. Native stamps BUZZ_STOP_RECEIPT_PATH beside the runtime log (.stop-<start_nonce>.json); ACP strips this variable from agent children. The file is create-new, mode0600, synced, signature-checked and bounded to4096 bytes. Missing, altered or wrong-scope files cannot authorize replacement.

Supported boundary and failure model: this is trusted cooperative buzz-acp → buzz-agent → buzz-dev-mcp → shell process-group execution on Unix, not hostile-code containment. Ordinary shell children/grandchildren in their owned group are included, even if ignoring TERM (the tool owner kills and reaps them). Deliberately detached/daemonized work, external services/jobs created by commands, arbitrary third-party tools, compromised executors/agent keys and OS-level unobservable workloads are outside the supported capability contract. Do not present it as universal descendant or remote-job termination. Unsupported servers do not silently inherit support from their name or exit status; they remain root_exited/unknown. Windows lacks the required observation here and fails closed. Lifetime uncertainty persists even if a later replacement child shuts down cleanly. A native restart without a retained child also stays unknown.

Move's approved meaning remains: confirmed selected-run Stop, then a fresh runtime session for the same agent at the destination, no automatic file/workspace transfer. Unknown/root-only outcomes block replacement; unrelated placements are preserved. Start and Move transport remain default-off.

Repeatable real-process check (fixture relay/provider)

cargo build --locked -p buzz-acp -p buzz-agent -p buzz-dev-mcp
export BUZZ_STOP_CHAIN_BIN_DIR="$PWD/target/debug" # use actual CARGO_TARGET_DIR if set
# Optional: a fresh directory for process-tree snapshots and per-owner logs.
export BUZZ_STOP_CHAIN_ARTIFACTS="$(mktemp -d)"
# Source-only native test; this does not test packaged mesh sidecars.
export TAURI_CONFIG='{"bundle":{"externalBin":[]}}'
cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml \
  selected_generation_process_chain -- --ignored --nocapture

Requires Unix and Node. The test starts real buzz-acp, buzz-agent, and buzz-dev-mcp binaries, uses the production native selected-generation guard and termination seam, and observes real shell/grandchild PIDs in independently owned process groups. A same-identity peer on another fixture relay remains running through selected Stop, stale-generation rejection and retry-after-reap rejection. Every peer process is then stopped by its own owner. Fixture services bind only loopback, accept test auth, and supply a canned OpenAI tool call. The test clears inherited configuration and uses only a public deterministic test key.

This is not a Desktop UI test, signed command receiver/outbox test, real LLM provider run or second physical machine. It verifies the signed supported completion proof and rejects different-community/generation proof reuse. The source-level native journal tests separately pin crash/ACK-loss deduplication and the rule that only confirmed exact Stop permits replacement.

Selected-run Move (preview)

queue_host_move accepts the selected source registration/run, destination registration and its provisioned agent/runtime/revision. The native owner checks both current registrations, authenticated active source and destination presence, existing destination instances and signed compatible provisioning before saving. Presence is only a selection/preflight hint, never a Stop result.

A Move dependency lives in the existing locked owner/community Start outbox. A domain-separated owner signature binds the exact encrypted Stop command and reserved destination Start template (including its predecessor). Only Stop is initially publishable. The app-scoped receiver handles both actions through the same native command/ledger seam, without giving hosts additional authority. Only a verified stopped receipt for that exact command, agent, host, community and run releases the reserved destination Start. Both registrations and destination configuration are checked again; execution rechecks configuration at spawn. The Start TTL begins at release. The release and immutable outbox entry are saved atomically before publication. Crash/reconnect/ACK-loss retries do not mint a run.

Root-only exit, rejection, timeout, missing presence/receipt and Unknown block replacement. The destination is reserved while waiting; other source runs and agent/host/community placements are untouched. A saved Move to another destination cannot silently supersede this intent. Once the source is confirmed stopped, configuration drift may be corrected with an explicit retry using current provisioning. If destination execution rejects Start, the ordinary explicit new_attempt_after recovery creates a new destination attempt; unknown Start still cannot be replaced. There is never an automatic source restart.

Hosts exposes exact active-run selection, disabled destinations with reasons, and a fresh-session/no-files/keys/configuration-transfer disclosure. Persisted progress remains visible after source presence disappears. Spawned is not Ready and a matching new run/host is displayed only when actually observed.

Validation and integration gates

The opt-in debug-only remote-start-tracer feature builds a separate host-start-tracer executable. It uses two local native Wry executors, fresh synthetic keys, independent keyring/HOME/app-data scopes, the actual native queue/pump/transition, and real buzz-acp/buzz-agent binaries. It requires an isolated loopback relay and a newly initialized fixture directory. See source desktop/src-tauri/src/host_start_tracer.rs; never use live credentials or a shared production profile. Fixture provider configuration is explicitly synthetic; a spawned process and public live run label are not an inference-turn proof or two physical machines. On macOS the tracer pins each executor to a native keychain file under its fixture HOME; it does not change the user's default keychain or search list.

The local two-executor tracer was exercised on 2026-08-31: native destination spawn, a verified host-signed spawned receipt, and matching public run/host label were observed. A restarted source reused the same immutable operation. The fixture initialized real buzz-agent ACP sessions but did not request inference. Cleanup reaped only the fixture's tracked root, not a certified Stop.

The 2026-08-31 Move tracer additionally completed confirmed selected-source Stop followed by destination spawn with the same agent and matching fresh run/host. The unrelated source peer survived Move and then completed its own ordinary Stop. An old source token was rejected at the destination. The expanded tracer also exercised ordinary Start after confirmed Stop, waited for that exact new run's signed live presence, then used ordinary Restart and finally its own confirmed Stop. Retrying the earlier successful Stop could not report the successor stopped. Both executors completed with the required result files; fixture cleanup was not used as certification evidence. A debug tracer failure is checked from its result and log as well as exit code (the tracer uses the returning native event loop).

Remaining gates include real-provider and second-physical-host validation, actual Hosts native UI capture, authenticated asynchronous lifecycle receipt updates beyond spawned, and destination provisioning UX. No final mesh-enabled DMG or combined feature certification is implied by this slice.

The same debug tracer has move-init, move-source, and move-destination roles (fresh fixture directory, isolated loopback relay). It provisions synthetic identities on both executors, starts the source plus an unrelated peer, queues Move through native IPC, and records either move-success.json (signed native outcomes + matching new run/host + surviving peer) or move-blocked.json. It never fabricates Stopped. Build all three workload binaries from the certified Stop source before running. Neither fixture cleanup nor a blocked trace is a successful Move certification. Native Hosts UI and real-provider work remain separate evidence requirements.