Dynamic Worlds¶
After a turn, Orb can ask the Agent whether the shared World changed. The Agent only proposes changes. A user reviews and accepts a pending changeset before it can affect the lore.
The user-facing feature is Dynamic Worlds.
Two layers, one table¶
lorebook_entries stores both the user's authored lore and the Agent's overlay:
| Authored | Dynamic | |
|---|---|---|
| Owner | User | Agent, after user approval |
| Source | Drawer or import | Accepted changeset |
| Agent actions | None | Create, update, archive |
Dynamic entries never overwrite authored rows. The authored layer is therefore the recoverable base; reset archives the live overlay and reveals it again.
An overlay row has one of three actions:
add— new lore.replace— hides an authored entry and supplies replacement content.suppress— hides an authored entry without supplying content.
Archiving an overlay retires it without deleting it. The entry it hid becomes visible again. Authored entries remain editable, and deleting one does not delete its overlay; an orphaned replacement becomes standalone lore.
The effective view¶
Callers must use prompting/lorebook.select_effective_entries, never the raw
pool. It:
- removes disabled and archived rows;
- removes authored rows hidden by a live
replaceorsuppress; - removes
suppressmarkers from the rendered result.
Dynamic entries follow authored entries under Dynamic World State. They use
the normal lorebook activation rules: constant for always-known facts and
keywords for local state. When Agentic Lorebook is active, Director selection
replaces keyword activation for dynamic entries as well as authored ones.
Proposing changes¶
pipeline/world_proposal.py runs after the editor and post-pipeline rewriting,
so it evaluates the text that will be saved. It does nothing when the Agent is
off, no enabled World has dynamic updates enabled, or the reply is empty,
aborted, or failed.
The stage:
- rereads every enabled World that opted in;
- makes one forced model call with the turn and a catalog of those Worlds;
- validates the operations against the live entries;
- splits valid operations into one pending changeset per World;
- stages those changesets with the assistant message and emits one
world_change_proposedevent per changeset.
A failed or malformed proposal is logged and discarded. It never changes the reply. A regenerated reply judges the original user message; steering text is not treated as a world event.
The model sees a small vocabulary: create, revise, and retract. Validation
derives the storage action from the target row, so the model does not need to
know whether it is editing authored or dynamic lore. A revision inherits the
target's activation and keywords unless it supplies new values. Only a create
uses defaults.
validate_proposal checks the World, target, scope, body, and duplicate names.
It also repairs a keyword entry with no keywords by using the entry name. The
model never executes database CRUD.
Review and apply¶
Applying a changeset takes the World lock and a SQLite BEGIN IMMEDIATE
transaction. The changeset's base_revision must still equal
worlds.content_revision:
- a match applies the complete batch;
- a mismatch marks the proposal
staleand returns409.
There is no force-apply or automatic rebase. Re-evaluate derives a new
proposal from the stored source messages and the current World. The old one is
then marked superseded; a new proposal replaces it only if the new evaluation
has operations.
Other review actions use the same guarded path:
- editing operations is atomic and revalidated on the server;
- undo applies a compensating changeset only when the affected dynamic rows still have their recorded after-state;
- reset archives the live overlay and is itself undoable;
- manual deletes on an opted-in World are recorded in history as applied
manualchangesets.
content_revision advances once for each mutation that changes lore: entry
create/update/delete, bulk import, apply, undo, or reset. Renames and enable
flags do not invalidate pending proposals.
History and visibility¶
Pending proposals are invisible to prompts, projections, and other characters. Accepted lore becomes visible the next time a context is loaded. Worlds keep one canonical timeline across conversation branches, so accepted lore remains even if its source branch is later abandoned.
Source edits or deletion stale pending proposals. Applied history remains, with nullable source references. Branch changes do not affect it.
Exports are authored by default. The effective view must be requested explicitly
(?view=effective or ?world_view=effective). Preset backups include dynamic
entries and changeset history with the World.
Conversation-scoped World choices¶
Navigation reads GET /conversations/{cid}/worlds and never writes World state.
worlds.is_global supplies an on default in every chat; a card-linked World also
defaults on for that conversation's cast, including every active group member.
conversation_worlds(conversation_id, world_id, enabled) overrides either default.
Saving a choice equal to its current default removes the override. Entry enabled
flags remain properties of the entries themselves.
get_effective_world_ids resolves this rule once for turn context. Lore selection,
off-turn workflow prefixes and context-size estimates use those IDs. The proposal
stage retains its initial candidate set and rereads deletion, Dynamic World opt-in
and that conversation's choices; switching chats has no effect, while turning a
World off in the originating chat suppresses its proposal.
Forks copy overrides. Presets assign the two-parent table to the chats domain and
drop choices referencing missing Worlds on a partial import. Migration 0076 turns
linked legacy Worlds into non-global defaults and preserves floating Worlds'
previous activation as is_global.