v0.24.2 — money bots: US-only, vetted-only board, and a README that stays true - #111
Merged
Conversation
… 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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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
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 --printinline on the event loop; one cadence fire blocked every route for minutes and looked like a crash. All moved toasyncio.to_thread, and a test now parses every module and fails on any directbackend.call(...)inside anasync 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 realclaudeCLI — the suite had gone from 85s to 10 minutes.conftestnow forces the engine's kill switch for every test.README
Rewritten against the code, and
tests/test_readme_accuracy.pykeeps 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
ruff check+ruff format --checkcleanGenerated with Claude Code