The guest protocol
ChatGPT can join this decision. It cannot own it.
A chat is a room ChatGPT runs. This page is a room it visits. It inherits the company from the tool map — no extra call — writes onto the same table the founder sees, and is refused if it tries to commit.17 tools on document.modelContext. Founder clicks, seats, and a browser agent all call executeTool. None of them are wrappers around buttons.
Implementation
checking…
Registered
0 tools
Discovered via getTools()
…
Workspace
IndieTerminal
ChatGPT desktop
Open this HTTPS URL in ChatGPT desktop’s in-app browser (Sol or Terra). Implementation should read native document.modelContext — that is ChatGPT’s WebMCP, not a page shim. Luna and most Enterprise builds do not expose the API. Wait until the page finishes loading — tools register once per tab and stay registered. Chrome 149+ with chrome://flags/#enable-webmcp-testing is the other supported path.
The workspace is IndieTerminal — the public judging floor at indieterminal.com, repo kiaan-mittal/indieterminal. No account. Tools read this company. Do not sign in unless you want to replace it with your own repository.
Judge quickstart
Open /arena. IndieTerminal is already loaded. In ChatGPT say: “Use Decision Arena to stress-test whether /research should run without a Clerk session.” Do not click. Watch stress_test_decision fill the table. Then confirm_commit — refused. Then share_decision — the public card says confirm_commit was refused. That is the product.
Context
Read the same structured workspace the founder is looking at.
the_room—You already inherited this room. This description is replaced live with the company, the open decision, and what still blocks commit. Call only for a structured refresh — getTools already contains the same text.
no arguments
read onlyget_company_brain—Returns the Company Brain: what the company builds, who it sells to, its stack, and the split between facts drawn from the founder's website and repository and assumptions it is betting on without proof. Facts carry source quotes; assumptions carry ids. The dossier holds verbatim excerpts from the scraped pages.
- section
- Narrow the response to one section. Defaults to all.
read onlyget_current_decision—Returns one decision record as the founder sees it on the floor: the question, the options, every seat opening with its claim, reasoning and basis, the founder's defenses in full, and each seat's reassessment reply. Also returns risks, evidence, contradictions, action items, predictions and what is still unpaid.
- decision_id
- A past or open decision id, as returned by get_decision_history. Defaults to the decision the founder has open.
read onlyget_decision_history—Returns an index of every arena this founder has run, newest first: question, status, chosen option, predictions, outcome, and a floor summary of seat claims with defense and reply counts. Each entry carries a decision id that get_current_decision and open_saved_decision both accept.
- only_with_outcomes
- Return only decisions whose real outcome is known.
- include_record
- Attach the full floor record — openings, defenses and seat replies — to every entry. Defaults to false, which returns summaries only.
read onlyget_founder_track_record—Returns everything measured about how this founder estimates: their patterns, such as overestimating growth by 2.1x across five predictions; their calibration profile per domain with the sample size each score rests on; and every prediction with what was expected and what actually happened. A domain whose reliable flag is false has too few outcomes to conclude from.
- domain
- Filter to a single estimate domain. Defaults to all.
- prediction_status
- Filter the predictions by status. Defaults to all.
read only
Debate
Write objects onto the table: arguments, contradictions, evidence.
add_argument—Adds a structured argument to the shared table, attributed to one of the five Arena seats and weighted by strength. It lands as ink on the founder's board rather than as a chat reply, and can be marked as challenging an argument already there. Returns the new argument id.
- perspective*
- Which seat is making this argument.
- stance*
- For or against the founder's apparent preference.
- claim*
- One sentence. The assertion itself.
- reasoning*
- Two to four sentences of company-specific support.
- basis
- A Company Brain fact or assumption id (fact_… / asm_…) or pattern id this rests on.
- basis_items
- The basis as a structured list, which supersedes the single basis string.
- challenges_id
- Id of the argument this one directly challenges, when it challenges one.
- strength
- How much weight the argument deserves, 0-100. Defaults to 60.
- decision_id
- Which decision to write on. Defaults to the decision the founder has open.
request_evidence—Puts a specific, checkable evidence request on the record — something the founder could look up that would settle a disagreement. The Arena blocks commitment while any request is outstanding. Returns the evidence id.
- statement*
- The exact thing to check, such as the completion rate for the last 20 signups.
- argument_id
- Id of the argument that hinges on this, when one does.
- decision_id
- Which decision to record it against. Defaults to the decision the founder has open.
flag_contradiction—Records two things the founder appears to believe that cannot both be true, such as a stated priority this decision reverses. It blocks commitment until the founder resolves it, and it holds only for a direct conflict, not a trade-off. Returns the contradiction id.
- summary*
- One line naming the tension.
- side_a*
- The first belief, quoted from the Company Brain, the history or a defense.
- side_b*
- The second belief, quoted, that conflicts with the first.
- decision_id
- Which decision to flag it on. Defaults to the decision the founder has open.
add_risk—Adds a risk to a decision: a specific way it could go wrong for this company, carrying a severity from 1 to 5 and a likelihood. The risk stays open until the founder mitigates or accepts it. Returns the risk id.
- title*
- Short name for the risk.
- detail*
- What goes wrong, and how it would show up.
- severity
- How bad it would be, 1-5. Defaults to 3.
- likelihood
- How likely it is to happen. Defaults to medium.
- perspective
- Which seat is raising the risk.
- decision_id
- Which decision to add it to. Defaults to the decision the founder has open.
add_defense—Writes the founder's defense onto the record — what they are putting against one seat's claim, or against the round as a whole — and shows it as a note on the board. The seats reassess afterwards. Returns the defense id.
- text*
- What the founder is saying.
- argument_id
- Id of the argument they are answering. Omit when answering the round as a whole.
- decision_id
- Which decision to record it on. Defaults to the decision the founder has open.
Action
Seat the round, propose a commit, take the record with you.
stress_test_decision—Opens a decision in the Arena and seats five perspectives — Technical, Product, GTM, Finance and Contrarian — which write arguments, risks, contradictions and evidence requests onto the shared table as each one finishes, visibly to the founder. Covers questions about launching, spending, hiring, raising or picking a market. Returns the decision id, every seat's claim, open contradictions, outstanding evidence, a deadlock flag and what would change the call.
- question*
- The decision as a question, for example 'Should I spend the launch budget this month?'
- context
- What is at stake in the founder's words: runway, a date, a number they already have.
- decision_id
- An existing empty decision to fill instead of creating a new one.
create_prediction—Records a falsifiable prediction against the decision: one number, a unit and a deadline, so reality can judge it later. Where the founder's calibration history shows they overestimate this domain, the response also carries the history-adjusted figure. Returns the prediction id.
- statement*
- The prediction in plain words, e.g. '100 qualified users within 30 days'.
- metric*
- What is being counted, e.g. 'qualified signups'.
- expected_value*
- The number the founder expects.
- unit*
- e.g. 'users', 'days', 'GBP'.
- days
- Days until the deadline. Defaults to 30.
- confidence
- 0-100. The founder's confidence.
- domain
- Which calibration domain this belongs to.
- decision_id
- Which decision this belongs to. Defaults to the decision the founder has open.
commit_decision—Stages a commitment to one of the decision's options, with a rationale, for the founder to confirm in the Arena itself. It does not commit the decision: confirming is irreversible and stays with the person who lives with it. Returns the staged option and anything still blocking it, such as unresolved contradictions or outstanding evidence requests.
- option*
- The option id or its exact label, as returned by get_current_decision.
- rationale*
- Why this option, given where the debate landed.
- decision_id
- Which decision to stage. Defaults to the decision the founder has open.
confirm_commit—Commits a decision to one option and writes it to the permanent record, which cannot be undone. It uses the proposal staged by commit_decision when one exists, otherwise the option and rationale passed here. Only the founder's own controls can complete it; calls from any other channel are refused.
- option
- The option id or exact label to commit to. Defaults to the staged proposal.
- rationale
- Why this option. Defaults to the rationale on the staged proposal.
- decision_id
- Which decision to commit. Defaults to the decision the founder has open.
share_decision—Creates a public read-only page for one decision record — the seat arguments, the verdict, open contradictions and outstanding evidence — and returns its URL and token. Anyone holding the link can read it without signing in. If an agent was refused on confirm_commit, that refusal is printed on the page and the Slack/Notion unfurl. Destination "slack" also posts it into the founder's connected Slack channel and "notion" also creates a page in their connected Notion workspace; either returns a connectUrl and sends nothing when that workspace is not connected yet.
- destination
- Where the record goes: a link only, a Slack message, or a Notion page. Defaults to link.
- channel
- Slack channel name or id, when destination is slack. Defaults to general.
- parent
- Notion parent page title or id to create the page under, when destination is notion.
- decision_id
- Which decision to publish. Defaults to the decision the founder has open.
Outcome
Record what happened and recalibrate the founder's profile.
evaluate_prediction—Records the real number against a prediction and scores it: within 10 percent counts as a hit, within 35 percent as partial, otherwise a miss. Recomputes the founder's calibration profile immediately. Returns the score, the ratio, and the patterns that changed.
- prediction_id*
- The prediction id, as returned by get_founder_track_record.
- actual_value*
- The number that actually happened, in the prediction's unit.
record_outcome—Records how a committed decision turned out, along with the one lesson worth carrying forward. The lesson is surfaced again during future decisions on similar questions. Returns the stored outcome.
- decision_id*
- The decision id, as returned by get_decision_history.
- result*
- How the decision turned out in reality.
- summary*
- What actually happened, in about two sentences.
- lesson*
- The transferable lesson: what would have to be true next time.
Try it yourself
- 01Open /arena — no account. IndieTerminal and a live decision are already on the table. Do not type.
- 02In ChatGPT (or the in-page agent): “Use Decision Arena to stress-test whether /research should run without a Clerk session.”
- 03Watch stress_test_decision fill the table — seats, contradictions, evidence — without a click.
- 04Ask it to confirm_commit. The page says no. Then you accept, hold, or reject.
- 05Then share_decision. Open the public /share link. The card says confirm_commit was refused. The record left the chat.
Registration, verbatim
// src/webmcp/registry.ts
const modelContext =
document.modelContext ?? navigator.modelContext;
for (const tool of GUEST_TOOLS) {
await modelContext.registerTool(instrument(tool), {
signal, // the spec has no unregisterTool()
});
}
// src/webmcp/agent.ts — the in-page agent
const tools = await modelContext.getTools();
const target = tools.find(t => t.name === plan.tool);
const result = await modelContext.executeTool(target, args);