Decision
decision forecasts the outcome under each alternative of a choice you control. Each row states one yes/no outcome question and lists that decision's mutually exclusive alternatives; the outcome is then forecast under each alternative as its own hypothetical.
"If I fund this organization at $0 / $300k / $2M, will it ship its study by 2027?" is a decision. So is "if we move all-team offsites from every 3 months to every 2 or every 1, will ARR grow more than 200% year on year?" In both cases the "if" clause is something you get to choose.
All alternatives are researched jointly, so the differences between them reflect the decision's real effect rather than three unrelated forecasts. Decisions always run at HIGH effort.
Decision or conditional forecast?
This is the distinction that matters most, and it is easy to get wrong.
forecastwith aconditionanswers the correlational question: "in worlds where X happens, what else is true?" Observing X may be evidence about the kind of world you are in.decisionanswers the causal question: "what does choosing X actually cause?" It reasons through mechanism, never through what your choice would reveal about the world.
Use decision whenever the "if" clause is a choice you control. Use a conditional forecast when the "if" is something you merely observe.
Example
Every operation is a coroutine. Run it with asyncio.run() as below, or await it directly in a Jupyter notebook.
import asyncio
import json
from pandas import DataFrame
from futuresearch.ops import decision
decisions = DataFrame([
{
"question": "Will the study be published in a peer-reviewed journal before 2028-01-01?",
"grant_size": json.dumps(["$0 (no grant)", "$300k", "$2M"]),
"resolution_criteria": "Resolves YES if the study appears in a peer-reviewed journal before January 1, 2028.",
"background": "The lab has published two related papers since 2024 and currently has no dedicated funding for this work.",
},
])
async def main():
result = await decision(
input=decisions,
alternatives_field="grant_size",
)
print(result.data[["question", "probabilities", "rationale"]])
asyncio.run(main())
Output columns
| Column | Type | Description |
|---|---|---|
probabilities |
str | JSON object mapping each alternative to the outcome's probability (0 to 100) given that alternative is chosen |
rationale |
str | Reasoning with citations from web research, covering all alternatives together |
The probabilities need not sum to 100 and need not be monotonic. Each one is a separate hypothetical, not a share of a single distribution. Funding at $2M can easily be less effective than $300k, and the numbers will say so if the research supports it.
Input columns
| Column | Required | Description |
|---|---|---|
question |
Yes | The yes/no outcome question |
| (alternatives column) | Yes | Named by alternatives_field. A JSON array of 2 to 50 unique numbers or strings. A binary "do X / don't do X" decision is the 2-alternative case. |
resolution_criteria |
Recommended | What counts as YES |
resolution_date |
Recommended | When the outcome resolves |
background |
Recommended | Context the forecaster should start from |
All columns in the table are used in the forecast, so any extra context you supply is read.
Parameters
| Name | Type | Description |
|---|---|---|
input |
DataFrame | UUID | TableResult | The input table |
alternatives_field |
str | Required, keyword-only. Name of the column holding each row's alternatives as a JSON array |
context |
str | Optional batch-level context applied to every row. Leave unset when rows are self-contained |
intervention |
str | Optional. The intervention assumptions (see below) |
session |
Session | Optional, auto-created if omitted |
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=decisions,
alternatives_field="grant_size",
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."
),
)
Because only one branch is ever actually chosen, decision forecasts are not scored. There is no ground truth for the alternatives you did not take, so these do not appear on our public track record the way ordinary forecasts do.
Via MCP
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 |
context |
string | Optional table-level context for 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
- forecast: ordinary and conditional forecasts, including the world-modeling pass.
- World modeling: the shared model of the world every forecast draws on.