Skip to content

Program engine M1.1b: run program steps on a supervised Bun host - #6265

Draft
schickling-assistant wants to merge 10 commits into
schickling/2026-11-10-program-engine-corefrom
schickling/2026-11-10-program-engine-host
Draft

schickling-assistant wants to merge 10 commits into
schickling/2026-11-10-program-engine-corefrom
schickling/2026-11-10-program-engine-host

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #6264 (base schickling/2026-11-10-program-engine-core), which depends on #6260.

Problem

Declared program steps need an engine: a supervised host that runs program bodies, journals each inner step durably, resumes after crashes, stops promptly when the step ends, and cannot be wedged by one program's busy loop or a dead host.

Goal

M1.1b of docs/st3/program-engine.md: ProgramEngine + program_host.mjs per host protocol v1.

Decisions

  • One Bun host per member; one Worker per running attempt; the main thread owns the pipe, effect process groups and liveness. Context method for linked runs is child(…) (field run is the run subject), per the contract fix.
  • Module from the store blob by hash into a verified, content-addressed cache file (a damaged copy is rewritten from the blob); the host re-verifies before import. A missing blob fails the step (R06).
  • Results fenced by program:STEP:ATTEMPT:KEY:result; $result journaled on done before the step completes; replay = current attempt's completed/failed entries, paged by 500.
  • Eager cancellation: ProgramEngine::release_left(run) runs at the top of every evaluate_one_mission_run (before the inactive-run return) and cancels attempts whose step ended, failed, timed out, retried, was cancelled with its run, or left the generation. The journal-write check stays as backstop.
  • DQ2: the host records each group (pid, boot id, leader start time) and kills it when its leader exits, on cancel, worker termination and stdin close; before any host starts the engine kills recorded groups whose leader still has the recorded start time (or is gone while the group lives) and drops earlier-boot records without a kill.
  • DQ8: host pings workers (deadline default 30 s): an unresponsive worker is terminated with its groups, the host sends unresponsive, the step fails naming the deadline. The engine pings the host every 5 s and replaces one silent for 30 s; programs resume.
  • Visibility API (agreed with that slice): wake(step) callback, Dispatched::HostDown(reason), host_fault(step, attempt).
  • Runtime: FRACTAL_PROGRAM_RUNTIME, else bun on PATH (documented). Bun added to the dev shell and the Rust check environment so the engine tests run, never skip.
  • Doc advisories fixed: §8 lists worker liveness as a program/host source; terminating a worker kills that attempt's groups.

Verification

At head 3ba3725 (merge of #6264 @9aa1096557) on the Linux build host, with the real Bun host (bun 1.3.13 from the dev shell):
CARGO_TARGET_DIR=<slice target> nix develop -c nice -n 19 cargo test -p fractal-reconcile --lib -- reconcile::program reconcile::tests::program_engine: 9 passed, 0 failed (1.62 s):

  • unit: recorded_groups_are_killed_only_when_their_leader_is_verified, messages_for_another_attempt_or_host_change_nothing, a_revision_runs_the_stored_blob_it_pins, a_key_has_one_result_per_attempt, replay_returns_this_attempts_results_in_order, replay_pages_through_long_journals_in_order
  • integration with a real Bun host and a hand-written bundle: a_program_resumes_from_its_journal_after_its_host_is_killed, a_step_that_leaves_its_attempt_stops_its_program_and_effects_at_once, an_unresponsive_worker_fails_only_its_own_step

Hosted CI at the exact head is the proof (pushed with ST_EVIDENCE_LOCAL=off). The #6264 tests listed in that PR passed at its head 9aa1096. python3 scripts/check-sqlite-large-values passed at this head (4285 sites).

Review findings answered

Complexity

~730 lines engine + host, ~400 lines tests.

Concerns

  • macOS start-time/boot-id paths (ps -o lstart=, sysctl kern.boottime) are untested here (Linux build host only).
  • Relies on Bun's Worker.terminate() interrupting a busy loop (covered by the unresponsive-worker test).

Friction & bottlenecks

Build host load ~60–95 on 32 cores, ~6 GiB RAM free; cold builds serialized across slices.

Follow-ups

M1.2 children and asks; M1.3 faults/visibility; M1.4 authority (journal actor, run credential).

References

docs/st3/program-engine.md §1, §2, §5, §6, DQ2, DQ8; spike PR #6254.

ProgramEngine dispatches each program attempt to one Bun host per member
(program_host.mjs, protocol v1): a worker per attempt, effects in recorded
process groups (boot id + leader start time, verified before the next host
starts), worker and host liveness pings, modules read from the store blob
into a verified cache file, one fenced result per key and attempt, and the
program's $result journaled on done. Every run evaluation releases program
attempts whose step left them, so cancellation, timeouts and retries stop
the program and its effects at once. Bun joins the dev shell and the Rust
check environment.
@schickling-assistant

Copy link
Copy Markdown
Contributor Author

Build-host verification at efe0519 (nix develop -c nice -n 19 cargo test -p fractal-reconcile --lib -- reconcile::program reconcile::tests::program_engine, Bun 1.3.13 from the dev shell): 9 passed, 0 failed (6 engine unit tests incl. DQ2 record verification; 3 real-Bun-host integration tests: resume after host SIGKILL, eager cancel on run cancel and step failure, unresponsive worker fails only its step). Hosted CI at the exact head is the proof.

…ing/2026-11-10-program-engine-host

# Conflicts:
#	crates/fractal-reconcile/src/reconcile.rs
A late message for an earlier attempt or a replaced host no longer
removes the current attempt; ack, nack and cancel go only to the host
generation that sent the entry while its attempt still runs; refused
completed/failed entries (reserved key, missing key or status) are
nacked. DQ2 reaping treats a leader as gone only on ESRCH, keeps records
whose live leader's start time cannot be read, verifies half-renamed
.tmp records, and uses absolute ps/sysctl paths on macOS.
…gine-core' into schickling/2026-11-10-program-engine-host
…gine-core' into schickling/2026-11-10-program-engine-host
…m:compoundingtech/fractal into schickling/2026-11-10-program-engine-host
@schickling-assistant

Copy link
Copy Markdown
Contributor Author

Review record for head 3ba372596521e3bb20acd8143c8b25d3db51385d (stacked on #6264 @9aa1096557).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants