Swarm runner: standalone run_swarm() lacks stack env scaffolding; YIELDOMEGA_SWARM_REFERRALS=0 verification deferred

Summary

Verification of the local bot swarm path with referrals disabled (YIELDOMEGA_SWARM_REFERRALS=0) was not completed in-session and is tracked here as deferred work.

When attempting to invoke swarm_runner.run_swarm() directly (outside scripts/start-local-anvil-stack.sh), contributors hit swarm runner configuration failures attributable to missing environment scaffolding that the stack wrapper normally establishes implicitly. Until that scaffolding is repeatable for standalone runs and/or surfaced on the canonical wrapper path, the “no swarm referrals” behavior remains undertested in practice.


Background

Intended behavior (YIELDOMEGA_SWARM_REFERRALS)

The TimeCurve bot swarm optionally bootstraps a shared referral code for deterministic local QA aligned with referrer flows and indexer ingestion (GitLab #94).

Implementation:

  • swarm_referrals_enabled() in bots/timecurve/src/timecurve_bot/referral_bootstrap.py returns false when YIELDOMEGA_SWARM_REFERRALS is set to 0 (otherwise defaults to enabled).
  • When enabled, swarm_runner.py funds the registrar HD index (27), runs ensure_swarm_referral_registered(...), and sets YIELDOMEGA_REFERRAL_CODE in worker subprocess env so buys use buy(..., codeHash, ...).
  • When disabled (YIELDOMEGA_SWARM_REFERRALS=0), the swarm should skip registrar funding for referrals, skip registration bootstrap, and omit YIELDOMEGA_REFERRAL_CODE from workers so buys exercise the non-referral path.

This is documented at a high level in bots/timecurve/README.md and cross-linked from docs/testing/invariants-and-business-logic.md.

Canonical wrapper path

When START_BOT_SWARM=1, start-local-anvil-stack.sh (tail of script):

  1. Runs scripts/sync-bot-env-from-frontend.sh, which materializes bots/timecurve/.env.local from frontend/.env.local (RPC, chain id, TimeCurve / treasury / NFT addresses, acceptedAsset via cast, etc.).
  2. Selects the venv Python when present, runs dependency preflight.
  3. From repo root (cd "${ROOT}"), exports YIELDOMEGA_ALLOW_ANVIL_FUNDING=1, sets PYTHONPATH, and executes:
    from timecurve_bot.swarm_runner import run_swarm; run_swarm()

run_swarm() calls load_config(env_file=None, send=True, allow_anvil_funding=False), which loads .env and, when the process cwd is the repo root, bots/timecurve/.env.local (bots/timecurve/src/timecurve_bot/config.pyload_dotenv_files).

Thus the wrapper does two critical things beside setting ALLOW_ANVIL_FUNDING:

  1. Ensures bot-facing contract/RPC variables exist by syncing frontend → bot .env.local.
  2. Runs with cwd at repo root so relative dotenv discovery matches repo conventions.

Problem statement (what went wrong)

A contributor attempted to manually reproduce swarm behavior under YIELDOMEGA_SWARM_REFERRALS=0 by calling run_swarm() outside start-local-anvil-stack.sh.

That standalone invocation did not inherit the same effective environment as the wrapper:

  • sync-bot-env-from-frontend.sh may not have been run (no fresh bots/timecurve/.env.local), producing load_config / ValueError failures such as unset YIELDOMEGA_RPC_URL / RPC_URL or unset YIELDOMEGA_TIMECURVE_ADDRESS.
  • YIELDOMEGA_ALLOW_ANVIL_FUNDING=1 may be unset, causing SystemExit(2) with the swarm’s documented guardrail.
  • Working directory may not be the repository root; load_dotenv resolution for bots/timecurve/.env.local is relative to cwd, not the package layout.
  • Even with a correct shell, forgetting PYTHONPATH to bots/timecurve/src breaks imports when not using python -m from an installed/env layout.

Net effect: the failure mode observed was a runner configuration / env orchestration gap, not necessarily a defect in swarm_referrals_enabled() logic itself. Because of that gap, YIELDOMEGA_SWARM_REFERRALS=0 was not empirically exercised during the intended session (DEFERRED).


Impact

Area Impact
QA confidence “No swarm referrals” path is weaker than “default referrals on” until someone runs swarm with =0 end-to-end.
Operator friction Only the documented wrapper composes prerequisites; ad-hoc Python entrypoints surprise contributors.
Regression risk Future changes to swarm_runner, referral_bootstrap, or worker buy wiring could regress the opt-out path without CI catching it.

Deliver one or more of the following (in order of leverage vs. scope):

A. Wrapper-visible opt-out (minimal, explicit)

Extend START_BOT_SWARM=1 UX on start-local-anvil-stack.sh to forward (or document and default-copy) YIELDOMEGA_SWARM_REFERRALS.

  • Example: If an operator exports YIELDOMEGA_SWARM_REFERRALS=0 before starting the stack, the same subprocess that calls run_swarm() inherits it—no Python code change strictly required, but today the script neither documents nor echoes this knob in the “Bot swarm” banner.
  • Enhancement: Print a one-line summary when starting the swarm: e.g. YIELDOMEGA_SWARM_REFERRALS=0 (referral bootstrap disabled) vs default.

This matches the contributor note: “follow up by setting the env on the wrapper script itself in a separate boot”—interpreted as making the wrapper path the place where this flag is first-class for local runs.

B. Document the standalone contract (docs / README)

Add a short “Run swarm without start-local-anvil-stack.sh checklist to bots/timecurve/README.md (and optionally docs/testing/e2e-anvil.md):

  1. Deploy / have frontend/.env.local (or equivalent addresses).
  2. bash scripts/sync-bot-env-from-frontend.sh
  3. From repo root: export YIELDOMEGA_ALLOW_ANVIL_FUNDING=1
  4. Optional: export YIELDOMEGA_SWARM_REFERRALS=0
  5. PYTHONPATH=bots/timecurve/src python3 -c "from timecurve_bot.swarm_runner import run_swarm; run_swarm()"
    or timecurve-bot --allow-anvil-funding swarm with the same env.

C. Friendlier failure messages (optional code)

In run_swarm(), when load_config fails on missing RPC/TimeCurve, catch ValueError once and append a hint pointing to sync-bot-env-from-frontend.sh and repo-root cwd (keep messages short; avoid noisy stack traces for expected misconfig).

D. Automated smoke (stretch)

A small scripted or pytest smoke that mocks/stubs RPC is likely overkill; prefer a documented one-liner plus optional CI job only if we can spin Anvil cheaply. Not required for closing this issue if manual acceptance below is satisfied.


Acceptance criteria

  1. Documented path
    There is repo-checked documentation (README and/or testing doc) that lists all prerequisites to run run_swarm() without start-local-anvil-stack.sh, including sync-bot-env-from-frontend.sh, YIELDOMEGA_ALLOW_ANVIL_FUNDING=1, repo-root cwd, and YIELDOMEGA_SWARM_REFERRALS=0 when testing the opt-out.

  2. Wrapper discoverability
    When START_BOT_SWARM=1, the stack script mentions YIELDOMEGA_SWARM_REFERRALS in user-facing output (help text, comment block, or banner) so operators know they can set 0 without reading Python.

  3. Verified behavior (manual)
    On a fresh local Anvil stack (or equivalent):

    • With YIELDOMEGA_SWARM_REFERRALS=0, swarm starts successfully via the documented path (wrapper or standalone checklist).
    • Logs / behavior show no successful registerCode bootstrap for the swarm shared code (or N/A when registry zero), and worker env does not set YIELDOMEGA_REFERRAL_CODE (or workers buy without referral code—however the bot surfaces that).
    • With default (referrals on), prior behavior remains unchanged (regression check).
  4. Cross-links
    Update docs/testing/invariants-and-business-logic.md Local stack bot swarm row or referrals subsection to reference this issue for the YIELDOMEGA_SWARM_REFERRALS=0 verification note (one sentence).



Labels / area

area:bots · type:tech-debt or type:documentation · priority::low-to-medium (suggest; adjust per project convention)