Pouchy World — size limits, and why each one exists
Written against world API 1.48.0. Every number below is enforced by the
server; the machine-readable copy is the maxItems / maxLength on the
world OpenAPI
(components.schemas.StoryPackageContent, plus x-pouchy-max-bytes for the
whole-package ceiling). Content you SEND over a limit is refused with a 400
that names the limit (roles exceeds 24, story package is 1043211 bytes; the limit is 900000 …), never silently truncated. The bounds in §5 are of
another kind — what the runtime holds, shows or reports per beat — and the
note beside each says how it behaves.
1. What changed in 1.48.0
| Limit | Before | Now | Constant |
|---|---|---|---|
| Roles in a story | 12 | 24 | STORY_MAX_ROLES |
| Roles in a world definition | 12 | 24 | WORLD_MAX_ROLES (pinned equal to the above) |
| Scenes | 50 | 100 | STORY_MAX_SCENES |
| Nodes | 100 | 200 | STORY_MAX_NODES |
| Branches | 100 | 200 | STORY_MAX_BRANCHES |
| Whole package size | (unchecked) | 900,000 bytes | STORY_PACKAGE_MAX_BYTES |
This is a loosening, with one new refusal: a package over 900,000 bytes. Over 1 MiB such a package could never be stored, and now gets a 400 that explains itself instead of a storage error. Between 900,000 bytes and 1 MiB it used to publish and is now refused. Revisions already published are not affected: content is validated when it is created or published, never when it is read, so a pinned revision keeps running exactly as before. Only publishing it again (or a new revision of the same size) meets the new ceiling.
2. The cast
| Limit | Value | Why |
|---|---|---|
| Roles per story / world | 24 | A product choice, not a technical one. Twelve was "small on purpose"; nothing measured depends on it (see Cost below). |
| Agents per role | exactly 1, distinct | A world refuses one agent bound to two roles, so a 24-role world needs 24 agents. |
| Roles that speak in one beat | 3 (WORLD_TURN_MAX_ROLES) |
Dramatic, and deliberately NOT raised: three speakers is a scene; more is a crowd an audience cannot follow. It also bounds each beat to at most 3 model calls. |
| Roles one event may wake | 5 (WORLD_MAX_ROLES_PER_EVENT) |
Equal to the event router's fan-out cap (MAX_EVENT_FANOUT). A world wired so one event needs more roles than the router will ever deliver to is refused at publish, not truncated at runtime. |
| Secrets / goals per role | 20 each | Capacity. Each is rendered only into that role's own prompt. |
Cost does not grow with the cast. A role that is not speaking never enters
another role's prompt: not its description, not its goals, not its secrets. The
only per-other-role content is the lines already spoken earlier in the same
beat (at most 2) and two capped id lists in the effect brief. A 24-role story
and a 3-role story produce byte-identical prompts for the roles that speak;
world-envelope-composition.test.ts proves this at the maximum cast size on
every build.
A bigger cast needs the beat to choose who is in the room. The 3 seats are
filled in roles declaration order. Without direction, roles 4–24 are
skipped on every beat, forever: they appear in skippedRoles with
code: "role_cap_reached". Choose the scene's cast per beat with
trigger.focusRoles (turn door, since 1.29.0), or route with event
subscriptions. If a character "never says anything", check skippedRoles
before anything else.
3. The story package
| Item | Max | Note |
|---|---|---|
| Scenes | 100 | |
| Nodes | 200 | each names a declared scene |
| Branches | 200 | each connects two declared nodes |
| Prerequisites per node | 10 | |
| Endings | 60 | raised from 20 earlier, for multi-episode serials |
| Established facts | 200 | |
| Canon lines | 100 | |
| Constraints | 50 | |
| Flags | 50 | |
| Initial relations | 50 | pinned to what runtime state can hold (WORLD_STATE_MAX_RELATIONS) |
| Episodes (series) | 24 | |
| Ending rules per episode / conditions per rule | 20 / 10 | |
| Turn budget per episode | 200 beats | the evidence window: a longer episode cannot be turned into a script |
| Text per item | 500 characters | canon, facts, secrets, goals, objectives, conditions, descriptions |
| Whole package | 900,000 bytes | see §4 |
4. The size ceiling — the limit you will actually meet
A published revision is stored as one Firestore document, and Firestore refuses any document over 1 MiB (1,048,576 bytes). The package ceiling is 900,000 bytes, measured as the UTF-8 bytes of the normalized content's JSON. The remaining ~140 KiB covers the revision row's own fields.
Why you will meet it before the item limits: the item limits multiply. Chinese, Japanese and Korean text is 3 bytes a character, so one maximally-filled role (20 secrets + 20 goals × 500 characters) is about 60 KB. Twenty-four such roles would be 1.44 MB. A real script is far smaller, but a long-text Chinese package reaches the byte ceiling long before 24 roles or 200 nodes.
When you hit it (400 story package is N bytes; the limit is 900000 …):
- Shorten the long text fields first: role
secrets/goals,canon,establishedFacts. - Keep reference material (full script, lore bible) out of the package. The
package carries what the runtime needs to decide and project state; the
original script is referenced by
source: { title, version }, never embedded. POST …/story-packages/import-candidate/validateruns the same validator without publishing and returns the same message, so you can check a package before you publish it.
5. Other runtime bounds you may see
| Bound | Value | How it behaves |
|---|---|---|
| State patch ops per beat | 20 | a larger batch is refused whole (patches are all-or-nothing) |
| Entities / relations in world state | 50 / 50 | a patch that would add the 51st is refused, with the whole batch. A big cast with a dense relationship graph reaches 50 relations long before 24 roles do: 24 roles make 276 possible pairs |
| Private notes per role in state | 20 | same: the op that would add the 21st is refused |
| Branch options a turn reports | 5 | the first 5 reachable, unfinished branches in declaration order; the rest stay in the story and appear as earlier ones complete |
| Relation ids in one role's effect brief | 8 other roles | ids only; the brief reports how many it cut |
| Deliberation | 2 candidates, 3 roles | server constants; a request may ask for fewer, never more |
| Story import (model-assisted) | 16,000 characters of script | a longer script is refused; the extracted package obeys every limit above |
| Script draft | 500 beats | one draft covers at most 500 committed beats; draft a longer worldline one episode at a time (episodeRunId, 1.27.0) |
| Enabled worlds / story packages per project | 1,000 / 1,000 | creation is refused at the ceiling |
6. Where the numbers live
src/lib/world/story-package.ts (story limits and the byte ceiling),
src/lib/world/definition.ts (world definition), and
src/lib/server/platform/world-coordinator.ts (WORLD_TURN_MAX_ROLES). The
dashboard's world wizard imports the same constants and shows
"n / 24 roles", so it cannot drift from the contract.