Skip to content

feat(money-bots): pick a venue + stop the app freezing on inline LLM calls - #113

Merged
rayketcham merged 1 commit into
mainfrom
feat/money-bots-pick-venue
Aug 15, 2026
Merged

feat(money-bots): pick a venue + stop the app freezing on inline LLM calls#113
rayketcham merged 1 commit into
mainfrom
feat/money-bots-pick-venue

Conversation

@rayketcham

Copy link
Copy Markdown
Collaborator

Pick a venue

The board only ever worked whatever the probe surfaced. Now you can point a cycle at one venue — the same move Missions made for the general boards.

  • Only US-eligible venues are offered (Kalshi, Polymarket US, Alpaca, Interactive Brokers, Tradier, Coinbase, Kraken). Offering the rest would be offering a cycle the gate is going to refuse.
  • A choice always produces work. Tradier and Polymarket US have no SDK repositories, so the probe never surfaces a candidate for them — without a fallback, picking one would answer "no venue program surfaced" and read as a broken picker. program_for_venue grounds those in the venue's own documentation instead.
  • Default stays "Any — follow the signal".

The event loop, properly this time

The earlier sweep caught direct backend.call(...) inside async def. It missed the worse case, one frame down: semantic_dedup_check is sync and shells out to the CLI, called inline from async should_accept — so every borderline save froze the entire app for the length of an LLM call. Same shape in /api/launchpad, /api/recruiter, the builder wizard, and the super-idea synthesizer.

The detector is now transitive: sync functions that call the backend, then anything sync calling those, failing on any async function invoking one inline. That's how it caught estimate_build, which never touches a backend itself — it calls _llm_estimate, which does.

Two false positives are documented rather than silenced:

  • a factory that returns a blocking callable doesn't block;
  • a sync callback defined inside an async function doesn't run on the loop — whoever invokes it decides, and capture_outcome_signals now awaits it off-thread.

Blocking feed fetches are threaded too, and GitHub calls now carry a token when present: unauthenticated allows 60 requests/hour and one probe makes 26, so a few cycles exhausted the quota and every source began returning 403 — which the probes correctly degraded to "nothing found", making the board look dead. The token is only ever sent to GitHub hosts.

The tail narrates the slow part now

Generation was announced only after it returned, so the tail sat frozen on category for the entire minutes-long model call — exactly the stretch being watched. It now announces on the way in, and the test asserts two generate events rather than "a generate stage exists", which is what let the gap through.

The tail also keeps itself current: 15s idle heartbeat, 3s while live, so an open tab picks up runs it didn't start.

Verification

  • 2497 passing, 30 skipped; ruff clean
  • deployed and verified live: picker renders the 7 eligible venues, board responds in ~14ms

Operational note: --reload watches a bind-mounted path where inotify doesn't fire, so Python changes never hot-load — templates and static files look live because they're read per request. The worker has to be restarted explicitly after Python edits, which is why several fixes in this branch appeared not to work when they were already on disk.

Generated with Claude Code

…LM calls

Three things, all from watching the board fail in front of the operator.

## Pick a venue

The board was purely signal-driven: it worked whatever the probe surfaced.
Now the operator can point a cycle at one venue — the same move Missions
made for the general boards.

Only US-eligible venues are offered, because offering the rest is offering
a cycle the gate will refuse. And a choice always produces work: Tradier
and Polymarket US have no SDK repositories, so the probe never surfaces a
candidate for them, and without a fallback picking one would answer "no
venue program surfaced" and read as a broken picker. `program_for_venue`
grounds those in the venue's own documentation instead.

## The event loop, properly this time

The earlier sweep caught direct `backend.call(...)` inside `async def`. It
missed the worse case one frame down: `semantic_dedup_check` is SYNC and
shells out to the CLI, called inline from async `should_accept` — so every
borderline save froze the whole app for the length of an LLM call. Same
shape in /api/launchpad, /api/recruiter, the builder wizard, and the
super-idea synthesizer.

The detector is now transitive: sync functions that call the backend, then
anything sync calling those, and it fails on any async function invoking
one inline. That is how it caught `estimate_build`, which never touches a
backend itself — it calls `_llm_estimate`, which does. Two false positives
are documented rather than silenced: a factory that RETURNS a blocking
callable does not block, and a sync callback DEFINED inside an async
function does not run on the loop — whoever invokes it decides, and
`capture_outcome_signals` now awaits it off-thread.

Blocking feed fetches (`fetch_pki_gaps`, `fetch_pulse_signals`,
`fetch_incumbent_intel`) are threaded too, and GitHub calls now carry a
token when one is present — unauthenticated allows 60 requests an hour and
one probe makes 26, so a few cycles exhausted the quota and every source
started returning 403, which the probes correctly degraded to "nothing
found". The token is only ever sent to GitHub hosts.

## The tail actually narrates the slow part

Generation was announced only AFTER it returned, so the tail sat frozen on
"category" for the entire minutes-long model call — precisely the stretch
being watched. It now announces on the way in, and the test asserts TWO
generate events rather than "a generate stage exists", which is what let
the gap through. The tail also keeps itself current: 15s idle heartbeat,
3s while a run is live, so an open tab shows runs it did not start.

2497 tests passing.

Co-Authored-By: Claude <[email protected]>
@rayketcham
rayketcham merged commit e73158d into main Aug 15, 2026
6 checks passed
@rayketcham
rayketcham deleted the feat/money-bots-pick-venue branch August 15, 2026 02:50
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