Forecasting any question
Marina answers forecasting questions with a probability or a number, backed by cited and checked evidence and several models, in about 20–60 seconds for $0.03–0.10. It also answers typed questions — pick one option, pick a set of options, give a number, rank a list, or name something — with a planning step, iterative research, several independent runs and a critique pass (Typed answers).
bun run forecast "Will the Fed cut rates at the FOMC meeting ending October 28, 2026?"bun run forecast "What will the US regular gasoline average be on Oct 15 2026?" --unit '$/gal'bun run forecast "…" --kind number --by 2026-12-31 --jsonIn the world, any entity can ask: forecast <question> (alias predict). Programs can call
POST /v1/forecast with {"question": "...", "kind"?: "probability" | "number", "resolveBy"?, "unit"?}
behind the model API’s auth; the reply is the full answer, below.
To have an HTTP answer scored and learned from when it resolves, as the forecast command’s
answers are, add "save": true or "resolves": "<venue>/<ticker>" (which also saves it). The
answer is kept under the entity your API key is bound to (a secret:name entry in
MODEL_API_KEYS), and the reply carries answerId for forecast track. A failed save returns
saved: false. Saving is refused (400) for a key bound to no entity, and for a measurement run
(x-marina-eval: …; mode=measure), whose outcomes must not teach.
Saved private predictions and research
Section titled “Saved private predictions and research”Open /predict or /research in the dashboard and connect a Marina account to start a saved
investigation. Each attempt keeps its answer, sources and captured execution history. You can
reopen the same attempt from the dashboard, a private chat card, the CLI or the native terminal.
Reading a saved result does not start another model call.
marina connect Analystmarina predict --name Analyst --plain "Will the release ship by October 31?"marina research --name Analyst --plain "What evidence would support releasing this change?"marina inquiry list --name Analyst --jsonmarina inquiry show CASE_ID --name Analystmarina inquiry tui --name Analystmarina predict and marina research open the native terminal on an interactive TTY.
Use --plain to wait for text, --json for machine output, or --detach to return the case and
attempt IDs immediately. --url selects the existing server; authentication uses its cached
account token or MARINA_TOKEN. Ctrl+C detaches from a wait; marina inquiry cancel ATTEMPT_ID
requests cancellation of server work. Terminal reads and cancellation do not require a coding
workspace.
In world chat, use inquiry predict -- <question> or inquiry research -- <question>, then
inquiry show CASE_ID. Predictions accept type:binary, type:number unit:points or
type:choice options:A,B,C; the CLI equivalents are --type, --unit and --options.
case:CASE_ID (--case) starts another attempt for the saved question, while
parent:ATTEMPT_ID (--parent) links child work. Reuse key:REQUEST_KEY (--key) when retrying
an uncertain submission; a deliberate new attempt needs a new key.
Cases are private to their durable account owner. The owner can use
inquiry grant CASE_ID ACCOUNT read or work to permit reading or contributing, and
inquiry revoke CASE_ID ACCOUNT to remove that grant. People and agents use the same account
permissions. inquiry review ATTEMPT_ID -- <text> records a review; inquiry resolve ATTEMPT_ID -- <outcome-json> records an authorized outcome through the existing forecast scorer. Private
results do not enter shared forecast histories or teach shared lessons. inquiry delete CASE_ID
erases the case and its owned evidence; the CLI spelling is marina inquiry erase CASE_ID.
Operator database backups and logical exports include private cases, evidence and access grants,
alongside other private world content. Keep these archives private. Erasing a case from the live
instance cannot erase copies already downloaded or held in an older backup.
Programs use the exported MarinaInvestigationClient or authenticated /api/investigations
routes. Start requests contain kind, question, an optional prediction answer shape and an
idempotencyKey. Attempts expose separate execution, answer-availability, evidence-capture and
accounting states. An interrupted attempt is retained after restart and is not automatically
replayed. A new attempt is required to retry it.
The current direct-model adapter captures supplied model inputs and returned outputs. Source bytes, retriever-internal calls and hidden model reasoning may be unavailable; the answer reports capture gaps. Accounting remains partial, and cancellation cannot guarantee that an already running provider stops or stops billing. Defaults are 80 admitted work calls, four concurrent work calls per episode and a ten-minute timeout; these are work limits, not a dollar ceiling. Private crew transport and historical research cutoffs are not supported by this adapter. Research produces a report and citations, without a forecast probability.
Research requires mechanically verifiable passages. A summary-only retriever can return useful
links while supplying no verifiable evidence; in that case the report fails with a recorded
reason. Use the search retriever (for example MARINA_FORECAST_RETRIEVER=search:openrouter)
to retain quoted page passages. An exhausted search-provider allowance is a provider failure,
not an empty search result. Predictions may still return a clearly marked partial answer.
Service admission is limited to 128 active attempts overall, 32 per owner and 1,000 retained attempts per owner. At most 16 capacity-limited calls run simultaneously. Captured JSON is limited to 2 MiB per object, 24 MiB per attempt and 128 MiB per owner. Journals allow 10,000 ordinary entries, with terminal and settlement entries still available after that limit; event reads return at most 200 entries per page. Erase unneeded cases to reclaim logical quotas. Saved cases remain until the owner erases them or the account is deleted; no automatic expiry is applied. Backup retention is controlled separately by the operator.
Qualifying an installation
Section titled “Qualifying an installation”From a source checkout, run bun run qualify:investigations /tmp/investigation-qualification
for a provider-free scale, journal, quota and erasure check. Its JSON report includes hardware,
thresholds and measurements for 1, 10 and 100 simulated actors. These are service measurements,
not evidence of live forecast quality or 100 simultaneously running models.
Prepare a small live pilot with an explicitly chosen model, retriever and spend target:
bun run qualify:investigations:live --model PROVIDER/MODEL --retriever search:openrouter --budget-usd 2Without --live, this only prints the plan. Add --live to permit provider calls. The pilot
creates a fresh database, records prediction/research/cancellation results, and checks private
SDK, CLI and world-chat reads. Spend targets check known charges before further work; upstream
calls and unreported costs can exceed them. Preserve failed pilots alongside successful ones.
After a successful pilot, build the dashboard and replay its report on a separate database:
bun run dashboard:buildbun run qualify:investigations:replay /path/to/live/report.jsonReplay needs Chromium and opens a local server without provider credentials. It checks the same
saved revisions and citations in the web UI, dashboard, private chat, CLI and native terminal,
including narrow screens and reloads, and rejects new admissions or spend during those reads.
Keep pilot databases, screenshots, captured evidence and qualification reports private; project
qualification records belong in marina-internal.
What happens
Section titled “What happens”- Research — a search-grounded model (OpenRouter’s web search) gathers dated, sourced facts: the current state, base rates, scheduled events, and what forecasters and markets expect.
- Citation check — each fact’s figures are looked up in the page it cites; lines are tagged
[verified],[unverified]or[unreachable]. Publishers whose terms bar bots are never fetched. - Analysts — one model per vendor (by default Claude Fable 5.1, GPT-6 Astra, Gemini 3.1 Pro) answers from the tagged dossier.
- Judge — Jev scores how well each analyst’s reasoning is supported by the verified facts;
weakly supported answers count for little. If the judge fails on an answer, that answer gets
no weight (an outage is no opinion, never a pass) and the error is recorded on it
(
judgeError) and in the answer’sjudgerecord (calls, errors, latency, cost). The arena’s research forecaster uses the same analyst + judge step (src/forecast/judge.ts). - Aggregate — probabilities are combined in log-odds, numbers as a weighted mean whose spread includes the analysts’ disagreement.
The answer carries everything needed to audit it: each analyst’s answer and reasoning, its
grounding score and weight, the sources, how many facts verified, cost and time — and a caveat
when the evidence was thin.
Typed answers
Section titled “Typed answers”Many questions want an exact answer, not a probability: which candidate wins, which bands a figure falls in, the published value, the top five in order. Give the answer’s shape and Marina returns one value of that shape.
bun run forecast "Who wins the São Paulo runoff?" --type choice --options "A=Candidate one|B=Candidate two"bun run forecast "Which ranges will the rate fall in?" --type multi --options "A|B|C|D"bun run forecast "What will the index read?" --type number --unit points --end 2026-10-09T00:00:00Zbun run forecast "Top 3 by weekend gross?" --type ranking --size 3 --context "Box Office Mojo, domestic"bun run forecast "Who will be named?" --type text --jsonIn the world: forecast <question> type:choice options:A,B,C [ends:<ISO time>] (also multi,
number, ranking size:<n>, text). Over HTTP: POST /v1/forecast with an answer spec —
{"type":"choice","options":[{"id":"A","label":"…"}, …]}, {"type":"multi",…},
{"type":"number","unit"?:"…","integer"?:true}, {"type":"ranking","size"?:5,"candidates"?:[…]}
or {"type":"text"} — plus optional endTime, asOf, context (resolution rules or format
notes), runs (1–9), researchRounds (1–4), critique (boolean) and contract (an explicit
answer contract). Which models run stays an
operator setting.
What happens:
- Plan — the planner restates the question, names the source that resolves it, the facts that decide it, what would change the answer, and the first search queries.
- Research rounds — search runs in bounded rounds; after each, the planner names what decisive fact is still missing and the next queries, or stops when the dossier is enough. If every round fails (an engine outage, an exhausted search quota), the runs still answer from the question and its notes, and the answer carries a caveat saying so.
- Lookups (on by default where available) — structured sources join the dossier as ordinary cited lines, steered by
the plan’s
datahints (a market search phrase, a sports key and teams, official series ids). For a number, the freshest official reading becomes the answer’s anchor: the runs start there, with the spread of that series’ own changes over the question’s horizon, and move further only for specific dated evidence. See Lookups. - Citation check — as above.
- Runs — K independent answers (analyst models used in turn), each validated against the
answer’s shape and, with a judge, weighted by how well the verified facts support it.
- A run whose answer misses the JSON shape gets output repair (a parse, then one re-encoding
shot, labelled
repaired:parse/repaired:shot). Repair never adds content. - A run whose answer has the right type but is incomplete — a ranking short of its size, a
multi-select short of its minimum, no answer, or a probability missing for some option —
gets ONE more call to the same analyst. The call shows the run’s own answer and reasons and
names exactly what is missing. Its reply is used only if it now validates, labelled
repaired:completion(the run’scompletionrecord keeps what was missing and whether it was accepted); its cost is the run’s. It is skipped in the time budget’s final phase, and a spend-cap refusal leaves the run as it was.options.completion: falseturns it off.
- A run whose answer misses the JSON shape gets output repair (a parse, then one re-encoding
shot, labelled
- Combine — by type: weighted plurality for a choice or a short string, per-option frequency
(≥ half the weight) for a set, the median (a 20 % trimmed mean from five runs) for a number,
Borda count for a ranking. The runs’ agreement is the answer’s
confidence.- Numbers are read with their scale and sign: “1.2 million”, “$3.4bn”, “68k”, “(79,000)”, “down 0.4”, a Unicode minus.
- When the unit names a scale (“USD billions”), an answer written with a scale word is converted to it.
- From three runs, a run written at another power-of-ten scale than the runs’ median (a thousands/millions confusion) is brought to the median’s scale first. The method then says
scale-aligned. - With
MARINA_FORECAST_SELECTION=confidence(opt-in), the run with the highest self-reported confidence is the answer, and that confidence is the answer’sconfidence. Agreement among same-model runs measures consistency, not correctness, while a run’s own stated confidence is much better calibrated. bun benchmarks/futurex/rescore-selection.tsre-scores saved forecasts under each mode offline, with no model calls. The default changes only whenconfidencebeatsagreementon a held-out split by the promotion margin.
- Critique — a second search looks for evidence against the leading answer, and a critic
may propose another. The proposal replaces the runs’ answer only when the critic’s confidence
exceeds the answer’s
confidence(the runs’ agreement, or the chosen run’s own confidence under confidence selection); both are kept in the record.
Verification (MARINA_FORECAST_VERIFY, on by default; off skips it; --formation verify
turns it on for one CLI run) — the verification formation inside one
forecast: before a run counts, an independent verifier (MARINA_FORECAST_VERIFIER, default the
critic) checks its draft against the dossier and the resolution rules — option semantics, unit and
scale, the latest reading, arithmetic — and a concrete correction replaces the draft. Each run
records the verdict and, on a correction, the draft it replaced.
The Arbiter (MARINA_FORECAST_ARBITER, default off; --formation arbiter): adjudication only
where answers disagree, in place of always-on verification. A forecast is contested when its runs’
drafts disagree, the verifier or the critic dissents, or the judge’s scores spread; only then is the
Arbiter (MARINA_FORECAST_ARBITER_MODEL, default the critic) asked. It keeps the majority, overrules
it with cited evidence, or abstains. An overrule of a majority needs citations from two independent
domains and confidence of at least 0.7 (MARINA_ARBITER_*). observe records the verdict on the
answer (arbiter) and changes nothing. on makes verifier corrections and critic revisions advisory,
so only a bar-clearing overrule changes the answer. replace also skips the per-run verifier. See
the Arbiter.
Lessons. A forecast can recall lessons from questions that have already resolved. There is
one lesson system: the judged, audited outcome loop
(outcome lessons). A resolved forecast,
FutureX week, Metaculus or ForecastBench question, or backtest row becomes an outcome; the outcome
becomes a candidate lesson (a category and one corrective rule), the decision layer judges it, and
it is stored in the lessons:forecast space. Every forecasting surface — the forecast command,
POST /v1/forecast, FutureX, Metaculus, ForecastBench and select backtests — recalls through the
same forecastLessonsFor (src/learning/forecast-bridge.ts), from the forecast and arena
domains, so a +nolessons ablation differs from its base arm only by the lessons. Recall needs no
armed learning loop and never creates the pool. Recall is lexical and byte-budgeted, and a lesson
is visible to a forecast only when its outcome was known at that forecast’s evidence cutoff
(visibleAt) — so a forecast made “as of” September never sees a lesson learned from an October
result. The lessons used are recorded on the answer (lessons), and the plan, the runs and the
critic all see them. MARINA_LESSONS=observe records them as observedLessons and shows them to
no model (the ablation arm); off recalls nothing. A retired lesson (lessons retire, or every
lesson citing a ledger run when that run is invalidated) is never recalled.
Barred sources. Research for a board never uses that board’s own pages: its dataset and
mirrors, which hold the resolutions, and its question, answer and leaderboard pages. FutureX,
Metaculus, ForecastBench and the select backtests each pass their list
(benchmarks/forecasting/barred.ts) to typedForecastDeps({ exclude }). The list is applied to
every retrieval engine, including the ones that ignore a brief’s exclude. Barred sources, and
the report lines that cite them, are dropped. A captured evidence snapshot that held a barred
source is dropped whole. Each drop is counted in the report’s warnings.
One builder. The forecast … type: command and POST /v1/forecast with an answer spec build
a forecast the same way (src/forecast/surface.ts): the operator’s models, the lesson pool, the
prior and recalibration settings, and the operator’s formation (MARINA_FORECAST_FORMATION) routed
by MARINA_FORECAST_ROUTE. The API adds only runs, researchRounds and critique.
Scoring a typed answer. A typed answer linked to a resolver Sample (resolves: or
forecast track) is scored when it resolves (src/forecast/typed-score.ts): a choice by
multiclass Brier from its distribution (else its pick), a multi-select by per-option Brier, a
ranking by top-k overlap, a text answer by normalised exact match, and a number by CRPS from its
uncertainty. The outcome is matched to the answer’s own options; one that matches none leaves the
answer open rather than scoring it against a guess. Each scored answer becomes an outcome for the
lesson loop.
Retrieval isolation (for past cutoffs). Date-filtered engines (tavily:, exa: with
EXA_API_KEY) only return pages published inside the window. Any other engine can be wrapped with
MARINA_FORECAST_RETRIEVAL_FILTER=strict, which keeps a research line only when every page it
cites has a known publication day on or before the cutoff (the engine’s date, else a date in the
URL), it cites no live-result page (encyclopedias, scoreboards, markets, charts) and it names no
later day (src/arena/research/isolation.ts). closed-book as the retriever does no retrieval at
all — a lower bound.
Evidence cutoff. Each answer uses nothing published after a cutoff: asOf when given, else
the earlier of now and the question’s endTime. Research asks for nothing later, search engines
that filter by date (Tavily) drop later results, sources dated after the cutoff are discarded,
lookups read values as of the cutoff, and lookups that only know current values are skipped when
the cutoff is in the past. The cutoff
and how it was chosen are recorded on the answer; a past cutoff adds a caveat, because engines
without date filters can still surface later pages.
Evidence depth. The citation check verifies a line with figures when every figure is on a
cited page (read in either the English or the continental-European number format) and a line
without figures when its whole text is on the page verbatim; anything else is never evidence. The
dossier each run reads is sized to the analysts’ context window, and the judge reads up to twelve
chunks of verified text. The question’s named resolution page is read first and its
source searched first. When fewer than five lines verify, one more round searches before the runs;
otherwise, when no run is grounded in verified evidence, that round searches for what the runs and
the judge left unverified, and the runs are redone only if it verified something new (the first
runs stay in initialRuns). When the runs disagree, one round searches the point they disagree on
and one more run reads it (crux: true), pooled with the others.
The typed answer object keeps every stage — plan, each research round (with its retrieval funnel and warnings), lookups, every run with its reason and weight, the combination and its support, the critique, the cutoff, sources, verification, evidence budget, judge record, cost and time. In-world answers are saved like any other forecast.
Answer contract
Section titled “Answer contract”A question often says how its answer must be written: “End with the option letter in
\boxed{}”, “a date in YYYY-MM-DD”, “rounded to one decimal place”, “select up to two”, “list
the top 3”, “in at most four words”. MARINA_FORECAST_CONTRACT (default off) turns those
instructions into an explicit answer contract (src/forecast/answer-contract.ts) — the form
(single choice, multi-select with pick bounds, a number with unit / integer / decimals, a date
in a format, a ranked list of a size, free text with a word limit), an optional \boxed{…}
wrapper and the format line verbatim — read from the question and its context and refining the
typed answer spec. A question that states no such instruction gets no contract.
observeparses the contract and records on the answer (contract) whether the final answer meets it. No prompt and no answer changes.oncarries the contract to the planner, every run, the verifier, the critic, the Delphi and tournament formations and the judge. Then a final validator checks the finished answer. A lossless rewrite (letter case, an option’s label to its id, a date in another notation, rounding to the stated decimals, a list cut to its size) is applied directly. A real violation (not an option, two dates, a short list, too many words) is re-asked once of the critic (else the planner), and if that fails a deterministic fallback is used (the first option named, else the most probable; the first number or date; a list or text cut to its limit). The answer keepscontract.status(ok,normalized,reasked,fallbackorviolation), the pipeline’s own answer incontract.beforeand the required rendering incontract.rendered; the prediction changes only when the contract’s value is a valid typed answer. A forecast with no answer is never filled in.
A request that carries its own contract is enforced unless the mode is observe. Over HTTP,
POST /v1/forecast accepts an optional contract beside answer: {"form": …} plus any of
minPicks, maxPicks, unit, integer, decimals, dateFormat (YYYY-MM-DD, YYYY-MM,
YYYY, MM/DD/YYYY, DD/MM/YYYY, Month D, YYYY, D Month YYYY), size, maxWords,
maxLength, wrapper ("boxed") and formatLine. The form must fit the answer type
(single-choice for choice, multi-select for multi, number, ranked-list for
ranking, free-text or date for text), options always come from answer, and an invalid
contract is a 400.
Priors, calibration, the skeptic crew and routing
Section titled “Priors, calibration, the skeptic crew and routing”Four mechanisms from Marina’s Social Simulation Arena forecaster, generalised to any typed question. All are opt-in, and each learns only from resolved history visible at the forecast’s cutoff: a record counts only once its outcome was known at or before the cutoff, and a question never learns from its own record (the same rule as lessons).
Resolved history (MARINA_FORECAST_HISTORY, a JSON-lines file; src/forecast/history.ts). One
record per resolved forecast: the forecast in numbers before adjustment, the prior it had, the
outcome as option ids or a value, when the outcome became known, and optionally the formation and
the board’s score. Option labels are kept only as short hashes. No question or answer text is
stored. Records are appended through noteResolvedForecast (src/forecast/adjust.ts), once per
id and never for a measurement run. Two writers feed the shared file:
- A saved answer (the
forecastcommand, orPOST /v1/forecastwithsave) is written when its Sample resolves, at the Sample’s time. This covers choice, multi and number answers, including a plain numeric forecast. A plain numeric forecast has no adjustment stage, so its saved mean and sd are its raw forecast. A number needs an sd, because number recalibration rescales it. bun run futurex learnwrites filed answers as their weeks resolve.
The FutureX clean backtest learns only from its own resolved rows, through a fresh in-memory history per run, and never touches the shared file.
Prior shrink (MARINA_FORECAST_PRIOR, default earned). A strong baseline caps a
forecaster’s large misses, so the answer is pooled toward the best prior available at the cutoff.
By default the weight is earned: it starts at 0 and changes an answer only after a fitted weight
wins on held-out history. Recalibration (MARINA_FORECAST_CALIBRATION) and formation routing
(MARINA_FORECAST_ROUTE=on) also run by default on the same rule, so with no history no answer
changes. on adds the operator’s cold-start weight toward a supplied prior, and off skips the
stage. The priors, in order:
- a market, community or statistical forecast the caller supplies (
priorsonPOST /v1/forecast:source,distributionorvalue/sd, andat, the time it was observed — a prior observed after the cutoff is rejected and the rejection recorded). Astatisticalprior comes from data rather than people: a series’ own history or a published reference class’s base rate (see Statistical priors); - one priced market a lookup matched, for a yes/no question. Two or more markets on one venue
are ambiguous and give no prior. When several venues each matched exactly one market and the
markets name the same question (every pair of titles shares at least two words), their prices
are pooled: a log-odds mean, weighted by log(1 + volume) when every venue reports volume in
the same unit (else equal weights, since USD and play money are not comparable). The prior’s
detaillists each venue’s price and weight and the spread, flagged when the venues are more than 0.15 apart. A single venue’s prior is unchanged; - for a number, the freshest official reading (the lookups’ anchor);
- the base rate of the question’s class in resolved history (the caller’s
category, else the answer type) — per recurring option label for a choice, per class for a multi-select, smoothed toward uniform; - the type default: uniform over a choice’s options, 0.5 per multi-select option.
Only an informative prior (1–4) earns a shrink. The type default is recorded but never shrunk
toward, because LLM forecasters already hedge toward the middle and pooling toward uniform makes that
worse. Probabilities are pooled in log-odds (a geometric pool for a choice); a number moves
linearly. Before there is evidence, the weight is MARINA_FORECAST_PRIOR_WEIGHT (0.5) toward a
supplied prior, and 0 toward anything else. A fitted weight replaces the default
only when it wins on held-out history (below). Each prior also records its time to close, and its
liquidity in USD when the caller supplies liquidity. When a bucket has enough records, the weight
is fitted within it: time to close of ≤ 7 days, ≤ 30 days or more, split by liquidity below or
above $10k when known. This lets a liquid market near its close earn more weight than a thin market
far from it.
Calibration (MARINA_FORECAST_CALIBRATION=on, or observe to fit and record without
applying). One map per answer group (src/forecast/recalibration.ts):
| group | map |
|---|---|
| a two-option choice (all yes/no questions share one) | Platt in log-odds on the first option |
| any other choice | Platt one-vs-rest, renormalised |
| a multi-select | Platt on each option’s own probability |
| a number | a scale on the stated sd (the point is unchanged) |
Cold start: MARINA_FORECAST_CALIBRATION=fixed:<a> (for example fixed:sqrt3) applies a
fixed Platt slope with b = 0 from the very first forecast. It needs no history, so a new board can
use it. A slope above 1 extremizes, which counters the pipeline’s hedging. You can also seed the
history with your own earlier resolved runs (records from the same pipeline, never other
forecasters’ forecasts).
Pooling and the tail guard. MARINA_FORECAST_POOL=logodds averages the runs’ probabilities in
log-odds (a geometric pool), so confident runs that agree are not dragged toward uniform. The
default is linear. MARINA_FORECAST_PROB_CLAMP=<ε> holds the final probabilities inside
[ε, 1 − ε]; use it on log-scored boards, where one confident miss is unbounded.
A monotone map never changes a choice’s most probable option or a number’s point estimate. It changes the probabilities a proper score reads, and which options of a multi-select clear one half.
The held-out rule (both of the above): fit on the older 60 % of the visible history, compare
with the default (identity, or the default weight) on the newer 40 %, and adopt the fitted setting
only if it improves MARINA_FORECAST_CALIBRATION_SCORE (Brier or log; CRPS for numbers) by at least
MARINA_FORECAST_CALIBRATION_MARGIN (5 %, relative). It also needs at least
MARINA_FORECAST_HISTORY_MIN (50) visible records. If adopted, it is refitted on everything
visible. This is how the arena chooses each series’ spread.
What the answer records. A question that asked for probabilities gets the adjusted ones. One
that asked only for a pick keeps its pick unless the adjustment moves the most probable option (or a
multi-select’s set at one half); the replaced pick is recorded. The answer’s adjustment holds the
raw forecast, the prior (source, value, time), the weight and the map with their held-out scores,
record counts, the newest outcome time used (always at or before the cutoff), and the final numbers.
The skeptic crew (MARINA_FORECAST_FORMATION=skeptic; src/forecast/skeptic.ts). This is the
arena’s crew as a formation. After the ordinary research:
- the first analyst proposes as the statistician (base rates, readings, usual change);
- the second analyst proposes as the analyst (the specifics and sources);
- the critic, else the third analyst, acts as the skeptic. It sees the prior, both proposals, the research and the forecaster’s own track record for this kind of question, and returns how much of the proposals’ move away from the prior to trust (0 to 1).
The answer is the prior plus trust × (the proposals’ mean − the prior), computed in log-odds by code, not by a fourth model. The skeptic can only shrink toward an informative prior and never adds extremity; for a number it may only widen the spread. Without an informative prior, the proposals’ log-odds mean stands unshrunk, and extremity is left to calibration. With one configured model, every role runs on it. Rankings and short strings have no prior: the two proposals are combined like runs.
Formation routing (MARINA_FORECAST_ROUTE=off|observe|on, default observe: the pick is
recorded on every answer while the default formation answers; every forecast surface. Resolved
history — kept by default next to the world database — carries each answer’s formation and score;
src/forecast/routing.ts). For each question class (the caller’s category if it has enough
records, else the answer group), a candidate formation replaces the default only when both hold:
- on at least
MARINA_FORECAST_ROUTE_MIN_Nquestions both answered, its paired gain in the board’s score has a 95 % lower bound above zero; - the gain clears the fishing margin 0.02 + 0.01·log₂(1 + candidates tried), the rule the benchmark ledger uses for promotions.
Otherwise the default formation answers. The decision is recorded on the answer (route).
Statistical priors
Section titled “Statistical priors”Many questions are statistics before they are research: “will this series be higher on date d
than on date D?”, or “will this kind of event happen again?”. A model asked to reason about them
from a single number tends to make confident calls on what is close to a coin flip. A statistical
prior answers them from data, with no model call, and is supplied to the forecaster as a
statistical prior: the runs see it, and prior shrink pools toward it.
- Series history as of a cutoff (
src/forecast/series-history.ts): FRED (vintage-correct withFRED_API_KEY: the observations as published the day before the cutoff; series without ALFRED vintages, such as licensed market prices, are read as observed and labelled not vintage), daily stock closes (split-adjusted only for splits before the cutoff, never the dividend-adjusted close) and any DBnomics series. Every point is dated before the cutoff’s day. - The comparison prior (
src/forecast/series-prior.ts): the frequency of a rise over the same horizon by drift, momentum or the same calendar window in past years (each also shrunk halfway to 0.5). Each series chooses its own method by replaying every method at earlier cutoffs inside its own past; the winner must beat 0.5 by 5 % (relative Brier), or the prior is 0.5. - Reference-class rates (
src/forecast/reference-class.ts): from published outcomes, nested classes from broadest to narrowest, each pulled toward its parent in proportion to its evidence. Only outcomes settled two days before the cutoff count.
A prior-only forecast (src/forecast/prior-answer.ts) is the answer the best prior gives on
its own, at no cost. Benchmark adapters include it as a candidate configuration: a model
configuration that cannot beat its own prior on held-out questions is adding noise.
Numeric helpers for agents
Section titled “Numeric helpers for agents”Every agent has two rank-0 commands for the arithmetic of forecasting. Neither calls a model.
calcdoes exact arithmetic, unit conversion, percent change and compound growth, for examplecalc (380 / 250)^(1/7) - 1.quant fit <values> steps:N(orto:<date>) fits a series and returns a mean, an sd and quantiles. It chooses among persistence, EWMA, a persistence/EWMA blend, a linear trend, a damped trend and a combination by their own past errors.quant buckets mean:<m> sd:<s> -- <ranges>(orq:<level>=<value>,…) gives each numeric range’s probability and the most likely range. Use it for multiple-choice questions with numeric ranges.
The quantitative-estimates skill teaches when to use each command. It ships with the server, so
any agent can read it with skill builtin quantitative-estimates (and skill search finds it)
without importing anything. The researcher and market-oracle roles carry a
quantitative-estimates trait that points to it. The mechanics are in
architecture/forecasting.md.
The quant stage in typed forecasts
Section titled “The quant stage in typed forecasts”MARINA_FORECAST_QUANT (default off) adds a deterministic stage to every typed forecast. It
makes no model call.
- Series baseline. When a lookup read an official series with history (FRED, BLS), the
series is fitted with
fitSeriesand projected to the question’s end time. - Range buckets. When every option of a choice is a numeric range (“Below 2.5%”, “2.5% to 2.9%”, “3.0% or higher”, “Between 100,000 and 200,000”, or counts such as “0”, “1”, “2 or more”), each option’s probability comes from that projection. Without one, it comes from a supplied numeric prior with a value and sd. Ranges displayed with rounding gaps are joined at the midpoints.
The modes:
observerecords the baseline, the bucket probabilities and the blend they would give on the answer (quant). Nothing a model sees changes, and the answer does not change, so the effect can be measured from resolved questions at no extra cost.onalso shows both to the runs (a dossier line, used as a reference class) and to the judge (an evidence chunk). For a choice asked with probabilities, it also blends the bucket probabilities into the runs’ distribution with weightMARINA_FORECAST_QUANT_WEIGHT(default 0.25). The prediction then becomes the most probable option.
Intake triage
Section titled “Intake triage”MARINA_FORECAST_TRIAGE (default off) classifies every typed forecast before any method runs,
so a figure that is already published gets read rather than forecast:
| Class | When | Method under on |
|---|---|---|
lookup |
the answer is already published at the cutoff | an authority read-off: one research round restricted to the publisher, the field read from verified evidence and checked; the full panel if it is not found |
anchor |
a numeric official series, not yet published | the panel with the quant stage on and the anchor method in its notes |
list |
a ranking, top-N or board | the panel with the ranking method (rebuild the board, project, sort) |
forecast |
anything else | the full panel, unchanged |
Dates decide “already published?”, never a model: the question’s stated resolution time, an
official FRED series that already holds the period, a release date the question names, a dated
event that is over, the fact model’s expected publication date, or a known statistic’s usual
release lag. NOW is the evidence cutoff, so in a backtest a figure published after the cutoff is
never a lookup. MARINA_FORECAST_TRIAGE_MODEL (default openrouter/deepseek/deepseek-v4.1-flash;
none = deterministic) adds facts — publisher, metric, period, expected publication date — and
MARINA_FORECAST_TRIAGE_CALENDAR reads FRED’s series and release calendar. observe records the
class, its reason, the facts and the method on the answer (triage) without changing anything.
Agents use the same classifier with triage <question> [type:…] [asof:<cutoff>] (rank 0, no
model call) and the built-in intake-triage skill. bun run eval:triage measures it offline on
stored results: the class mix and each class’s mean score and share of the loss.
Residents as forecasters
Section titled “Residents as forecasters”With MARINA_FORECAST_RESIDENTS=on, the world’s residents can answer a forecast themselves, with
their own tools, memory and lessons:
forecast Will the Fed cut rates in December 2026? role:forecaster pattern:delphi residents:3forecast Will turnout exceed 60%? crew:pollsters pattern:ensembleforecast Was CPI above 3% in May 2026? asof:2026-06-01 measure:yes-
role:<role>asks the live resident with that role and the highest standing;crew:<crew>asks the crew servingmarina:<crew>. With neither,role:forecaster. -
pattern:ensemble(the default) asksresidents:(default 3) residents independently and combines their answers by type. The other patterns build on those answers:pattern:delphishows each resident an anonymous panel summary and asks it to revise once.pattern:tournamentputs the answers through a knockout bracket judged by the pipeline’s critic.pattern:skepticasks two residents (a statistician and an analyst) and lets the critic set how far to trust their move from the prior.
These are the same formations a plain typed forecast runs, from the one formation catalogue.
-
The forecast is a world task: Marina researches first, gives every resident the same dossier, and combines with the same pure code as any typed forecast (critique, answer contract, triage, the Arbiter). The reply names the task, the residents that answered and the calls spent.
-
The as-of fence. With a past
asof:(ormeasure:yes, which always fences), every resident’s tools are bound to that date while it works: web search answers only from date-strict sources published by then, a fetched page is the archived copy from then,datareads as of then,recallandlessonsserve only what was known by then, and anything that could reach later evidence (other outbound tools, a nestedforecast) is refused with a message naming the cutoff. A live question about the future runs without it. -
Each resident’s own run is filed as its own answer, so when the question resolves the residents whose runs scored earn
forecast_resolvedstanding (aneval_modemeasurement never does). -
POST /v1/forecasttakes the same aspattern,crew,roleandresidentswith a typedanswer; it needs an API key bound to an entity. -
Outside a server (
bun run forecast),marina:<crew>analysts are asked over HTTP with the fence and deadline as headers;role:analysts need the server.
Keeping score
Section titled “Keeping score”Every in-world answer is saved with its full audit trail (forecast list shows yours). To have
one scored, link it to the resolver Sample it resolves on — forecast <question> resolves:kalshi/<ticker>, or later forecast track <id> kalshi/<ticker>. When that market or
watch resolves, the answer is scored once: Brier for a probability, CRPS for a number. Typed answers
are saved with their answer as one string (prediction).
Configuration
Section titled “Configuration”| Variable | Default | Meaning |
|---|---|---|
OPENROUTER_API_KEY |
optional | retrieval and the Jev judge run through OpenRouter; without it the forecast runs degraded on whatever models are configured (Single model) |
MARINA_FORECAST_ANALYSTS |
three vendors via OpenRouter; else up to three configured models (a local runtime first) | comma-separated provider/model ids |
MARINA_FORECAST_RETRIEVER |
openrouter-web:google/gemini-3.1-pro-preview@exa,search with an OpenRouter key; else search (the search backend chain, keyless DuckDuckGo at the end) |
the research engine(s) |
MARINA_FORECAST_DOSSIER_CHARS |
sized to the analysts’ context window (12 000–60 000) | typed answers: research characters each run reads; verified lines are kept first |
MARINA_FORECAST_FOLLOWUP |
on |
typed answers: one extra research round — before the runs when fewer than MARINA_FORECAST_MIN_EVIDENCE (5) lines verify, else after them when no run is grounded |
MARINA_FORECAST_ARBITER |
off |
typed answers: observe records an Arbiter verdict on contested forecasts; on lets only a bar-clearing overrule change the answer (verifier and critic advise); replace also drops the per-run verifier |
MARINA_FORECAST_DISAGREEMENT |
on |
typed answers: when the runs disagree, search the crux and pool one more run |
MARINA_FORECAST_JUDGE |
jev |
jev, decisions (the configured MARINA_DECISIONS backend — OpenJev, TypeSafe, a chat classifier; falls back to jev when none is set) or none (equal weights) |
MARINA_FORECAST_PLANNER |
the first analyst | typed answers: plans and names research gaps |
MARINA_FORECAST_CRITIC |
the planner | typed answers: the disconfirmation pass |
MARINA_FORECAST_RUNS |
3 |
typed answers: independent runs (1–9) |
MARINA_FORECAST_RESEARCH_ROUNDS |
2 |
typed answers: research rounds (1–4) |
MARINA_FORECAST_CRITIQUE |
on |
off skips the critique |
MARINA_FORECAST_SELECTION |
agreement |
typed answers: confidence takes the most self-confident run instead of combining by agreement (see Combine) |
MARINA_FORECAST_BUDGET_S |
unset (none) | typed answers: wall-clock budget per forecast. From about 75 % no further research round starts. At the cap, lookups, verification and the critique are skipped and the answer is combined from the runs that have finished (the first to finish, when none has). The answer then carries budgetForced, and a skipped check is verified.verdict: "not_run" |
MARINA_FORECAST_LOOKUPS |
auto |
structured sources: auto (every one that can run here), off, or a list of polymarket, kalshi, odds, fred, bls, markets / all, plus the opt-in manifold (see Lookups) |
MARINA_FORECAST_CONTRACT |
off |
typed answers: the answer contract — observe records whether the final answer meets the question’s own format instructions; on carries them through every stage and enforces them |
MARINA_FORECAST_MARINA_URL / _KEY |
http://localhost:3300 |
where marina:<crew> analysts are asked |
MARINA_FORECAST_RESIDENTS |
off |
on: marina:<crew> and role:<r> analysts are residents asked in-process, and forecast <q> crew:<c> pattern:<p> (or role:<r>) runs a resident forecast |
MARINA_FORECAST_RESIDENT_CALLS / _TIMEOUT_S / _BUDGET_USD |
24 / 900 / none |
per resident forecast: resident calls, wall clock (each call’s deadline is what is left), and its own spend cap |
An analyst may be a crew: MARINA_FORECAST_ANALYSTS=marina:answerer asks the answerer crew on a
Marina server, so a crew in, say, the verification formation answers every run. The retriever
accepts openrouter-web:<model>[@exa|@native], sonar:<model>, tavily:<basic|advanced> (with
TAVILY_API_KEY), search[:<backends>] (see Search) and asof[:<providers>],
comma-separated to merge; an engine that fails while others answer is named in the round’s
warnings. asof is keyless and
date-strict: GDELT news, Wikipedia revisions, Hacker News and arXiv, each bounded to the
forecast’s cutoff instant, with news read from its Wayback capture at or before it. It is the
retriever to use for a backtest, since nothing published after the cutoff can reach the dossier;
see Search.
Lookups
Section titled “Lookups”Lookups are on by default wherever they can run: MARINA_FORECAST_LOOKUPS unset (or auto)
turns on every keyless lookup (polymarket, kalshi, fred, bls) plus odds when
ODDS_API_KEY is set; off turns them all off, and an explicit list picks some. Every surface
that forecasts — the forecast command, bun run forecast, POST /v1/forecast, and any benchmark
adapter built on the typed forecaster (FutureX, for one) — gets them through this one default, with
no adapter-specific code. Benchmarks that are not forecasting tasks (τ²-bench, SWE-bench) do not
use them. The same sources answer directly through the data command and the search room’s
markets / odds / series verbs.
Each lookup is a plain data request through the URL guard (no model spend). One that is not configured, fails or times out contributes nothing, and the answer records why. None of them reads anything published after the cutoff: each either reads values as of the cutoff or, when its source only knows current values, runs for a live cutoff only. The cutoff a lookup sees is never later than now: a question that closes next week is looked up as of now (a data source refuses a future as-of date, and FRED’s “today” is the US Central date — a UTC cutoff of now is read as FRED’s current date).
| Name | Source | Live cutoff | Past cutoff | Key |
|---|---|---|---|---|
polymarket |
Polymarket markets | current prices of open markets | the last price at or before the cutoff (CLOB price history); never the resolution | none |
kalshi |
Kalshi markets | bid/ask midpoint | the last hourly candle at or before the cutoff; never the result | none |
odds |
The Odds API head-to-head | current pre-game odds, margin removed, averaged across bookmakers | the snapshot at or before the cutoff (/historical, a paid-plan endpoint) |
ODDS_API_KEY |
fred |
FRED (St. Louis Fed) | latest observations | as published on the cutoff date (ALFRED vintages) | FRED_API_KEY for a past cutoff; keyless works live |
bls |
BLS public API | latest monthly observations | skipped (BLS serves only the latest revision) | BLS_API_KEY optional (raises the quota) |
manifold (opt-in) |
Manifold Markets (play money) | probability, volume and scheduled close of open markets; a multiple-choice market’s leading answers | yes/no markets only: the probability after the last trade at or before the cutoff (bet history, within 30 days) and the mana traded up to it; never the current probability, volume, close date or resolution; nothing when the history is missing | none |
Opt-in lookups run only when named (MARINA_FORECAST_LOOKUPS=auto,manifold, or
MARINA_ARENA_RESEARCH_LOOKUPS=fred,manifold); auto, all and markets never include them, so
adding one changes no default. A market created after the cutoff, or settled by then, contributes
nothing. Good Judgment Open is not offered: it has no public API, its terms forbid automated
collection, and its crowd forecasts are not published as of a past date, so no reading would be
safe for a backtest. A further crowd source implements the same lookup interface in
src/forecast/lookup-crowd.ts and adds one entry to OPT_IN_LOOKUPS in src/forecast/lookups.ts.
Markets are matched to the question by their words (and by date when both sides know one) with a
conservative threshold, so an unrelated market’s price is never shown. Sports odds show only games
that start after the cutoff, so every price is a pre-game price. Each lookup’s result is kept on
the answer with its mode (live or historical) and the time its data reflects; a number’s
anchor is kept as anchor (value, date, horizon in days and steps, spread).
How good is it?
Section titled “How good is it?”Measure it on your own questions: every answer is kept with its evidence, and a resolved question is scored once. The Social Simulation Arena guide shows how the same pipeline is backtested and scored forward, and what a backtest can and cannot claim. Treat a single forecast as a well-sourced opinion, not a guarantee.
If every run in a grounding follow-up fails, Marina retains the earlier usable cohort and records the failed attempts in groundingFollowUp.failedRuns. A failed single-run replacement also preserves its predecessor. The answer explicitly says that the retained predictions use the original dossier; the failed attempts never gain voting weight.
