feat(recipes): ruflo swarm → acceptance gate via a host-side supervisor - #101
Conversation
The original 5.4 sketched ruflo.orchestrate() returning .visible/.holdout/ .merged_diff/.agent_trails/.converged/.rounds/.criteria_met. No such API exists: ruflo is a Node CLI with no Python package and no result object with those fields. The section also assumed a swarm-terminal hook to register the gate on; the plugin HookEvent enum carries no swarm-level terminal event (swarm:consensus-reached appears only in ruflo's docs). Rewritten as a dated correction against the verified surfaces: the blocking hive-mind spawn --claude invocation, the .swarm/ JSON files, the memory export's sparc-phases criteria vocabulary, and the supervisor-owned interrupt flag. Research dossier: review/recipes/2026-07-25-ruflo-api-research.md.
A host-side supervisor around ruflo's blocking swarm CLI, replacing the swarm's self-report with a real gate. ruflo hive-mind spawn --claude spawns the Claude Code CLI as the swarm's body and maps exit 0 to success; the sparc-gates memory namespace holds the swarm's own per-phase pass rows and truthScore. The supervisor reads only host-side surfaces, runs the withheld holdout split plus the anticheat trajectory sweep over the agent trails, projects through to_terminal_state and records via loop.emit. Zero ruflo changes, zero loop/ kernel changes. Three traps pinned by tests: ruflo exits 0 on Ctrl-C, so AbortedByHuman comes from the supervisor's own signal flag and never from the exit code; ruflo verify is install-integrity, not a run verdict, so it is never wired as the gate; the sparc-gates self-verdict stays green under sabotage while the gate refuses. Plus the false-completion invariant and a FailedSpecGap demo for a declared criterion no check covers. Deterministic and credential-free: the example replays the committed examples/ruflo-gate/fixture/ recording by default (--live opts into the real invocation), stated plainly in the README. Only the swarm is recorded - the gate, projection, emit, doctor and metrics path all execute for real. The opt-in live schema-drift alarm is skipped unless LOOP_RUFLO_LIVE=1. Verified against ruflo 3.32.9 (2026-07-25).
Anchored directly after action-dogfood so a sibling recipe can land after recipe-temporal without a conflict. No npm, no Node, no credentials: the job installs only pyyaml/pytest/jsonschema and runs the fixture-replay recipe suite, matching the other recipe jobs' cost profile.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93061accca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| obs = observe(ws) | ||
|
|
||
| gate = holdout_gate.decide(visible_checks(ws), holdout_checks(ws)) | ||
| ac = anticheat_scan.scan(diff_text="", trajectory=agent_trails(obs)) |
There was a problem hiding this comment.
When a live swarm edits a gate or CI file, injects skipped tests, or adds tautological assertions, this call always supplies an empty diff, disabling every diff-based critical/high signature in anticheat_scan.scan; the trajectory scan only detects answer-path reads and cannot compensate. Such a tampered run can therefore still be projected as Succeeded, so capture and scan the host-side changes produced by the swarm.
Useful? React with 👍 / 👎.
| claim = json.loads(report_path.read_text(encoding="utf-8")) if report_path.is_file() else {} | ||
| log_lines = [line for line in log_path.read_text(encoding="utf-8").splitlines() | ||
| if line.strip()] if log_path.is_file() else [] | ||
| dropped = claim.get("dropped_rows") |
There was a problem hiding this comment.
Verify behavior instead of trusting generated summaries
On a live run that writes the expected constants into dedupe-report.json and produces 16 suitably formatted log lines, every holdout check passes even if src/import_contacts.py is missing or completely broken, because the checks only compare two swarm-owned output summaries and never execute the work product against independent input. This recreates the self-reporting false completion that the supervisor is meant to prevent; the holdout should exercise the implementation or independently derive the expected output.
Useful? React with 👍 / 👎.
|
|
||
| def certify(workspace: Path, run: SwarmRun) -> dict: | ||
| ws = Path(workspace) | ||
| obs = observe(ws) |
There was a problem hiding this comment.
Project early live interruptions before reading artifacts
When the operator interrupts a live run before ruflo creates .swarm/state.json or the memory export—or when the export command fails—this unconditional observation raises FileNotFoundError before to_terminal_state sees run.human_abort. The real Ctrl-C path therefore exits with a traceback and no AbortedByHuman contract, despite the replay-only simulation passing; handle incomplete observations and project the recorded interrupt or external failure first.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a runnable ruflo integration recipe that gates a swarm via a host-side supervisor (fixture-replay by default) so Loop Engineer’s acceptance gate, projection, emit, doctor, and metrics execute for real without requiring Node/credentials in CI.
Changes:
- Introduce
examples/ruflo-gate/supervisor + committed.swarm/fixture replay and documentation. - Add deterministic end-to-end pytest coverage for the recipe, including false-completion, interrupt, and spec-gap traps.
- Update ST3 integration adapter spec and add a dedicated CI job for the recipe test.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/test_ruflo_recipe.py |
Deterministic pytest suite that exercises fixture replay and validates doctor/metrics outcomes. |
examples/ruflo-gate/swarm_example.py |
Host-side supervisor that replays or runs a ruflo swarm and projects through to_terminal_state + loop.emit. |
examples/ruflo-gate/README.md |
Example-level documentation explaining why the supervisor seam exists and how to run demos. |
examples/ruflo-gate/fixture/src/import_contacts.py |
Vendored “work product” file to make touched-path trails resolve during replay. |
examples/ruflo-gate/fixture/dedupe.log |
Fixture evidence used by holdout checks (dropped-row log). |
examples/ruflo-gate/fixture/dedupe-report.json |
Fixture report used by visible/holdout checks and sabotage scenarios. |
examples/ruflo-gate/fixture/.swarm/tasks/task-001-spec.json |
Fixture .swarm/ task record for replay. |
examples/ruflo-gate/fixture/.swarm/tasks/task-002-implement.json |
Fixture .swarm/ task record for replay. |
examples/ruflo-gate/fixture/.swarm/tasks/task-003-logging.json |
Fixture .swarm/ task record for replay. |
examples/ruflo-gate/fixture/.swarm/tasks/task-004-review.json |
Fixture .swarm/ task record for replay. |
examples/ruflo-gate/fixture/.swarm/state.json |
Fixture .swarm/ state used to determine “settled” status. |
examples/ruflo-gate/fixture/.swarm/memory-export.json |
Fixture memory export providing acceptance criteria vocabulary + self-report artifacts. |
examples/ruflo-gate/fixture/.swarm/coordination/consensus-2026-07-25T09-31-52Z.json |
Fixture coordination trail for anti-cheat trajectory input. |
examples/ruflo-gate/fixture/.swarm/agents/agent-coder-02.json |
Fixture agent trail for replay. |
examples/ruflo-gate/fixture/.swarm/agents/agent-coder-03.json |
Fixture agent trail for replay. |
examples/ruflo-gate/fixture/.swarm/agents/agent-queen-01.json |
Fixture agent trail for replay. |
examples/ruflo-gate/fixture/.swarm/agents/agent-reviewer-04.json |
Fixture agent trail for replay. |
examples/ruflo-gate/fixture/.hive-mind/sessions/hive-mind-prompt-swarm-2026-07-25.txt |
Fixture prompt transcript to support “swarm asked vs swarm proven” narrative. |
examples/ruflo-gate/fixture/.claude-flow/metrics/v3-progress.json |
Fixture progress snapshot (non-proof) to mirror real layout. |
docs/superpowers/specs/2026-06-30-st3-integration-adapters.md |
Corrects the prior fictional ruflo API + documents the verified supervisor seam. |
docs/integrations/ruflo.md |
New integration recipe doc describing surfaces, traps, and terminal mapping. |
.github/workflows/ci.yml |
Adds a dedicated CI job to run the ruflo recipe tests via fixture replay. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - uses: actions/checkout@v7 | ||
| - uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: "3.12" |
| proc = subprocess.run(LIVE_COMMAND, cwd=workspace, text=True) | ||
| subprocess.run( | ||
| ["npx", f"ruflo@{RUFLO_VERSION}", "memory", "export", "-o", ".swarm/memory-export.json"], | ||
| cwd=workspace, text=True, check=False, | ||
| ) | ||
| return SwarmRun(returncode=proc.returncode, mode="live", human_abort=_interrupted) |
Closes #38.
ruflo (
ruvnet/claude-flowrenamed; pinned 3.32.9) integrated per the shipped recipe pattern with ZERO engine and ZERO kernel changes — via the seam that actually exists: a host-side supervisor around the blockinghive-mind spawn --claudeCLI (examples/ruflo-gate/swarm_example.py), because the issue's stated hook seam does not (see the re-scope comment on #38). The swarm'ssuccess == exit 0is SELF-REPORT the acceptance gate replaces: criteria come from the memory-exportsparc-phasesacceptanceCriteria, the swarm's ownsparc-gatesverdicts are read but never trusted, and the gate/projection/emit/doctor/metrics path executes for real against a committed fixture replay of a recorded.swarm/tree (scripts/test_ruflo_recipe.py, deterministic, credential-free). Traps encoded from the verified dossier: Ctrl-C exits 0, so AbortedByHuman comes only from the supervisor's own SIGINT handler;ruflo verify --jsonis install-integrity, not a run verdict. Live mode is documented but deliberately not CI-run (needs Node + claude + credentials); the newrecipe-rufloCI job runs the fixture path.Also lands the dossier-mandated prerequisite: the ST3 spec §5.4 ruflo snippet was fiction (
ruflo.orchestrate()/.merged_diff/.convergedexist nowhere) — rewritten against the real surface as a dated correction.Rebased onto post-#94/#99 main conflict-free; recipe tests 7 passed / 1 skipped post-rebase.