setActions(toggle(actions, value))}
+ />
+
+
+
+
+ {level === 'custom' && (
+
+ )}
+ >
)}
- {open && !parsedRequest.success && (
+ {open && !swarmMode && !parsedRequest.success && (
Select at least one agent, outcome, and action, and enter a valid
range.
@@ -5416,42 +5673,67 @@ function ExperimentExportPanel({
)}
{disabled && (
- Pause playback and wait for all turn, reset, and personality work to
- finish.
+ {swarmMode
+ ? 'Pause playback and wait for the active swarm tick or reset to finish.'
+ : 'Pause playback and wait for all turn, reset, and personality work to finish.'}
)}
- {preview && (
-
-
-
- Matching
- - {preview.matchingTurnCount} turns
-
-
-
- Communications
- - {preview.matchingCommunicationCount} matched
-
-
-
- Control changes
- - {preview.matchingControlChangeCount} matched
-
-
-
- Diplomacy/alliance events
- - {preview.matchingDiplomacyEventCount} matched
-
-
-
- Size
- - {preview.serializedUtf8Bytes} bytes
-
-
-
- Approx. AI input
- - {preview.approximateAiInputTokens} tokens
-
-
-
- Selected cost
- - {formatCost(preview.knownCostCredits)}
-
-
- )}
+ {preview &&
+ (swarmMode ? (
+
+
+
- Committed swarm ticks
+ - {preview.matchingSwarmTickCount} retained
+
+
+
- Provider attempts
+ - {preview.matchingProviderAttemptCount} retained
+
+
+
- Size
+ - {preview.serializedUtf8Bytes} bytes
+
+
+
- Approx. AI input
+ - {preview.approximateAiInputTokens} tokens
+
+
+
- Selected cost
+ - {formatCost(preview.knownCostCredits)}
+
+
+ ) : (
+
+
+
- Matching
+ - {preview.matchingTurnCount} turns
+
+
+
- Communications
+ - {preview.matchingCommunicationCount} matched
+
+
+
- Control changes
+ - {preview.matchingControlChangeCount} matched
+
+
+
- Diplomacy/alliance events
+ - {preview.matchingDiplomacyEventCount} matched
+
+
+
- Size
+ - {preview.serializedUtf8Bytes} bytes
+
+
+
- Approx. AI input
+ - {preview.approximateAiInputTokens} tokens
+
+
+
- Selected cost
+ - {formatCost(preview.knownCostCredits)}
+
+
+ ))}
{notice && (
{notice}
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 87696c7..a4b7b19 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -16,7 +16,10 @@ fallbacks; provider attempts survive world rollback. See ADRs 0028 and 0029.
Swarm tick records are separate from legacy agent turn records. Full all-agent
exports and the archive retain safe plans, directives, action choices, and
factual provider usage; selective legacy turn filters do not export partial
-swarm plans. World Lab's dedicated swarm presentation follows in PR C.
+swarm plans. World Lab selects a dedicated swarm presentation from the
+scenario mode. It shows Zero strategy, worker directives and Jev decisions,
+progress, and provider usage while omitting irrelevant legacy social cognition
+controls. See ADR 0030.
Provider-attempt and credit-exposure admission are owned by `SimulationService`. Its ledger is
deliberately separate from deterministic world state and committed-turn
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index 035deeb..1862f0c 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -51,6 +51,12 @@ structured plans and outcomes, while raw planner messages and responses remain
server-private. Legacy social and prose-memory cognition does not run in this
mode.
+World Lab receives only configured booleans and adapter/model labels for the
+swarm providers. It displays committed safe plan and reflex telemetry plus
+server attempt totals; neither provider key nor raw request/response body is
+included in the snapshot. An unavailable provider triggers the documented
+deterministic fallback and remains visible as unavailable to the operator.
+
`OPENROUTER_API_KEY` is the only required OpenRouter environment value and is read only by the Game API process. It never enters catalog DTOs, assignments, exports, fixtures, browser responses, errors, or logs. The repository-root `.env` is ignored; `.env.example` contains only a placeholder.
The development API has no authentication, rate limiting, or provider-account balance enforcement. It does enforce server-owned per-experiment provider-attempt and conservative credit-admission ceilings configured by World Setup. Credit reservations bound admission exposure but cannot guarantee the upstream bill, especially when reported cost exceeds the operator's reservation. It binds to loopback and its CORS allowlist is limited to the documented local World Lab origins. Do not deploy its cost-incurring tick endpoint to unauthenticated public traffic.
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 771516b..6f46240 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -9,6 +9,10 @@ PR B adds scripted Agent Zero and Jev providers to test plan validation,
planning-before-reflex ordering, real engine resolution, deterministic failure
fallback, cancellation rollback, and independent provider-attempt retention.
The live provider smoke test remains separately opted into.
+PR C adds mode-specific World Lab component coverage for Zero strategy,
+worker directives and probabilities, provider usage, Zero-only model setup,
+and hiding of legacy social/personality views while retaining legacy UI
+coverage. Browser smoke still exercises the deterministic legacy baseline.
Attempt-budget tests use deterministic providers and cover whole-roster tick
admission, retry permits, cancellation finalization, and the distinction
diff --git a/docs/adr/0030-zero-swarm-world-lab.md b/docs/adr/0030-zero-swarm-world-lab.md
new file mode 100644
index 0000000..715e1ee
--- /dev/null
+++ b/docs/adr/0030-zero-swarm-world-lab.md
@@ -0,0 +1,42 @@
+# ADR 0030: Zero-swarm World Lab presentation
+
+## Status
+
+Accepted for PR C.
+
+## Decision
+
+World Lab selects its presentation from the scenario's explicit
+`cognitionMode`. Legacy experiments keep the existing agent, chat, diplomacy,
+personality, goal, and memory views. In `zero-swarm-v1`, the operator sees the
+latest Agent Zero strategy, each worker's current directive and physical
+action, Jev confidence and probability distribution, recent worker progress,
+and provider usage. The swarm view reads committed `swarmTicks`, including
+safe semantic worker situation buckets, and factual attempt accounting from
+the Game API. It does not derive or display legacy turn metrics as swarm
+outcomes.
+
+The Agents workspace remains available for the designated Zero model.
+Personality and strategy personality controls, worker generative model
+assignments, chat, diplomacy, alliances, individual goals, and prose memories
+are absent from the zero-swarm presentation. World Setup retains mode
+selection while hiding irrelevant behavior assignment controls when the
+selected mode is zero-swarm.
+
+The snapshot exposes server-only provider availability as configured booleans,
+adapter names, and the pinned reflex model name. It never exposes keys or raw
+provider payloads. Missing provider credentials are visible to the operator;
+they do not block a deterministic fallback tick. Zero's generative model is
+the only model assignment required for zero-swarm readiness.
+
+Swarm exports use the existing full-safe archive schema so retained swarm ticks
+and provider attempts remain available for comparisons. That schema also
+retains legacy configuration fields for compatibility; World Lab labels this
+explicitly and does not use those fields to drive swarm execution.
+
+## Consequences
+
+The World Lab remains one production developer/admin surface with one
+execution controller. The zero-swarm view is read-only telemetry; it grants
+neither models nor the browser world mutation authority. Persistent directives
+and event-driven replanning remain PR D.
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 981da59..3394864 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -2713,6 +2713,7 @@ export const swarmWorkerTickRecordSchema = z
.object({
agentId: agentIdSchema,
directive: swarmDirectiveSchema,
+ situation: reflexObservationSchema.shape.currentSituation.optional(),
action: worldActionSchema.optional(),
actionResult: worldActionResultSchema.optional(),
reflexDecision: reflexDecisionSchema.optional(),
@@ -3366,6 +3367,16 @@ export const simulationSnapshotSchema = z
status: simulationStatusSchema,
providerMode: providerModeSchema,
providerConfigured: z.boolean(),
+ swarmProviderStatus: z
+ .object({
+ plannerMode: z.enum(['openrouter-swarm', 'scripted-swarm-test']),
+ plannerConfigured: z.boolean(),
+ reflexMode: z.enum(['typesafe-jev', 'scripted-reflex-test']),
+ reflexConfigured: z.boolean(),
+ reflexModel: z.string().trim().min(1).max(200).optional(),
+ })
+ .strict()
+ .optional(),
modelConfiguration: experimentModelConfigurationSchema,
behaviorConfiguration: behaviorConfigurationSchema.optional(),
resolvedModels: z