A world-simulation engine for LLM-driven agents: event-sourced core, behavior-tree scheduling, memory and relationships, packaged as portable .cyberworld files
Project description
anima-world
A world-simulation engine for LLM-driven agents. Characters wake up, get hungry, go to work, gossip about each other, form cliques, spend money, remember what happened last week, and change how they feel about you. One SQLite file is one world.
English | 中文
What this is
Most "AI agent" frameworks give you a chatbot with tools. This gives you a place — one that keeps running whether or not anyone is watching, and remembers what happened while they were away.
A world is a tick-driven simulation. On every tick each character runs a behavior tree against its own needs and the state of the world, and acts. Those acts become events in an append-only log, which is the world's only source of truth — balances, relationships, and locations are all projections of that log, never separately stored rows that could drift out of sync.
The LLM sits beside the simulation, not inside it. It writes the narration, plans what a character does with free time, and judges how a conversation changed a relationship — all on background threads. The clock never blocks on a network call. Take the LLM away entirely and the world still runs; it just narrates in templates.
The engine is a library, not a service. There is no HTTP server and no port. Your application imports it, and the world lives inside your process:
your app ──import anima_world.api──▶ world.db
See it run
$ pip install anima-world
$ anima-world start
ANIMA 世界引擎
────────────────────────────────────────────
① LLM
! LLM 未配置 —— 叙事、空闲计划、关系判定都会降级成模板文本
修复:anima-world config set llm.api_key sk-…
② 世界
✓ 新建 saves/world.db
时钟:1 tick/秒 —— 约 5 分钟走完一个世界日(现实时间的 300 倍速)
✓ 3 个角色就位: 苏晚夏、陆知遥、沈亦柔
③ 运行
世界在本进程里运行,叙事会打印在下面;停止:Ctrl-C
[第0天 00:10] 遥:遥 wandered around
[第0天 00:10] 夏:夏 went to sleep
[第0天 00:35] 遥:遥 went to sleep
^C
世界已停下,快照已保存。下次接着跑:anima-world start
start configures the LLM, creates the world, and runs it — in that order, with no
documentation required first. Without an API key everything still works; narration is
templated and characters have no plans. That degradation is deliberate, and it is never
silent (anima-world doctor will tell you, and World.state() carries the reason).
Heads up on language: the engine speaks Chinese. CLI output, narration prompts, the built-in seed world, and the reference docs are all in Chinese. The API itself is English (
World.open,world.tick,world.chat), so embedding it in an English-language application works fine — you supply your own seed and prompts.
Install
pip install anima-world # Python 3.11+
Three runtime dependencies, chosen to stay out of your way since they land in every host
that embeds a world: cryptography (encrypts the API key at rest), openai (any
OpenAI-compatible endpoint), httpx.
From source:
git clone https://github.com/aubrey-anima/core.git anima-world
cd anima-world && pip install -e ".[dev]" && python -m pytest
Use it in your app
This is the main interface. World is a plain object with a lifetime — open it, drive
it, close it.
from anima_world.api import World
with World.open("saves/world.db") as world:
world.start_clock() # background clock; or drive it yourself
print(world.state()["world_time"]) # {'day': 0, 'hour': 6, 'minute': 25, ...}
# Talk to a character on a player's behalf. Streams; your app owns the transcript.
for chunk in world.chat("夏", [{"role": "user", "content": "你好"}],
player_id="p1", display_name="阿宇"):
print(chunk, end="")
# Commit a finished exchange: summary + one world event + a relationship verdict
world.record_chat_turn("夏", "p1", [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好呀"},
])
world.player_move("p1", "cafe")
world.config_set("scheduler.tick_rate", 1.0)
For batch work, drive the clock yourself — world.tick(n) is synchronous and
deterministic, which is what makes fast-forwarding and testing possible:
with World.open("w.db") as world:
world.tick(288) # one world day
print(world.memories("夏")) # what she remembers, ranked
print(world.needs("夏")) # {'energy': 0.59, 'hunger': 0.16, ...}
print(world.cliques()) # who has clustered together
What a world contains
Four subsystems, each an opt-in flag except memory. They are off by default because a world that only walks around and talks is a legitimate world, and every mechanism you enable is more LLM spend and more surface to reason about.
| Subsystem | Flag | What it adds |
|---|---|---|
| Memory | always on | Retrieval scored on relevance × recency × importance, periodic reflection that writes higher-order memories, and a forgetting curve |
| Needs | needs.enabled |
energy / hunger / social decay per tick and drive an urgency band in the behavior tree — a tired character stops what it is doing and goes to sleep |
| Economy | economy.enabled |
Items, money, shops, wages, and price drift. The ledger is a projection of payment events, so balances cannot be forged |
| Social | social.enabled |
Three-axis relationships (always on) plus gossip that propagates second-hand with confidence decay, and emergent cliques |
anima-world config set needs.enabled true --db-path saves/world.db
How it is built
One file is one world. world.db holds events, chats, memories, and config.
world.db.key sits next to it and encrypts the API key — it must travel with the
database, or the key becomes unreadable and the world silently drops to Mock. (Not
actually silently: the engine says so at boot and keeps saying so in state().)
The event log is the only truth. There is no balances table, because two sources
of truth eventually disagree and you cannot tell which one is right. world.db does not
store "夏 has 50 coins" — it stores why she has 50 coins. Reconciliation is replay.
One running world owns its file. Half the truth lives in memory (clock, projection,
locks, thread pools), so a second process writing the same world.db forks it
immediately. Offline work — packaging, fast-forwarding — happens after the world closes.
Version is contract. One release freezes (engine code, db format, package format)
together. The major version is the db format version, and db.py enforces it at
runtime: mount a database from an incompatible format and it refuses on the spot rather
than quietly writing garbage.
Ship a world to someone else
Worlds package into a single .cyberworld file — either a template (just the seed, the
world builds itself on first boot) or a snapshot (a database that has already lived).
anima-world world export --seed seed.json --output my.cyberworld \
--world-id my-world --name "我的世界" --mode template
anima-world world import my.cyberworld --destination ./instances
Commands
anima-world start # create + run, with guided setup — start here
anima-world doctor # health check: files, keys, a real LLM call, clock speed
anima-world config # read/write settings, secrets encrypted and masked
anima-world run # foreground host, no onboarding (for deployment)
anima-world simulate # headless fast-forward
anima-world world # export / import .cyberworld packages
Documentation
| docs/REFERENCE.md | Every command, every World method, every config key, the beat-script format 🇨🇳 |
| docs/ARCHITECTURE.md | Why it is shaped this way: the truth model, the tick frame, threads and locks, invariants, known debt 🇨🇳 |
| CONTRIBUTING.md | Development setup, the invariants a patch must not break, how to propose changes |
| CHANGELOG.md | Release history |
Contributing
Issues and pull requests are welcome. Start with CONTRIBUTING.md — it lists the handful of invariants that a change must not break (there is exactly one lock in the system, the LLM is never called on the tick thread, and a few file formats are mirrored by other repositories).
License
Apache License 2.0. Use it, modify it, ship it inside closed-source products; keep the copyright notice and state what you changed. Apache rather than MIT for the patent grant, and permissive rather than copyleft because an engine that hosts embed cannot be copyleft without infecting every host that embeds it.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file anima_world-1.0.2.tar.gz.
File metadata
- Download URL: anima_world-1.0.2.tar.gz
- Upload date:
- Size: 156.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
115bb1b096f6bb45daba60d93969797a1fc8877163dbfafa7cb1c72688315477
|
|
| MD5 |
baab3e9052b831e0a309a76d18ee8363
|
|
| BLAKE2b-256 |
9f5b24980d85877ad532b3ad6a747ad56d2d238257a59ac96c198f35683d7743
|
Provenance
The following attestation bundles were made for anima_world-1.0.2.tar.gz:
Publisher:
release.yml on aubrey-anima/core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
anima_world-1.0.2.tar.gz -
Subject digest:
115bb1b096f6bb45daba60d93969797a1fc8877163dbfafa7cb1c72688315477 - Sigstore transparency entry: 2230639032
- Sigstore integration time:
-
Permalink:
aubrey-anima/core@6e24e2dcba4e2493f5b60558dd631a4f2c0eabe5 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/aubrey-anima
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6e24e2dcba4e2493f5b60558dd631a4f2c0eabe5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file anima_world-1.0.2-py3-none-any.whl.
File metadata
- Download URL: anima_world-1.0.2-py3-none-any.whl
- Upload date:
- Size: 169.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8ff6b4ac6f690f17feae8adc3de08217bf8cadaf1806a0e016ff04bfbb048b8
|
|
| MD5 |
6314dee507767943e29a9bf90bf6ce24
|
|
| BLAKE2b-256 |
12946f479fdc8b0dbeaddc5406a23007298a47d95e482d942b0aa1cddc7b1632
|
Provenance
The following attestation bundles were made for anima_world-1.0.2-py3-none-any.whl:
Publisher:
release.yml on aubrey-anima/core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
anima_world-1.0.2-py3-none-any.whl -
Subject digest:
d8ff6b4ac6f690f17feae8adc3de08217bf8cadaf1806a0e016ff04bfbb048b8 - Sigstore transparency entry: 2230639120
- Sigstore integration time:
-
Permalink:
aubrey-anima/core@6e24e2dcba4e2493f5b60558dd631a4f2c0eabe5 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/aubrey-anima
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6e24e2dcba4e2493f5b60558dd631a4f2c0eabe5 -
Trigger Event:
push
-
Statement type: