Bot review - #324
Draft
LouisP96 wants to merge 22 commits into
Draft
Conversation
Reads the structure back out of the explanation string that ForecastBot._create_comment produces: the three top level sections, each forecaster's prediction from the summary bullets, and each forecaster's rationale. The same string is saved to disk and posted as a Metaculus comment, so this reads either source. Forecasters are keyed R<report>:F<forecaster> because a bot with research_reports_per_question > 1 has a forecaster 1 in every report. Model names on the bullets are optional; the framework does not write them, though bots may annotate their own. Co-Authored-By: Claude Opus 5 <[email protected]>
Sections and forecaster rationales come from the same splitter ForecastReport already uses, rather than a second regex implementation. Only the summary bullets are read directly, since they are not headings. Co-Authored-By: Claude Opus 5 <[email protected]>
The bot review needs two things the client could not yet fetch: the comments a bot posted (its forecast reports, since the comment text is the report explanation verbatim) and the tournament standing they earned. get_own_comments defaults to is_private=True. post_question_comment posts private comments by default, and the list endpoint omits private comments unless asked for them, so a bot's own reports are otherwise invisible -- and the API returns no count, making that indistinguishable from a bot that never commented. The endpoint also refuses any author but your own, so there is no cross-bot variant to offer. Leaderboard entries include community aggregates, which have no user and carry an aggregation_method instead. They are ranked alongside forecasters, so they are kept rather than filtered: comparing your rank against them is how you tell whether you beat the crowd. Verified live against the Metaculus API: three freshly posted comments round-trip, and the MiniBench 2026-06-29 leaderboard parses to the same rank and score the site reports. Co-Authored-By: Claude Opus 5 <[email protected]>
Co-Authored-By: Claude Opus 5 <[email protected]>
One row per question in a tournament: the bot's latest forecast reduced to something readable, how the question resolved, and the official scores. Replaces the standalone urllib client the prototype used. The list endpoint omits my_forecasts, so scores and the bot's own forecast only come from per-post detail fetches; the list sweep is just there to enumerate the tournament and to catch questions the bot never forecast. Verified against MiniBench 2026-06-29: 60 questions, 37 forecast, 36 scored, 1 annulled, rank 17 at 348.5 -- matching the public leaderboard and the prototype's output. Co-Authored-By: Claude Opus 5 <[email protected]>
Adds forecasted_only, which filters server-side with forecaster_id, and get_outcomes_for_posts for reviewing a handful of questions directly. Co-Authored-By: Claude Opus 5 <[email protected]>
Registered as a poetry script alongside source-archive, so a bot maker gets it from installing forecasting-tools rather than copying a driver into their own repo. Writes the table as json for later stages to read. Co-Authored-By: Claude Opus 5 <[email protected]>
Loads a local .env like the source-archive CLI does, and prints the user id in every mode, since my_forecasts is scoped to the token and an ambient one otherwise makes the review silently report no forecasts. Co-Authored-By: Claude Opus 5 <[email protected]>
--all read as every question on the site rather than every question in the tournament. Co-Authored-By: Claude Opus 5 <[email protected]>
Spans tournaments, so it is the query a scheduled review runs rather than one tied to a competition. Narrows server-side on scheduled resolve time, then filters on the actual one. Co-Authored-By: Claude Opus 5 <[email protected]>
The API filters on scheduled resolve time, so a question that resolved later than scheduled can fall outside the server-side window while sitting inside the requested one. Measured at 1 of 44 resolved questions, 2.6 days late; the window now reaches 14 days further back and the local filter on actual resolve time stays exact. Co-Authored-By: Claude Opus 5 <[email protected]>
Renames TournamentOutcomes to OutcomeTable and stores the whole Leaderboard rather than just our entry, so the report can print the rank denominator and score type. The CLI now prints the summary instead of dumping every row, and --summary writes it to a file. Co-Authored-By: Claude Opus 5 <[email protected]>
The list endpoint has no my_forecasts, so building a table costs one request per post. --from-json re-renders a table saved with --output without touching the API. Co-Authored-By: Claude Opus 5 <[email protected]>
The best/worst lists were hardcoded to spot peer score. That is right for MiniBench and the AI benchmark tournaments, whose leaderboards are spot_peer_tournament, but the Metaculus Cup is peer_tournament: a time-averaged score diluted by coverage, so a question can rank very differently under the two. Verified on the MiniBench leaderboard that the entry score is the plain sum of per-question spot peer scores (348.50 over 36 questions), and that peer/spot_peer tracks coverage (corr 0.92). Unknown score types keep the spot peer default. Co-Authored-By: Claude Opus 5 <[email protected]>
The comment a bot posts is its report, so it doubles as the trace of that run, and it needs no setup: the template ships with folder_to_save_reports_to=None, so local report files usually do not exist, while publish_reports_to_metaculus is on. Measured against the template bot's saved reports, a comment matches the local explanation to within three characters of trailing whitespace. A question can carry several runs, so QuestionOutcome.trace picks the one standing at spot_scoring_time - the run that actually earned the score. The count of questions whose only runs came after that time is reported in the summary, since those forecasts scored nothing under spot scoring. Comments attach to posts, so on a post with more than one question the runs are split by the *Question* line the report writes into its summary. Co-Authored-By: Claude Opus 5 <[email protected]>
get_own_comments defaults to private comments, but bots publish either way: prior_louis_bot's tournament reports are all public (64 comments over 64 posts) while its ad-hoc runs are private (16 over 11), and the template bot's are private. Fetching only one kind silently produced no traces for half the bots that would use this. Reading both per post would have doubled an already per-post request count, so both are now read in bulk and grouped by post: a 37-question tournament went from 37 comment requests to 2. Comments on posts outside the table are dropped without parsing them. Also moves the traced count below the annulled list, where the indented entries were reading as if they belonged to it. Co-Authored-By: Claude Opus 5 <[email protected]>
A trace records which run was standing when the question was spot scored, but get_trace read whichever comment was newest, so on a question the bot re-forecast it would have handed back reasoning that had no effect on the score. It now takes an optional comment_id, exposed as --comment, and the trace's own comment_id is what to pass. Drops post_id from RunTrace: it duplicated the row that owns the trace and nothing read it. Co-Authored-By: Claude Opus 5 <[email protected]>
Covers the four ways to build a table, what the summary and the JSON each carry, pulling reasoning back out a section at a time, and the Python equivalent. Verified the documented calls against a live question. Co-Authored-By: Claude Opus 5 <[email protected]>
Nothing in forecasting-tools writes a model name into a forecaster bullet (forecast_bot.py emits a bare "*Forecaster N*:"), so the field only ever populated for bots that annotate their own summaries. Reading one bot's convention is exactly what this module is meant to avoid. The parenthesised suffix is still tolerated by the pattern, now non-capturing, so annotated bullets keep parsing rather than failing to match. The forecaster key is the stable identifier across questions. Co-Authored-By: Claude Opus 5 <[email protected]>
The two modes had disjoint flag sets sharing one namespace, so "--show 44328 --output x.json" ran, printed research, wrote no file and reported nothing. Subparsers make the combination impossible to express. bot-review review --tournament <slug> --output review.json bot-review show 44328 --section research Co-Authored-By: Claude Opus 5 <[email protected]>
Bare load_dotenv() resolves relative to the frame that calls it, so from an installed console script it walks up from site-packages and finds nothing. "bot-review" is what the README and the workflow tell people to run, and it could not see a project .env at all; only "python -m" worked. Co-Authored-By: Claude Opus 5 <[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.
This PR adds functionality to more easily inspect and diagnose recent bot performance, specifically aimed at finding reasoning errors rather than technical issues.
Only a Metaculus token needed, no LLM (although I have also drafted a SKILL in the template bot repo to leverage these tools to pinpoint reasoning errors).
Specifically,
bot-review reviewpulls scores from the Metaculus API, summarising performance and highlighting the best and worst performing questions (over the course of n days or some specified tournament).E.g.
produces a json with the table of tournament questions and relevant forecast information, scores etc, and a summary in markdown:
Example summary output
Then
bot-review showprints one section of one report in order to inspect bot reasoning, perhaps on the worst scoring questions. These are pulled from Metaculus comments.E.g.
bot-review show 44328 --section research:Or e.g.
bot-review show 44328 --forecaster R1:F3to show a single forecaster's reasoning:And I've drafted a skill which I imagine should form part of the template bot repo rather than in forecasting tools?
The skill
My example output would be:
Example review written by the skill
Please let me know any thoughts!