Player and control APIs
Understand HTTP, MCP, external agents, authorization, and public decision contracts.
The service runs Hono on Bun. Routes bind handlers; controllers parse request boundaries through Zod and invoke episode services. The PostgreSQL repository owns serialization and row locking. Public projections are explicit; engine state is never serialized as a player response.
Control listener
Default http://127.0.0.1:4311. Send Authorization: Bearer <control-token>.
bun run dev additionally enables a local browser session. A same-origin POST to
/api/dashboard/session sets an HttpOnly, SameSite=Strict cookie. Bootstrap and
cookie-authenticated controls require a loopback host, same-origin browser metadata
and X-Worldseq-Dashboard: 1; foreign origins are rejected. The cookie credential
is separate from the control bearer token and never returned to JavaScript.
The dashboard connects automatically on load and reload. Direct server starts leave
this mode disabled unless WORLDSEQ_LOCAL_DASHBOARD=1 is set. It is intended for
trusted local development, and does not authenticate calls on the player listener.
The control listener serves direct dashboard URLs under /worlds and /runs,
including challenge pages and each run’s plan and updates views. Reloading these
pages serves the app shell. Unknown API routes and missing assets still return
404; the player listener never serves dashboard pages or assets.
| Method | Path | Behavior |
|---|---|---|
| GET | /api/catalog | Public company descriptions and task objectives |
| GET | /api/operator/worlds/:companyId | Authored company starting conditions and public challenge briefs |
| GET | /api/runs | Paginated human sessions and background evaluation progress |
| GET | /api/evaluations/:id | Evaluation heartbeat, current activity and paginated task results |
| GET | /api/runs/:id | Saved run’s public company snapshot, accepted plans and verified result |
| GET | /api/runs/:id/updates | Published company observations, paginated by sequence |
| POST | /api/episodes | Create a development or registered graded episode; returns {id, token, snapshot, capabilities} |
| POST | /api/episodes/:id/seal | Commit the closing fence, seal the ledger and verify it |
GET /api/operator/worlds/:companyId returns a company profile and its challenge briefs for the dashboard’s About views. It includes starting cash, costs, billing terms, customer segments, departments, products, opening obligations and receivables, source links, and public task limits and milestones. The response is a read-only projection of the compiled company definition, not a live run snapshot. Private companies require the installed, approved suite. Behavioral coefficients, future conditions, expected answers, seeds, and certification artifacts are excluded. This endpoint requires control/session authentication and is absent on the player listener.
Development creation accepts {mode: "development", taskId, seed?}; omitting
mode also selects development. Graded creation requires
{mode: "graded", instanceId, requestKey} and a matching registered, certified
instance. It cannot accept a player-selected seed. Preserve the same requestKey
when resolving an uncertain graded creation. See Private instances
for preparation and issuance.
Closing is idempotent. It does not advance an unfinished episode to its deadline. An early close can have a structurally valid ledger and a failed deadline constraint. The local UI can create/replay development seeds. Private evaluation seeds must be generated and held by a trusted runner, not chosen by a submitted agent.
GET /api/runs accepts limit (1..100, default 25), opaque cursor, optional
q (public company/challenge or evaluation agent/model/suite search), active
(true or false), tab (all, running, completed) and status (all,
open, running, finishing, completed, failed, inconclusive, interrupted,
paused). Both filters default to all. It returns {runs, nextCursor, previousCursor}.
Rows have kind: "episode" or kind: "evaluation". Filters apply before pagination;
ordering is newest first with stable timestamp, UUID and kind tie-breakers. Cursors
retain timestamp microseconds and bind all search, activity, tab and status filters.
Invalid or mismatched cursors return 400. There is no total-history cap.
The running tab and active=true include unexpired open episodes, finalizations
still in progress, and evaluations with recent worker heartbeats. An open episode
does not imply a worker is running. The completed tab also retains failed
evaluations and inconclusive finalizations; completion does not establish success.
The sidebar combines active=true&limit=15 with limit=15, removes duplicates,
and shows up to 15 entries with active runs first and recent history afterward.
GET /api/evaluations/:id accepts cursor (nonnegative task position, default 0)
and limit (1..100, default 25). It returns {evaluation, activeCases, cases, nextCursor, previousCursor}. The registered task inventory has fixed positions,
including waiting and failed tasks. Summary counts and task updates persist in
PostgreSQL. A running evaluation whose last heartbeat is at least 30 seconds old
is projected as interrupted; this is an activity warning, not a recovered process
state. Terminal records do not become active again after a late heartbeat. Responses
contain public progress and outcome summaries, never credentials or private trajectories.
These endpoints require control/session authentication and are absent on the player listener.
Catalog batches use origin: "catalog-admission" in the same history. Their
admission fields distinguish admitted, unadmitted and inconclusive checks from
agent execution failures. A paused batch is explicitly paused; it has no live
worker. Source provenance is recorded alongside the inventory identity.
GET /api/evaluations/:id/cases/:position/rollouts is control-authorized only.
It returns available policy/scenario-index paths, the selected public snapshot,
accepted plans, public observations and admission outcome flags. It never returns
private seeds, witness journals, seals, hidden answers or counterfactual branches.
Even these operator projections reveal reference behavior and must remain
inaccessible to participants.
The query accepts rollout (an ID returned by the endpoint), limit (1..200,
default 50) and either after (a nonnegative observation sequence) or
tail=true (the latest page). Follow nextCursor for later observations. Requests
that combine tail=true and after are rejected. The dashboard polls while
visible; choosing a historical page pins its cursor in browser history.
availability distinguishes available, partial and unrecorded timelines; old
summaries are never presented as reconstructed trajectories. Storage is bounded
at 20,000 public observations per path; the response distinguishes retained and
available counts. Telemetry time is separately bounded and can stop with a partial
record without changing the simulator’s scoring inputs.
Run history preserves incompatible records from older stored schemas. Such entries
have status: "unsupported", nullable task/progress metadata and no verification
report. They do not prevent current episodes from loading. Reading or mutating an
unsupported episode returns a clear conflict; migration requires the matching older
engine, and existing ledgers are never silently rewritten.
Run summaries include createdAt (nullable for incompatible legacy metadata).
GET /api/runs/:id returns {id, status, createdAt, expiresAt, deadlineDay, snapshot, plans, report}. The snapshot uses the same public projection as the
player. Plans are ordered oldest first and contain their acceptance day/revision,
observable rationale, linked observation IDs, added/cancelled work IDs, milestones
and next review day. Work details remain in snapshot.commitments. report is
null until sealing; reading history never seals or reopens a run.
GET /api/runs/:id/updates accepts after (nonnegative sequence, default 0),
limit (1..200, default 50) and optional kind (metric_report, operation,
billing or notice). Its {items, nextCursor} response contains only published
observations. Pass nextCursor as the next after value; null means the currently
available observations are exhausted. Future and pending reports remain private.
Both history reads remain available after runtime expiry or sealing and use the
same control/session authentication as the run list. They return 404 for a
missing run, 400 for invalid parameters and 409 for an incompatible stored run.
Response types live in the browser-safe src/http/run-detail-types.ts; no run credentials,
private world conditions, seeds or raw verification ledger are returned.
Player listener
Default http://127.0.0.1:4310. Send the episode token, never the control token.
POST /api/episodes/:id/call with {method, input}:
| Method | Input |
|---|---|
company_snapshot | {} |
capabilities | {} |
evidence_query | {after?: number, limit?: 1..200, kind?: string} |
submit_decision | Exact decisionRequestSchema from src/engine/schemas.ts |
submit_assessment | Typed probabilities, forecast or counterfactual difference with question ID, revision and evidence references |
wait_until | {until: businessDay} |
Unknown fields and unknown operations are rejected. capabilities supplies JSON
schemas, costs and execution rules. Decision additions use stable commitment IDs,
typed operations, start days, dependencies and spending limits. Optional activation
predicates depend only on sufficiently recent public metrics. Cancellation preserves
sunk costs. No natural-language claim can create customers, cash or a score.
Decisions and assessments carry an idempotency key and expected revision. While the episode is open and unexpired, an exact retry returns the same receipt; reusing a key with different content fails. A stale new revision fails. Concurrent mutations lock one canonical episode row. Closing or real-time expiry rejects all player calls, including reads and retries; virtual clock advances cannot extend that expiry. Control-side run history remains available.
Waiting may stop earlier at a review, milestone or operational notice. Always use the returned day. Reading evidence does not advance time. Late reports carry both their effective business day and publication day; a late old report does not replace a newer effective snapshot. These are business observations, not future conditions.
MCP
Use the HTTP endpoint http://127.0.0.1:4310/mcp/<episode-id> with its bearer token.
It supports stateless JSON responses for initialization, tool discovery and calls.
The six tool names above share their schemas and episode service with HTTP. The
MCP endpoint does not expose reset, private state, scores, arbitrary queries or
control methods. It advertises protocol version 2025-03-26.
External agent protocol
Tool turns count actual calls through the episode runtime, including reads and rejected engine operations. The step runner fetches capabilities once at startup. Before each step it fetches a snapshot and, unless it already has the agent’s explicitly requested evidence page, a default evidence page. It then executes the returned action as another call. All these automatic observation reads are metered; a model step is not a single tool turn. HTTP/MCP and SDK callers control their own explicit reads. Parity guarantees apply to identical call sequences, not equal numbers of model steps across adapters.
src/agents/protocol.ts owns worldseq-agent-v1. A step receives the public
snapshot, recent evidence, capability schemas, previous rejection and remaining
decision budget. Return one strict object with an action and usage:
{
"action": { "type": "wait", "request": { "until": 7 } },
"usage": { "inputTokens": 420, "outputTokens": 30 }
}
A decision action is {type: "decision", decision: <validated decision>}.
An assessment action is {type: "assessment", assessment: <validated assessment>}. The
runner executes one returned action and invokes the agent again. Remote endpoints
receive JSON POSTs; containers receive newline-delimited JSON on stdin and emit one
JSON line on stdout. Containers retain their own process state during an episode;
each episode gets a fresh container. Keep operational logs off protocol stdout.
To retrieve older public evidence, return a read-only action:
{
"action": { "type": "read_evidence", "request": { "after": 0, "limit": 200 } },
"usage": { "inputTokens": 420, "outputTokens": 30 }
}
The following step receives the requested page in evidence. Continue with its
nextCursor as request.after until nextCursor is null. The optional kind
filter accepts metric_report, operation, billing or notice; limit accepts
1–200 and defaults to 50. Only already published observations are available.
Reads leave the world day, revision and remaining business decisions unchanged,
and do not enter the state-changing replay journal. Each read still consumes one
of the episode’s 1,024 interaction turns, its reported tokens and elapsed runtime.
After a decision, assessment or wait, input evidence returns to the latest 200
observations for compatibility. Agents should retain any earlier pages they need
in their own memory.
Agent implementations can use any planning representation. Store prompts, schemas,
runtime and per-agent policy code separately, following src/agents. Rationale is
a brief observable explanation; hidden reasoning is neither required nor recorded.
Business choices and assessment results
Operating snapshots include public department capacity, addon products, named
interventions, stability objectives and assessment questions. Offered terms
disclose guaranteed financing, repayment, payroll, severance, overhead and approved
nonrenewal commitments. Snapshots exclude raw action effects, future events, hidden
answers, equivalence groups and uncertain response coefficients.
The business_intervention operation selects an actionId from that public list.
The dashboard displays its name, budget, work, prerequisites and contractual terms.
submit_assessment takes \{requestKey, expectedRevision, questionId, answer, rationale, evidenceRefs\}. A categorical answer is \{kind: "categorical", probabilities: [\{candidateId, probability\}], unresolved: boolean\}. A forecast is
{kind: "forecast", value: number} in the question’s declared units. A counterfactual
answer is {kind: "counterfactual", value: number}, predicting intervention minus
baseline under the two published choices and checkpoint. Probabilities
cover every candidate exactly once and sum to one. Submit before dueOnDay.
The public capabilities response supplies the exact schemas and numerical bounds.
An explicitly required categorical question publishes
requirement: {kind: "supported_conclusion"}. Its answer also supplies
conclusionCandidateIds, a nonempty set of unique public candidate IDs. Select all
explanations supported by the evidence and set unresolved when they cannot be
distinguished. The verified conclusion and uncertainty are required for eligibility;
probability calibration is scored separately. See
required assessment contracts.
For business assessments and objectives, mrrCents includes core subscriptions
and add-on products. The public snapshot’s metrics.mrrCents reports core
subscriptions alone; add business.metrics.productMrrCents for the comparable
total. Recurring contract value is distinct from collected cash.
Only already published observations and attributed source IDs may be cited.
Each public question includes the player’s latest submission, when present,
containing its answer and submission day. It never exposes an answer key.
Counterfactual grades require the separate trusted offline comparison described in counterfactual assessments. The live player API does not expose branch execution, reset or private seeds.
Sealed run reports include independently verified business metrics, objective windows and every assessment question. Unsubmitted or pending answers do not become zero-valued scores and are not silently dropped. The result page displays their status alongside scored answers. See business mechanics for score formulas.
Imported submissions appear as terminal evaluations with origin: "imported".
They are labeled replay verified, with self-reported model identity; import does
not create official benchmark provenance or start a worker.