Forecast
forecast takes a DataFrame of questions about the future and returns a forecast and a rationale for each row. You can ask what will happen, or the outcomes of a decision. The answer can take five shapes:
- Binary: probability (0 to 100) of YES/NO questions like "Will X happen?" (live example)
- Numeric: percentile estimates (p10 through p90) for continuous quantities like "What will the price/value/count be?" (live example)
- Date: percentile date estimates (p10 through p90, as
YYYY-MM-DD) for timing questions like "When will X happen?" (live example) - Categorical: one probability per outcome for a mutually exclusive, exhaustive set like "Which candidate wins: A, B, C or Other?" (probabilities sum to 100) (live example)
- Thresholded: one probability per threshold condition on a single outcome, like oil above $80 / $90 / $100 (probabilities non-increasing across the conditions)
Each live example is a forecast we ran and published, showing the result, the reasoning, and every source behind it. Published forecasts has more of them, grouped by type.
Categorical and thresholded are grouped modes: each row carries its own set of options, all forecast jointly with a single rationale. Both require effort_level="HIGH".
Any of these can be asked as a decision: give the options someone is choosing between and the same outcome is forecast under each one. The decision can be yours or anyone else's; see Forecasting a decision. A forecast can also be made conditional on a state of the world nobody chooses, such as an election result; see Conditional forecasts. Today a decision works for binary, numeric and date outcomes.
A first example
Most forecasts worth running are about a decision. Here is one: the same outcome, forecast under each option.
import asyncio
from pandas import DataFrame
from futuresearch.ops import decision
async def main():
result = await decision(
input=DataFrame([
{
"question": "How many sitting parliamentarians will be listed on ControlAI's campaign statement on December 31, 2028?",
"grant": ["$0 (no grant)", "$250k", "$1M"],
},
]),
context=(
"We are a family foundation deciding this quarter how much to give ControlAI. "
"The gift would be unrestricted and announced publicly, and no other funder is "
"waiting on our decision."
),
alternatives_field="grant",
forecast_type="numeric",
output_field="parliamentarians",
units="parliamentarians",
)
print(result.data[["question", "percentiles", "rationale"]])
asyncio.run(main())
Each option comes back with its own range, and the rationale explains the differences. Forecasting a decision has the details. The sections below cover the shapes a forecast can take.
Forecast types
Binary
Every operation is a coroutine. Run it with asyncio.run() as below, or await it directly in a Jupyter notebook. Later snippets on this page omit the wrapper for brevity.
import asyncio
from pandas import DataFrame
from futuresearch.ops import forecast
questions = DataFrame([
{
"question": "Will the US Federal Reserve cut rates by at least 25bp before July 1, 2027?",
"resolution_criteria": "Resolves YES if the Fed announces at least one rate cut of 25bp or more at any FOMC meeting between now and June 30, 2027.",
},
])
async def main():
result = await forecast(input=questions, forecast_type="binary")
print(result.data[["question", "probability", "rationale"]])
asyncio.run(main())
| Column | Type | Description |
|---|---|---|
probability |
int | 0 to 100, probability of YES resolution. Clamped to [3, 97]; even near-certain outcomes retain residual uncertainty. |
rationale |
str | Detailed reasoning with citations from web research |
Numeric
result = await forecast(
input=DataFrame([
{
"question": "What will the price of Brent crude oil be on December 31, 2026?",
"resolution_criteria": "Closing spot price of Brent crude oil (ICE) on Dec 31, 2026.",
},
]),
forecast_type="numeric",
output_field="price",
units="USD per barrel",
)
print(result.data[["price_p10", "price_p25", "price_p50", "price_p75", "price_p90"]])
| Column | Type | Description |
|---|---|---|
{output_field}_p10 … {output_field}_p90 |
float | 10th, 25th, 50th, 75th, and 90th percentile estimates. Monotonically non-decreasing: p10 ≤ p25 ≤ p50 ≤ p75 ≤ p90. |
units |
str | The units provided as parameter |
rationale |
str | Detailed reasoning with citations |
Schema: engine/services/forecast/data_types.py:83-105.
Date
result = await forecast(
input=DataFrame([
{
"question": "When will Anthropic IPO?",
"resolution_criteria": "Date Anthropic common shares first trade on a public exchange.",
},
]),
forecast_type="date",
output_field="ipo_date",
)
print(result.data[["ipo_date_p10", "ipo_date_p50", "ipo_date_p90", "rationale"]])
| Column | Type | Description |
|---|---|---|
{output_field}_p10 … {output_field}_p90 |
str | YYYY-MM-DD percentile estimates, or the literal "never" for percentiles in the indefinite future |
rationale |
str | Detailed reasoning with citations |
Schema: engine/services/forecast/data_types.py:63-80.
Categorical
For questions with a fixed set of mutually exclusive outcomes. Each row names an input column (via categories_field) holding that row's options as a JSON array of strings. The outcomes are researched together and forecast jointly, so the probabilities are coherent and sum to 100. Make the set exhaustive: add an explicit "Other" option when the listed candidates don't cover every possibility.
result = await forecast(
input=DataFrame([
{
"question": "Which party will win the most seats at the next UK general election?",
"resolution_criteria": "Party with the most seats in the House of Commons after the next general election.",
"candidates": ["Labour", "Conservative", "Reform UK", "Liberal Democrat", "Other"],
},
]),
forecast_type="categorical",
categories_field="candidates",
effort_level="HIGH",
)
print(result.data[["probabilities", "rationale"]])
| Column | Type | Description |
|---|---|---|
probabilities |
str | JSON object mapping each outcome to its probability (0 to 100). Mutually exclusive and exhaustive: the values sum to 100. |
rationale |
str | One joint rationale covering all outcomes, with citations |
Each row's categories_field column must hold 2 to 50 unique options. Categorical is HIGH effort only.
Schema: engine/services/forecast/data_types.py (build_grouped_response_schema).
Thresholded
For a single uncertain quantity, forecast the probability that it clears each of several thresholds. Each row names an input column (via thresholds_field) holding its conditions as a JSON array of numbers or strings, ordered from least strict to most strict. The conditions are nested, so the probabilities are non-increasing down the list.
result = await forecast(
input=DataFrame([
{
"question": "What will the price of Brent crude oil be on December 31, 2026?",
"resolution_criteria": "Closing spot price of Brent crude oil (ICE) on Dec 31, 2026.",
"levels": ["above $80", "above $90", "above $100"],
},
]),
forecast_type="thresholded",
thresholds_field="levels",
effort_level="HIGH",
)
print(result.data[["probabilities", "rationale"]])
| Column | Type | Description |
|---|---|---|
probabilities |
str | JSON object mapping each condition to its probability (0 to 100). Non-increasing across the listed order, since each condition is stricter than the last. |
rationale |
str | One joint rationale covering all thresholds, with citations |
Each row's thresholds_field column must hold 2 to 50 unique conditions in least-strict-to-most-strict order; the engine treats the labels as opaque and cannot reorder them for you. Thresholded is HIGH effort only.
Schema: engine/services/forecast/data_types.py (build_grouped_response_schema).
Forecasting a decision
decision() forecasts an outcome under each option of a choice. Each row states one outcome question, a column lists the options, and the outcome is forecast under each option as its own hypothetical. The options are researched together, so the differences between them reflect what the choice would cause, not three unrelated forecasts.
"If we give this organization nothing, $250k or $1M, how many lawmakers back its campaign by 2028?" is a decision. So is "if a city charges $9 or $15 to drive downtown, how much does traffic fall?". The decision can be yours, your company's, or someone else's: a competitor, a regulator, a government.
Three habits make these forecasts more useful.
- Put every option that is really on the table in one call, and include not acting unless it has been ruled out. Options priced in separate calls are not on a common scale.
- Ask for a quantity or a date rather than a cutoff: "when will it publish?" rather than "will it publish by 2028?". The distribution answers your cutoff and every other one.
- To look at several outcomes of the same decision, send several rows that share the same options. Outcomes of different types, a date and a number for instance, need separate calls today.
Sometimes the options come back level. That is an answer: this decision does not move this outcome, and the rationale says why. The same lever can matter for a nearer outcome, so ask for one before concluding the decision does not matter.
If the "if" is something someone decides, list the options and use decision(). If nobody decides it and you can only wait and see, use a conditional forecast. Forecasting the outcomes of a decision walks through all of this with worked examples, including what to do when a result looks wrong.
A probability
Which version of an offer on a house the seller accepts within three weeks: $566k or $571k, each with a 30-day or a 90-day settlement, or walking away. Two sentences of context, the listing and the buyers' position, and every variant in one call.
import asyncio
from pandas import DataFrame
from futuresearch.ops import decision
ABOUT_THIS_SALE = (
"A three-bedroom house in Ferntree Gully, in Melbourne's outer east, has been "
"listed at $575,000 since August 10, 2026 with no accepted offer, and the agent "
"says two other parties have inspected twice but not offered. The buyers are "
"pre-approved, have no property to sell, and would submit the offer on "
"September 28, 2026; accept means a signed contract within three weeks of that date."
)
offers = DataFrame([
{
"question": "Will the seller accept this offer within three weeks of receiving it?",
"offer": [
"$566,000 with a 90-day settlement",
"$571,000 with a 90-day settlement",
"$566,000 with a 30-day settlement",
"$571,000 with a 30-day settlement",
"Make no offer and walk away",
],
},
])
async def main():
result = await decision(
input=offers,
context=ABOUT_THIS_SALE,
alternatives_field="offer",
forecast_type="binary",
)
print(result.data[["question", "probabilities", "rationale"]])
asyncio.run(main())
| Option | Probability the seller accepts |
|---|---|
| $566,000 with a 90-day settlement | 48 |
| $571,000 with a 90-day settlement | 59 |
| $566,000 with a 30-day settlement | 55 |
| $571,000 with a 30-day settlement | 66 |
| Make no offer and walk away | 0 |
The extra $5,000 is worth 11 points and the faster settlement 7.
A number
A city introduces no charge, $9, $15, or $9 at peak only to enter downtown; by what percentage weekday entries fall in the first year. Two facts about the city, its cordon and its transit ridership; it also works with none.
ABOUT_THE_CITY = (
"The city is Chicago, and the zone is the central cordon bounded by Lake Michigan "
"on the east, Chicago Avenue on the north, Halsted Street on the west and "
"Roosevelt Road on the south, which about 360,000 vehicles enter on an average "
"weekday. Chicago Transit Authority buses and trains carried 319.2 million rides "
"in 2025 and Metra commuter rail carried 38.1 million, and all eleven Metra lines "
"end at four downtown terminals inside or on the edge of that cordon."
)
result = await decision(
input=DataFrame([
{
"question": "By what percentage will weekday vehicle entries into the charging zone fall during the first full year of operation, compared with the year before?",
"charge": [
"Introduce no charge, leave the zone as it is",
"Charge $9 per entry on weekdays between 6am and 8pm",
"Charge $15 per entry on weekdays between 6am and 8pm",
"Charge $9 at peak hours only, free at other times",
],
},
]),
context=ABOUT_THE_CITY,
alternatives_field="charge",
forecast_type="numeric",
output_field="entries_fall",
units="percent",
)
print(result.data[["percentiles", "units", "rationale"]])
| Option | p10 | p50 | p90 |
|---|---|---|---|
| Introduce no charge | -4.1 | 0.0 | 4.0 |
| $9 per entry, 6am to 8pm | 5.9 | 12.5 | 20.0 |
| $15 per entry, 6am to 8pm | 9.0 | 17.8 | 27.7 |
| $9 at peak hours only | 2.2 | 7.0 | 13.5 |
A date
The US requires incident reporting, an investigator and real-time monitoring of frontier runs, or it does not; when an agent first copies near-frontier weights out of a lab's control.
result = await decision(
input=DataFrame([
{
"question": "When will an AI agent first copy near-frontier model weights out of a lab's control?",
"policy": [
"Mandatory incident reporting, a statutory investigator, and real-time monitoring of frontier runs",
"No new law, and internal deployments go unreported",
],
},
]),
alternatives_field="policy",
forecast_type="date",
output_field="first_exfiltration",
)
print(result.data[["percentiles", "rationale"]])
| Option | p10 | p50 | p90 |
|---|---|---|---|
| No new law, and internal deployments go unreported | 2027 | February 2029 | 2034 |
| Mandatory incident reporting, a statutory investigator, and real-time monitoring of frontier runs | 2027 | July 2030 | 2038 |
The law buys about seventeen months. The numbers are the do-nothing and reporting-law columns of the published five-option run behind How We Can Prevent Rogue Agents; the snippet shortens its question and its option labels. See the forecast.
Whatever the outcome type, each option is a separate hypothetical, not a share of a single distribution. The values across options need not sum to 100 and need not be monotonic.
Intervention assumptions
A decision forecast has to assume something about what actually executing an alternative means: whether it is public, when it happens, and how everyone else reacts. Left unset, the defaults are that the decision is made soon, all downstream consequences count (including other agents' reactions), the world responds realistically in every branch, and the forecast reasons through mechanism rather than through what the choice would reveal.
Supplying intervention replaces those defaults wholesale, so state the full set of assumptions rather than just the one you want to change:
result = await decision(
input=DataFrame([
{
"question": "How many sitting parliamentarians will be listed on ControlAI's campaign statement on December 31, 2028?",
"grant": ["$0 (no grant)", "$250k", "$1M"],
},
]),
alternatives_field="grant",
intervention=(
"Assume the grant is announced publicly at the time it is made, that no "
"other funder backfills a declined application, and that the decision is "
"made within the next quarter."
),
)
Telling the forecaster what it cannot look up
A forecast about your own decision depends on facts the web does not have: who is deciding, your size, money and timeline, what constrains you, what you have tried, what happens if you do nothing. Put them in context, as plain dated facts a stranger could reason from. context is one string for the whole call, so you say it once however many rows you send.
result = await decision(
input=DataFrame([
{
"question": "How many sitting parliamentarians will be listed on ControlAI's campaign statement on December 31, 2028?",
"grant": ["$0 (no grant)", "$250k", "$1M"],
},
{
"question": "How many sitting parliamentarians will be listed on ControlAI's campaign statement on December 31, 2030?",
"grant": ["$0 (no grant)", "$250k", "$1M"],
},
]),
context=(
"We are a family foundation deciding this quarter how much to give ControlAI. "
"The gift would be unrestricted and announced publicly, and no other funder is "
"waiting on our decision."
),
alternatives_field="grant",
forecast_type="numeric",
output_field="parliamentarians",
units="parliamentarians",
)
context can also carry an instruction that applies to every row, such as which sources to prefer ("Focus on EU regulatory and diplomatic sources."). Never paste in numbers from an earlier forecast: they come back as a premise.
With nothing about you, the forecaster invents a typical decider and says so. A price-rise question with no context came back with a p10 to p90 of 660 to 22,000 subscribers for holding the price; two sentences (how many subscribers, how many cancel each month, how many join) narrowed it to 1,900 to 2,480. The guide shows what more context buys and what it does not.
Input columns
The input DataFrame should contain at minimum a question column. All columns are passed to the research agents and forecasters.
When forecasting prediction-market or contest questions, pass in the relevant fields from that platform (resolution criteria, close date, creation date, and current price) to ensure a high-quality forecast. See Prediction-market questions below for the per-platform field mappings, API endpoints, and a worked example.
| Column | Required | Purpose |
|---|---|---|
question |
Yes | The question to forecast |
resolution_criteria |
Recommended | Exactly how the outcome is determined, verbatim from the source when one exists: Polymarket description, Kalshi rules_primary + rules_secondary, Metaculus resolution_criteria + fine_print |
resolution_date |
Optional | When the question closes or resolves: Polymarket endDate, Kalshi close_time, Metaculus scheduled_close_time |
background |
Optional | Facts specific to this one row's question. Anything that applies to the whole call, including facts about you, goes in context. |
market_creation_date |
Recommended for prediction markets | When the market or question was created: Polymarket createdAt, Kalshi created_time, Metaculus created_at |
market_price |
Recommended for prediction markets | The current market price or community forecast, with its as-of date: Polymarket outcomePrices, Kalshi yes_bid/yes_ask mid, Metaculus community prediction |
| (options column) | For a decision | Named by alternatives_field. The options as a list of 2 to 50 strings or numbers. "Do it" and "don't" is the two-option case. |
Column names are not enforced. Research agents infer meaning from content, so a column named scenario instead of question works fine.
Self-contained questions need none of the optional columns; {"question": "When will Anthropic IPO?"} is a perfectly good row. The optional columns matter when the question has an external source of truth; include them whenever they exist.
Prediction-market questions (Polymarket, Kalshi, Metaculus)
When a question lives on a prediction market or forecasting platform, forecast quality depends on passing the market's own definition of the question. Fetch the market from the platform API and pass its fields through verbatim, without paraphrasing the resolution criteria. The clauses that look like boilerplate (official data source, delay handling, early-close conditions) are often the ones that decide the outcome.
| Input column | Polymarket (Gamma API) | Kalshi (events API) | Metaculus (API token required) |
|---|---|---|---|
question |
market.question |
event title + child market yes_sub_title |
title |
resolution_criteria |
market.description (already includes the fine print) |
rules_primary, plus rules_secondary when non-empty |
resolution_criteria + fine_print |
resolution_date |
endDate |
close_time |
scheduled_close_time |
background |
event title + event description |
event title, sub_title, category |
description |
market_creation_date |
createdAt |
created_time |
created_at |
market_price |
first element of outcomePrices, or the bestBid/bestAsk mid |
mid of yes_bid_dollars/yes_ask_dollars (dollar strings; last_price_dollars goes stale on thin markets) |
community prediction |
Endpoints:
- Polymarket:
GET https://gamma-api.polymarket.com/events?slug={event-slug}returns the event with a nestedmarkets[]array. No auth. - Kalshi:
GET https://external-api.kalshi.com/trade-api/v2/events/{EVENT_TICKER}?with_nested_markets=true, where the event ticker is the last URL path segment, uppercased. No auth. - Metaculus:
GET https://www.metaculus.com/api/posts/{id}/with anAuthorization: Token <token>header (free account required; unauthenticated requests return 403).
Most Polymarket and Kalshi event URLs map to many child markets (one per candidate, threshold, or date bucket), so make sure you build a row from the specific child market you mean to forecast.
import json
from datetime import date
import httpx
from pandas import DataFrame
from futuresearch.ops import forecast
event = httpx.get(
"https://gamma-api.polymarket.com/events",
params={"slug": "world-cup-winner"},
).json()[0]
market = event["markets"][0] # pick the child market you want
yes_price = json.loads(market["outcomePrices"])[0]
questions = DataFrame([{
"question": market["question"],
"resolution_criteria": market["description"], # verbatim, don't paraphrase
"resolution_date": market["endDate"],
"background": event["title"],
"market_creation_date": market["createdAt"],
"market_price": f"{yes_price} (Yes, as of {date.today()})",
}])
result = await forecast(input=questions, forecast_type="binary")
For screening many markets at once, see Find Profitable Prediction Market Trades.
Parameters
| Name | Type | Description |
|---|---|---|
input |
DataFrame | Rows to forecast, one question per row |
forecast_type |
"binary" | "numeric" | "date" | "categorical" | "thresholded" |
Type of forecast to produce |
effort_level |
"LOW" | "HIGH" | None |
See Effort and cost below. Defaults to HIGH, whatever the row count. categorical, thresholded, and any conditional forecast require "HIGH". |
context |
str | None | Optional. What is true for the whole call: facts about who is deciding and their situation, and any instructions that apply to every row. See above. |
output_field |
str | None | Name of the quantity being forecast (required for numeric and date, e.g. "price", "launch_date") |
units |
str | None | Units for the forecast (required for numeric, e.g. "USD per barrel", "billions USD") |
categories_field |
str | None | Name of the input column holding each row's outcomes as a JSON array of strings, 2 to 50 unique options (required for categorical) |
thresholds_field |
str | None | Name of the input column holding each row's threshold conditions as a JSON array of numbers or strings, least strict to most strict (required for thresholded) |
condition |
str | None | A single condition string applied to every row, making the forecast conditional on it. Mutually exclusive with condition_field. |
condition_field |
str | None | Name of an input column holding each row's own condition. Mutually exclusive with condition. |
session |
Session | Optional, auto-created if omitted |
decision() takes the same input, context, forecast_type (binary, numeric or date), output_field, units and session, plus:
| Name | Type | Description |
|---|---|---|
alternatives_field |
str | Required, keyword-only. Name of the column holding each row's alternatives as a JSON array |
intervention |
str | Optional. The intervention assumptions (see above) |
The forecast_type enum is defined in engine/services/forecast/data_types.py (ForecastType.BINARY | NUMERIC | DATE | CATEGORICAL | THRESHOLDED); effort_level in the same file (ForecastEffortLevel.LOW | HIGH).
Effort and cost
effort_level trades cost for accuracy:
| Effort | Per-row time | Per-row cost |
|---|---|---|
LOW |
~3 to 5 min | $0.25 to $3 |
HIGH (default) |
~5 to 10 min | $2 to $5 |
The ranges overlap because cost scales with how hard the question is as well as with effort. See pricing for current costs.
HIGH is the default, whatever the row count. When effort_level=None the engine resolves to HIGH (engine/services/forecast/effort.py:23-27). Batch size does not change it, so a 200-row forecast left on the default is charged at the high rate for every one of the 200 rows. Pass effort_level="LOW" explicitly when you are sweeping a large list and want the cheaper tier.
CATEGORICAL and THRESHOLDED forecasts, and any conditional forecast, require HIGH and reject LOW.
See the guide for worked examples.
Track record
The forecaster behind this API competes in Metaculus's FutureEval tournament, against human forecasters in the Metaculus Cup, and on ForecastBench. All are live standings, so those links carry the current positions. We iterate on it against BTF-3, our pastcasting benchmark, where it holds the best pooled score; Iterating on a forecaster explains the approach. The methodology is published in the BTF benchmark paper, the question generation and resolution paper, and the Strategic Reasoning paper, with questions, resolutions, and agent rationales released as Hugging Face datasets for BTF-2 and BTF-3. For a real-world test of the same methodology, see how an S&P 500 paper portfolio built from FutureSearch forecasts has performed.
World model
Forecasts do not run in isolation. After the research agents finish, a world-modeling pass reconciles each answer against the shared drivers it has learned across thousands of forecasts. The pass improved all nine base forecasters we tested on BTF-3 (four of them significantly), keeps repeated questions stable, and is always on. The size of its repairs also turns out to predict forecaster accuracy without waiting for resolutions.
Conditional forecasts
A conditional forecast is for a premise nobody chooses: a state of the world you can only observe, usually far off or outside anyone's control. "If the Democrats win the presidency in 2028, what will GDP growth be in 2030?" is one. If the premise is something someone decides, including a company, a regulator or a government, use Forecasting a decision instead.
Keep forecast_type set to whatever the outcome calls for and supply the condition separately. Both branches are forecast together, so the estimates share one view of how the condition bears on the outcome.
Supply the condition in one of two mutually exclusive ways:
condition: a single condition string applied to every row. Use it for a one-off question, or to ask the same conditional across a list of entities (one shared "if A" mapped over each row).condition_field: the name of an input column holding each row's own condition, when rows carry distinct conditions.
The result keeps the outcome's normal columns and produces each of them a second time, suffixed _given_condition (the world where the condition holds) and _given_not_condition (where it does not), alongside the usual rationale. Conditional forecasts are HIGH effort only.
result = await forecast(
input=DataFrame([
{
"question": "What will Nvidia's one-day stock return be the day after its next earnings report?",
},
]),
forecast_type="numeric",
output_field="stock_return",
units="percent",
condition="Nvidia's next quarterly revenue comes in above $80.07B",
effort_level="HIGH",
)
print(result.data[["stock_return_p50_given_condition", "stock_return_p50_given_not_condition", "rationale"]])
Conditioning on a decision also imports whatever making it would reveal about the world, which is not what someone facing the decision is asking. Forecasting a decision reasons through what each option would cause.
Via MCP
MCP tool: futuresearch_forecast
| Parameter | Type | Description |
|---|---|---|
data |
list[object] | Inline data as a list of row objects |
artifact_id |
string | Alternatively, an artifact ID from a previous upload |
forecast_type |
"binary" | "numeric" | "date" | "categorical" | "thresholded" |
Type of forecast to produce |
effort_level |
"LOW" | "HIGH" |
Optional. Defaults to HIGH. categorical, thresholded, and any conditional forecast require HIGH. |
context |
string | What is true for the whole call: facts about who is deciding and their situation, and any instructions that apply to every row. |
output_field |
string | Name of the quantity (required for numeric and date) |
units |
string | Units (required for numeric) |
categories_field |
string | Name of the column holding each row's outcomes as a JSON array of strings (required for categorical) |
thresholds_field |
string | Name of the column holding each row's threshold conditions as a JSON array, least strict to most strict (required for thresholded) |
condition |
string | A single condition string mapped over every row, making the forecast conditional on it (mutually exclusive with condition_field) |
condition_field |
string | Name of the column holding each row's own condition (mutually exclusive with condition) |
MCP tool: futuresearch_decision
| Parameter | Type | Description |
|---|---|---|
data |
list[object] | Inline data as a list of row objects |
artifact_id |
string | Alternatively, an artifact ID from a previous upload |
alternatives_field |
string | Name of the column holding each row's alternatives as a JSON array |
forecast_type |
string | "binary" (default), "numeric", or "date": the outcome type forecast under each alternative |
output_field |
string | Name of the quantity being forecast (required for numeric and date) |
units |
string | Units for the outcome (required for numeric) |
context |
string | What is true for the whole call: facts about who is deciding and their situation, and any instructions that apply to every row. |
intervention |
string | Optional intervention assumptions |
Provide either data or artifact_id, not both. See the MCP server reference for the rest of the lifecycle (progress, results, status).
Related docs
Guides
- Turn Claude into an Accurate Forecaster: binary, numeric, and date forecasting for any question.
- Forecasting the outcomes of a decision: a decision, yours or anyone else's, with worked examples.
- Forecast Outcomes for a List of Entities: one outcome per row across a list.
- Forecast Categorical and Threshold Questions: the two grouped modes.
- Find Profitable Prediction Market Trades: Polymarket and Kalshi screening.
- Published Forecasts: real results, and how to give one a permanent public page.
- Forecast Conditional Scenarios:
conditionandcondition_field.
Case studies
- Which CEO Replacement Maximizes Share Price: three calls price every option a board has, with a named-candidate ladder against an unnamed control.
- Forecast a Decision: Grant Funding at Three Levels: the same outcome priced under three funding levels for 18 proposals.
- Forecast Anthropic and OpenAI IPOs: Dates and Valuations: date mode and numeric, high effort.
- Forecast a SpaceX Sum-of-the-Parts Valuation: numeric, multi-segment.
- Forecast AI Researcher Seed Valuations: numeric across 116 entities.