Skip to content

ground-ruvnet flywheel advisory conflates configured with operational and omits mandatory project anchor #138

Description

@sparkling

Summary

plugin/scripts/ground-ruvnet.sh currently treats any occurrence of RUFLO_HARNESS_LOOP in .claude/settings.json or .claude/settings.local.json as proof that Ruflo's self-learning flywheel is on. It then suppresses the flywheel advisory.

That is not the runtime contract:

  1. Ruflo checks process.env.RUFLO_HARNESS_LOOP in the daemon process.
  2. A Claude settings file does not ensure a daemon launched by Codex, a monitor, launchd, systemd, or another shell inherited that environment.
  3. Since Ruflo #2840 / PR #2848, a foreign/downstream project must also provide a valid, hash-pinned project-local flywheel anchor. Without it, the runtime intentionally fails closed.
  4. The Brain's current "TURN IT ON" guidance still says only to add the environment entry and start the daemon, so it cannot produce a working downstream setup.

The result is a false "enabled" state: the Brain stays silent while Ruflo records either "opt-in required (RUFLO_HARNESS_LOOP=1)" or "project-local flywheel anchor required".

Current source

Current Brain main marks the flywheel on from either the process environment or a substring grep:

# ── Self-learning flywheel (ruflo ≥3.24, ADR-176) — OFFER it, never switch it on for them ────────
# Opt-in is a single env var; `harnessLoopOptedIn()` in @claude-flow/cli reads process.env directly,
# so a project enables it via .claude/settings.json `env`. Unset is a true no-op. We detect BOTH so
# an already-enabled project is never nagged.
FLYWHEEL=off
case "${RUFLO_HARNESS_LOOP:-}" in 1|true|yes|on|TRUE|Yes|On) FLYWHEEL=on ;; esac
if [ "$FLYWHEEL" = "off" ] && grep -qs 'RUFLO_HARNESS_LOOP' .claude/settings.json .claude/settings.local.json 2>/dev/null; then
FLYWHEEL=on
fi

It then instructs users to add {"env":{"RUFLO_HARNESS_LOOP":"1"}} to .claude/settings.json and run ruflo daemon start:

if [ "$RUFLO_STATE" = "yes" ] && [ "$FLYWHEEL" = "off" ] && claim_flywheel_day; then
cat <<'EOF'
[RuvNet Brain — the self-learning flywheel is available here and switched OFF]
This project runs Ruflo, and ruflo ≥3.24 ships a self-optimizing flywheel that is OFF by default. Offer it plainly and warmly — then drop it. NEVER enable it without an explicit yes. The hook enforces the delivery cadence; do not repeat this advisory yourself.
Explain it in the user's words, not the plumbing's:
- WHAT IT IS: ruflo can improve one of its own operating policies over time — specifically how it finds the right stored memory for a query (the BM25/cosine blend, diversity, etc.). Each candidate is scored against a frozen, human-labelled relevance set; a change is promoted ONLY if it measurably beats the current champion on held-out data. Every promotion leaves a signed receipt you can replay yourself, and a drift canary auto-rolls-back on regression. Think "git for decision-policies": each generation has a parent, a diff, evidence, a signature, and an immutable root you can walk back to.
- WHY IT'S DIFFERENT: most "self-improving agent" claims are unfalsifiable. This one only turns the wheel when an improvement is independently proven, and it hands you the receipt.
- WHAT IT COSTS: $0. No LLM calls and no network on the decision path.
- HONEST CAVEAT (say this, don't hide it): it stays idle until the project has accumulated at least 12 stored neural patterns to harvest a corpus from. Before that it reports "store too small" and does nothing. On a brand-new project that means it earns its keep later, not today.
- TURN IT ON: add {"env":{"RUFLO_HARNESS_LOOP":"1"}} to .claude/settings.json, then `ruflo daemon start`
(the GLOBAL binary — never `npx ruflo@latest`, which runs its own private copy and hides drift).
- TURN IT OFF: remove that env var (and `RUFLO_DAEMON_AUTOSTART=0` stops the daemon auto-starting).
Offer like this: "Ruflo can quietly tune how it recalls memory — testing changes against a frozen benchmark and only keeping what provably wins, with a receipt you can replay. It's free, it's off by default, and it does nothing until this project has enough history. Want me to turn it on?" If they decline, respect it and never raise it again unless they ask.

Ruflo's current implementation instead reads only the daemon process environment:

https://github.com/ruvnet/ruflo/blob/f35c545fbe927aeb4ab8433bab8d827f69436572/v3/%40claude-flow/cli/src/services/harness-worker.ts#L40-L54

Downstream repositories fail closed without a project-local anchor:

https://github.com/ruvnet/ruflo/blob/f35c545fbe927aeb4ab8433bab8d827f69436572/v3/%40claude-flow/cli/src/services/harness-project-anchor.ts#L139-L187

That behavior is intentional and was shipped by:

The "at least 12 patterns" advisory is also only the first data gate. The generation path separately requires at least 20 harvested held-out tasks:

https://github.com/ruvnet/ruflo/blob/f35c545fbe927aeb4ab8433bab8d827f69436572/v3/%40claude-flow/cli/src/services/harness-flywheel-generations.ts#L247-L252

Live reproduction

Validated on two hosts and current published generations.

macOS

  • RuvNet Brain 4.0.35
  • Ruflo 3.34.0
  • 15 scanned Ruflo project roots contained .claude/settings.json with RUFLO_HARNESS_LOOP=1
  • Current project-attached daemons had RUFLO_HARNESS_LOOP unset
  • .claude-flow/metrics/harness-loop.json reported "opt-in required (RUFLO_HARNESS_LOOP=1)"
  • No project had a flywheel anchor or lineage

Linux

  • RuvNet Brain 4.0.36
  • Ruflo 3.36.0
  • 12 scanned Ruflo project roots contained the same opt-in setting
  • One project daemon had the variable unset and reported "opt-in required"
  • A second daemon inherited RUFLO_HARNESS_LOOP=1, reached the next gate, and reported:
    "project-local flywheel anchor required; create .claude/eval/flywheel-anchor.manifest.json or pass anchorPath + anchorHash"
  • No scanned project had a flywheel anchor or successful lineage

Brain 4.0.36 contains the same ground-ruvnet.sh detection and instructions shown above.

Minimal reproduction

  1. In a non-Ruflo repository, install Ruflo and RuvNet Brain.
  2. Add {"env":{"RUFLO_HARNESS_LOOP":"1"}} to .claude/settings.json.
  3. Start or auto-start the project daemon from a Codex session, monitor, service manager, or shell that did not inherit the Claude Code setting.
  4. Run the Brain hook. It sees the substring and classifies the flywheel as on.
  5. Wait for the harness worker or inspect .claude-flow/metrics/harness-loop.json: Ruflo reports "opt-in required".
  6. Restart the daemon with the environment exported. The next tick fails because the mandatory project anchor is absent.

An even smaller detector test is {"env":{"RUFLO_HARNESS_LOOP":"0"}}: the current grep still marks it on.

Expected behavior

The Brain should distinguish:

  • off - no explicit consent
  • requested/configured - a host settings file asks for it, but runtime readiness is unverified
  • blocked: daemon environment - project daemon did not inherit the opt-in
  • blocked: anchor - downstream anchor/manifest is absent or invalid
  • waiting for data - runtime is configured but pattern/held-out gates are not met
  • active - a project-attached daemon has the opt-in, the anchor validates, and a recent worker receipt proves the path reached its data/evaluation stage

A settings-file substring alone must never be labelled "on" or suppress a material misconfiguration warning.

Suggested implementation

1. Stop treating grep as operational proof

Parse the JSON and inspect the exact .env.RUFLO_HARNESS_LOOP value. Even a truthy value should mean only configured/requested, not active.

Do not interpret .claude/settings.json as a host-neutral daemon configuration layer: it belongs to Claude Code and is not automatically applied by Codex or external daemon launchers.

2. Consume a machine-readable Ruflo readiness contract

The clean boundary would be a non-mutating Ruflo command such as:

ruflo metaharness flywheel doctor --project-root . --format json

with a stable result containing:

  • bundled/version
  • explicit consent state
  • attached project daemon and whether it inherited the opt-in
  • anchor required/valid/reason
  • pattern and harvested held-out counts
  • eligibility
  • last tick state/reason

If Ruflo does not yet expose this contract, the Brain should report "configured, runtime unverified" rather than claiming on. A companion Ruflo issue can add the doctor surface; the Brain should consume it instead of duplicating Ruflo's anchor/hash and daemon semantics.

3. Correct the enablement guidance

For downstream projects, guidance must cover:

  1. Explicit user consent.
  2. A curated project-local labelled anchor plus the hash-pinned manifest required by ADR-331/#2840.
  3. A launch path that actually supplies RUFLO_HARNESS_LOOP=1 to the project-attached daemon on both Claude Code and Codex.
  4. Restart/reconciliation of an already-running daemon that lacks the environment.
  5. Verification through the machine-readable readiness/last-tick result.
  6. The distinction between the initial 12-pattern gate and the later held-out significance gate.

Do not auto-create a synthetic anchor: the human-labelled benchmark is the trusted objective and requires project-owner judgement.

4. Preserve consent and cadence

Keep the once-per-day advisory behavior from #53 and never enable the flywheel automatically. A project that explicitly requested the feature but is blocked should receive a separate bounded misconfiguration advisory rather than being silently treated as active.

Acceptance tests

Add a table-driven test matrix for ground-ruvnet.sh covering:

  • setting absent
  • exact truthy setting
  • "0", "false", false, null, and unrelated string occurrence
  • malformed settings JSON
  • setting truthy but no daemon
  • attached daemon without inherited opt-in
  • inherited opt-in but missing anchor
  • invalid/hash-drifted/escaping anchor
  • valid anchor but insufficient patterns
  • sufficient patterns but insufficient held-out tasks
  • recent successful/eligible tick
  • Claude Code and Codex launch contexts
  • macOS and Linux

No blocked state may be reported as on, and no fix may weaken Ruflo's anchor containment/hash checks or explicit-consent boundary.

Non-goals

  • Do not remove or bypass the project-local anchor requirement.
  • Do not auto-enable the flywheel.
  • Do not fabricate a human-labelled anchor.
  • Do not add an alternate updater, cache, runtime shim, or network/provider dependency.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions