feat(money-bots): pick a venue + stop the app freezing on inline LLM calls - #113
Merged
Conversation
…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]>
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.
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.
program_for_venuegrounds those in the venue's own documentation instead.The event loop, properly this time
The earlier sweep caught direct
backend.call(...)insideasync def. It missed the worse case, one frame down:semantic_dedup_checkis sync and shells out to the CLI, called inline from asyncshould_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:
capture_outcome_signalsnow 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
categoryfor 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
Generated with Claude Code