Skip to content

v0.24.2 — money bots: US-only, vetted-only board, and a README that stays true - #111

Merged
rayketcham merged 7 commits into
mainfrom
fix/bot-generation-quality
Aug 15, 2026
Merged

v0.24.2 — money bots: US-only, vetted-only board, and a README that stays true#111
rayketcham merged 7 commits into
mainfrom
fix/bot-generation-quality

Conversation

@rayketcham

Copy link
Copy Markdown
Collaborator

Seven commits refining the v0.24 money board from live operation. Every one of them came from watching it actually run.

The board means something now

  • Only vetted strategies appear. Flagged ones are counted and listed compactly with the reason they failed — visible and auditable, not competing for attention.
  • Rejections teach the generator. The last six objections ride into the next generation as "already rejected on this board, and why". The panel kept killing the same two errors until they started carrying forward.
  • Compact cards. They were dumping ~1.5KB of spec each; now they carry what you scan by (venue, edge, capital band, one-line mechanism) with the full spec behind one toggle.

US-only

The operator is US-based and the board was proposing venues they cannot trade on — five of eight stored strategies named the offshore Polymarket CLOB or Hyperliquid pairs, three of them marked "vetted", which is the board asserting the opposite of the truth.

Every venue now carries an eligibility status. The gate refuses restricted venues outright and fails closed: an unrecognised venue is refused pending verification, and a strategy naming both an eligible and a restricted venue resolves to restricted, because a two-legged strategy is only as tradeable as its worst leg. reverdict_us_eligibility() re-judged what was already stored.

Generation actually produces vetted strategies now

  • Fee arithmetic is required up front. Every kill was "you quoted a one-way cost as a round trip". The seed now demands per-leg fees, an explicit round trip including the exit, and the subtraction shown — and tells the model to walk away if the mechanism cannot clear its own costs.
  • Category rotation. Release notes talk about order books and funding rates, so the probe only ever tried the three categories where fees eat retail size. A saturated route now yields to the least-explored one.
  • Model split (@operator's call): generation on Sonnet, red team on Opus.
  • A timeout is not a verdict. A strategy was recorded as failing review because the reviewer hit the 180s clock. Timeout is 420s now and a failed call yields review-incomplete.

Result: two US-tradeable vetted strategies on the board — a Kalshi multi-outcome book arbitrage and an Alpaca/CME micro-bitcoin basis carry.

Two bugs worth calling out

The app froze twice. Ten modules called claude --print inline on the event loop; one cadence fire blocked every route for minutes and looked like a crash. All moved to asyncio.to_thread, and a test now parses every module and fails on any direct backend.call(...) inside an async def.

The probe starved itself. After going US-only the candidate pool shrank to one item while 16 URLs sat permanently "seen", so every click answered "no new venue program surfaced". A fully-seen pool now yields a revisit; sources widened 7 → 13 repos. Fixing this also exposed a test that had started calling the real claude CLI — the suite had gone from 85s to 10 minutes. conftest now forces the engine's kill switch for every test.

README

Rewritten against the code, and tests/test_readme_accuracy.py keeps it honest: board groupings and sizes, every documented axis existing on the model, every documented env var being one the code reads, defaults matching their constants. This README advertised the old money board for a full release after it was rebuilt; that class of drift now fails the build.

Verification

  • 2285 passing, 7 skipped; ruff check + ruff format --check clean
  • suite back to 73s after the live-LLM leak was closed
  • live board verified: 2 vetted cards, 9 counted in the flagged/ineligible block

Generated with Claude Code

rayketcham and others added 7 commits August 14, 2026 22:36
… categories

Eleven live cycles, zero vetted strategies. The panel was not being harsh —
it was right every time, and it kept being right about the same two things.

1. Fee arithmetic. Every kill was some version of "you quoted a one-way
   cost as a round trip". A two-venue position pays four fills, not two,
   and doubling the fee inverted the sign of the result each time. The
   seed now requires the model to state per-leg maker/taker fees, compute
   the round trip explicitly (entry AND exit, both legs), subtract
   slippage and carry, and show the subtraction — and to WALK AWAY and
   propose something else if the mechanism does not clear its own costs.
   Finding that out in the prompt costs nothing; finding it out in the
   panel costs a full cycle.

2. Category rotation. SDK release notes talk about order books and
   funding rates, so the probe routed to market-making and basis-carry
   every single time — the two categories where fees genuinely eat a
   retail-size edge. It never once tried capital-automation or
   incentive-capture, where the venue PAYS you rather than you extracting
   from other traders. A saturated route now yields to the least-explored
   category; the signal still grounds the strategy.

Not fixed by weakening the reviewer. A weaker model would pass these
drafts because it did not check the arithmetic, and the board would fill
with strategies that lose money on contact.

Co-Authored-By: Claude <[email protected]>
…g a timeout look like a verdict

Two fixes from watching live cycles.

1. Model split (operator's call, and the right one). Drafting a strategy
   is a variety task where a faster model means more attempts per hour and
   more shots on goal; reviewing one is where rigor pays. `resolve_role_
   backend("generate")` defaults to Sonnet, `("review")` stays on Opus.
   Both overridable (FORGE_BOT_GEN_MODEL / FORGE_BOT_REVIEW_MODEL), and
   the FORGE_LLM_BACKEND kill switch still wins — a role must never be a
   way around it.

2. A timeout is not an objection. The revision pass hit the 180s CLI
   timeout, `stress` read the absent answer as "no usable revision", and
   the strategy was recorded as having FAILED review. It had not; the
   reviewer ran out of clock. Timeout is now 420s by default
   (FORGE_LLM_TIMEOUT_SEC), and a failed call yields a distinct
   `review-incomplete` verdict instead of `flagged`. A model that says
   "unfixable" is still a real rejection.

Co-Authored-By: Claude <[email protected]>
…the generator

Operator's call, and it makes the board mean something: if the red team
rejected it, it does not belong on a board of ways to make money.

- the grid renders VETTED strategies only
- flagged/below-bar move to a counted, collapsed block with the reason for
  each — visible and auditable, not competing for attention
- headline counts both, so an empty board still says how much was tried

And the part that compounds: what the panel rejected now feeds forward.
`_bot_avoid_lessons` pulls the last six rejections with their objections
into the generation seed as "already rejected on this board, and why — do
not repeat these". The panel kept killing the same two errors (a one-way
fee quoted as a round trip; a capacity claim the reward pool cannot pay)
and nothing carried that forward, so the generator made them again every
cycle.

Cards are also compact now. They were rendering the whole spec inline —
~1.5KB each, four of which read as a wall — so the card carries what you
scan by (venue, edge, capital band, one-line mechanism, the objection in
two lines) and folds the full spec behind one toggle.

2148 tests passing.

Co-Authored-By: Claude <[email protected]>
Twice now the symptom was "the whole dashboard is dead" — every route
hanging, no response at all, for minutes at a time. Both times a cadence
was sitting inside subprocess.run(["claude", "--print", ...]) on the event
loop. The CLI backend blocks for tens of seconds per call and a review
panel makes six, so a single fire can freeze the app for the better part
of ten minutes, and it looks like a crash rather than a busy engine.

Ten modules were calling the backend inline inside async functions:
ambition, cashflow, fundability, pki, premortem, snipe, llm_generator,
the challenge and verdict-audit runners, and one route. All now go
through asyncio.to_thread.

The real fix is the test: it parses every module, finds each `async def`,
and fails on any direct `backend.call(...)` inside one. Passing
`backend.call` to to_thread is a reference and stays legal; invoking it
inline does not. Six sync helpers are exempted by name with the reason
recorded, so the exemption list is a decision rather than a silence.

2238 tests passing.

Co-Authored-By: Claude <[email protected]>
…ggested

The operator is US-based, and the board was proposing venues they cannot
legally trade on. Five of the eight stored strategies named the offshore
Polymarket CLOB or Hyperliquid-vs-Binance/Bybit pairs — including three
marked "vetted", which is the board asserting the opposite of the truth.

- `Venue.us_status` (eligible / restricted / verify) on every registry
  entry, and the registry is now US-first: Kalshi, Polymarket US, Alpaca,
  Interactive Brokers, Tradier, Coinbase, Kraken as eligible; Hyperliquid,
  dYdX, Binance, Bybit, OKX, Betfair and the offshore Polymarket CLOB
  recorded as restricted so the gate can refuse them BY NAME
- `venue_us_status()` resolves the free-text venue strings specs actually
  write ("Polymarket (CLOB)", "Hyperliquid perps paired against Bybit")
- the gate refuses restricted venues outright, and FAILS CLOSED: an
  unrecognised venue is "verify" and is refused pending eligibility, never
  assumed safe. A strategy naming both an eligible and a restricted venue
  resolves to restricted — a two-legged strategy is only as tradeable as
  its worst leg
- the seed states the constraint, lists the eligible venues, and calls out
  the specific trap: Polymarket US is a DIFFERENT venue from the offshore
  CLOB and does not share its market structure
- the probe only sweeps repos for venues the operator can use, so a cycle
  is no longer spent on a signal the gate will reject
- `reverdict_us_eligibility()` re-judges what was already stored; 5 items
  moved to `us-ineligible` and off the board, shown with the reason

The registry records a status and a note, never a legal conclusion —
"confirm before funding" stays on every entry, because venue terms and
state-level availability move and this engine does not track them.

2260 tests passing.

Co-Authored-By: Claude <[email protected]>
…ay call a live model

"Generate one now" answered "no new venue program surfaced from any
source" on every click. Not a broken button — the probe had declared the
world exhausted. Restricting the board to US venues shrank the candidate
pool to ONE item while 16 URLs sat permanently in the seen set, so there
was never anything fresh to pick.

- a fully-seen pool now yields the highest-scoring candidate marked
  `revisit` instead of nothing. A previously worked program is not used
  up: what gets built from it depends on the category rotation, the
  mechanism drawn from the library, and the accumulated rejection lessons,
  and all three have moved since the last visit. Only a genuinely empty
  pool returns None, and a revisit is labelled as such in the probe log so
  it is never mistaken for fresh signal
- widened the sources from 7 repos to 13 across Kalshi, Alpaca, Coinbase,
  Kraken, Interactive Brokers and the DeFi protocols. Live pool went from
  1 candidate to 5

Also: the same behaviour change turned a cadence test from a no-op into a
test that ran the ACTUAL pipeline against the real `claude` CLI. The suite
went from 85 seconds to ten minutes and burned real generations, and the
only reason it surfaced was the absurd wall-clock. conftest now sets
FORGE_LLM_BACKEND=none for every test — the engine's own kill switch,
honoured by every resolver. A test that genuinely wants a backend
monkeypatches the resolver or opts out explicitly, which is local and
visible. Suite is back to 71s.

2267 tests passing.

Co-Authored-By: Claude <[email protected]>
…e (v0.24.2)

The /money-bots section described the board as it was designed, not as it
shipped: it promised drafts were dropped when they are now stored with a
verdict, said nothing about the US-only rule, the model split, or
rejections feeding forward, and asserted the panel had killed every draft
when two have since passed.

Rewritten against the code, with the numbers checked: 21 playbook
mechanisms, 13 probe repositories, four lenses, 33 probes and 5 admitted.

The durable part is tests/test_readme_accuracy.py. This README has drifted
before — it advertised /money-bots as fundability-ranked product ideas for
a whole release after the board was rebuilt around a different axis — so
every mechanically checkable claim is now a test: board groupings and
their sizes, every documented axis existing on the model, every documented
env var being one the code actually reads (including the ones bound by
pydantic's FORGE_ prefix rather than written down), the defaults matching
their constants, and the specific stale claims that were wrong today.

Prose that cannot be checked mechanically stays a human review, but
anything with a number in it now fails loudly when the code moves.

2285 tests passing.

Co-Authored-By: Claude <[email protected]>
@rayketcham
rayketcham merged commit 6dfb54b into main Aug 15, 2026
6 checks passed
@rayketcham
rayketcham deleted the fix/bot-generation-quality branch August 15, 2026 01:17
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.

1 participant