feat(reasonix): run a supervised serve worker with steerable threads

Draft
SH
6d ago
+464-0

Stacks on #2909. Adds a durable Reasonix worker per host, so threads stay warm and can be opened and steered without restarting the engine.

What

  • home-manager/services/reasonix — launchd (RunAtLoad + KeepAlive) and systemd (Restart=always) units running start.sh, which binds an ephemeral loopback port, publishes port/pid/token under ~/.local/state/reasonix, and reuses the generated token across restarts. Gated to galactica for the proof of concept.
  • fish rxthreads — status/list/open/new/send/steer/tail client. Reads the published port and token, addresses threads by sessionPath, steers via the turn-scoped enqueue_steer queue op (which auto-discovers the live turnId from /runtime-states).
  • fish rxgoal — arms a goal and kicks the first round. Arming alone does not schedule a round: kickGoalDriver is reachable from EditGoalDurable and TurnDone, but not from SetGoalDurable (what POST /goal uses), so an idle armed goal stays at roundsStarted 0. The function submits one bootstrap turn whose TurnDone starts the chain. Also --status and --clear.
  • Specs: spec/reasonix_server_spec.sh (13), spec/reasonix_goal_spec.sh (7), plus coverage entries.

Verification

  • make nix-build — built; generated reasonix-server.plist verified (ProgramArguments, PATH, RunAtLoad, KeepAlive)
  • shellspec spec/reasonix_server_spec.sh — 13 examples, 0 failures
  • shellspec spec/reasonix_goal_spec.sh — 7 examples, 0 failures
  • shellspec spec/coverage_spec.sh — 129 examples, 0 failures
  • shellspec spec/reasonix_config_spec.sh spec/reasonix_server_spec.sh spec/reasonix_goal_spec.sh spec/coverage_spec.sh — 169 examples, 0 failures

Proven live on galactica via launchctl bootstrap: worker state = running, last exit code = (never exited); three concurrent threads resident; a real turn completed (turnStatus: completed, persistenceStatus: ready); mid-turn steer accepted (outcome: applied) and its text reached the model's transcript.

Known limitations

  • send posts to /inbox/items, which has no sessionPath field — it always queues to the foreground thread. steer addresses correctly. Fix is to route send through /inbox/queue.
  • Autonomous goal rounds were not observed firing over HTTP: goal pursuit calls update_goal, which blocks on the ask permission gate unattended, and goalRoundEligibility requires the exclusive v3 binding. Both are upstream, not dotfiles.
  • make nix-switch is required to make the worker permanent across reboots; the POC ran from a Nix-store plist via launchctl bootstrap.

Pre-existing

spec/reasonix_config_spec.sh has 4 failures on the base branch (tomlq availability in the hydrate tests). Verified present with these changes stashed.


Summary by cubic

Adds a durable Reasonix serve worker per host so threads stay resident and can be opened and steered without restarting the engine. Also fixes open and send so a message to a named thread lands in that thread instead of the foreground one, and ships the thread client as a fish function.

  • Adds launchd (RunAtLoad + KeepAlive) and systemd (Restart=always) units running start.sh, which binds an ephemeral loopback port and publishes port/pid/token under ~/.local/state/reasonix, reusing a generated token across restarts.
  • Adds fish rxthreads for status/list/current/open/new/send/steer/tail, reading the published port and token and addressing threads by sessionPath; new now reads back the foreground session id instead of the empty 204 body.
  • Fixes open and send request contracts: open now sends sessionId to /resume, and send selects the target thread before enqueuing because /inbox/items has no sessionPath field.
  • Adds fish rxgoal to arm a goal and submit a bootstrap turn, because HTTP arming alone does not schedule the first round; also supports --status and --clear.
  • Adds specs for the server, the goal function, and coverage entries.

Known limitations

  • send still routes through /inbox/items; routing through /inbox/queue is the intended fix.
  • Autonomous goal rounds were not observed firing over HTTP: goal pursuit blocks on the ask permission gate, and goalRoundEligibility requires the exclusive v3 binding. Both are upstream, not dotfiles.