worldseq

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.

MethodPathBehavior
GET/api/catalogPublic company descriptions and task objectives
GET/api/operator/worlds/:companyIdAuthored company starting conditions and public challenge briefs
GET/api/runsPaginated human sessions and background evaluation progress
GET/api/evaluations/:idEvaluation heartbeat, current activity and paginated task results
GET/api/runs/:idSaved run’s public company snapshot, accepted plans and verified result
GET/api/runs/:id/updatesPublished company observations, paginated by sequence
POST/api/episodesCreate a development or registered graded episode; returns {id, token, snapshot, capabilities}
POST/api/episodes/:id/sealCommit 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}:

MethodInput
company_snapshot{}
capabilities{}
evidence_query{after?: number, limit?: 1..200, kind?: string}
submit_decisionExact decisionRequestSchema from src/engine/schemas.ts
submit_assessmentTyped 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.