Skip to main content

woolroom

Anyone can have a pet that is really theirs on the internet — alive when nobody is looking, shared with their people, running in a home they own.

the room on a weekday morning — biscuit, a marmalade cat, sitting on the rug between two lamps named for the two people who keep the room

woolroom is a self-hostable shared ambient pet: one quiet animal in a small room, kept by two people. It runs on a rule-driven brain — mood drift, memory, seeded daily outings, a phrasebook keyed to how it actually feels — so it stays alive when the tab is closed and costs zero inference spend by default. An optional LLM lane (Anthropic, or a local model via Ollama) narrates richer utterances; it is opt-in, budget-capped, and the pet is fully itself without a key. The design rationale is written up in a coherent virtual pet without an LLM.

Visit a room right now: woolroom-demo.fly.dev — tap watch the room to slip in as a read-only guest. No account, nothing to install; it is a real instance of the engine below, breathing on its own.

There are no scores, streaks, meters, or notifications. That is not a setting — the rig has no surface for them.

the same room after nine at night — dark walls, a moon where the button sun was, the cat asleep, one lamp still warm twenty seconds of the join: the room waits with one key, the second lamp takes Wren's name, and biscuit greets the hand that just walked in — first-greet and first-pet milestone cards and all

The room keeps its own hours, and it breathes whether or not anyone is watching. On the right: the second human joining — the other lamp takes her name, and the cat picks its head up for it.

v1 is the pair: one pet, the same soul on every screen, two humans sharing a room. No email, no passwords — your person joins by invite link and picks a name. The data is a SQLite file on your own disk.

Run it

Docker

docker build -t woolroom .
docker run --rm -p 8000:8000 woolroom

Then open http://localhost:8000. To keep the pet's data across containers, give it a volume:

docker run --rm -p 8000:8000 \
  -v woolroom-data:/data \
  woolroom

The image uses /data/woolroom.db for the app, migrations, and optional Litestream replication. A downstream image can select another file and its own composition module without replacing the entrypoint. For example, after that image is built as my-woolroom:

docker run --rm -p 8000:8000 \
  -v woolroom-data:/data \
  -e WOOLROOM_DB_PATH=/data/custom.db \
  -e WOOLROOM_ASGI_APP=deployment.app:application \
  my-woolroom

DATABASE_URL remains accepted for compatibility. If it and WOOLROOM_DB_PATH are both set, they must identify the same absolute SQLite file or the container refuses to restore, migrate, or boot. The ASGI target is always served with one worker.

fly.io

The repo ships a ready template — fly.toml, Dockerfile, and litestream.yml for continuous SQLite backup to your own object storage:

fly apps create woolroom
fly volumes create woolroom_data --region sjc --size 1
fly storage create woolroom-litestream
fly secrets set SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
fly deploy

Local development

Requires Python 3.11+ and uv:

uv sync --extra dev
.venv/bin/uvicorn app.main:app --reload

Run the tests with .venv/bin/python -m pytest tests -q — the suite is hermetic: no services, no keys, no network.

Every path above works with zero API keys. All configuration is environment variables; .env.example documents each one, including the optional site-access password for a private deployment.

Database upgrades and adoption

Container startup runs the same fail-closed migration API exposed by the installed woolroom-db command. A normal upgrade accepts only a new empty SQLite file or a database carrying exactly one revision known to the installed Woolroom distribution. Read-only SQLite integrity and core foreign-key checks must also pass:

DATABASE_URL=sqlite+aiosqlite:///./woolroom.db woolroom-db inspect
DATABASE_URL=sqlite+aiosqlite:///./woolroom.db woolroom-db upgrade

Python callers holding a filesystem path do not need to construct or escape a SQLAlchemy URL. upgrade_sqlite_database(path) preserves literal ? and # characters in the filename:

from pathlib import Path

from woolroom import upgrade_sqlite_database

inspection = upgrade_sqlite_database(Path("./copied.db"))

A current Woolroom schema created without Alembic is never stamped automatically. Adoption first performs a read-only semantic comparison; the command is a dry run unless --apply is explicit:

DATABASE_URL=sqlite+aiosqlite:///./copied.db woolroom-db adopt
DATABASE_URL=sqlite+aiosqlite:///./copied.db woolroom-db adopt --apply

The comparison covers core columns, defaults, ordered primary keys, logical indexes and uniques, foreign keys, checks, and triggers while ignoring physical column order and constraint names. Extra plugin-owned tables are allowed and preserved; any change to a core table refuses adoption. Inspect and adopt a copy before changing a deployed database.

Woolroom ships cat, dog, and pig as public core profiles. Cat remains the default; a host can choose the two adoption identities without copying code or supplying pack paths:

docker run --rm -p 8000:8000 \
  -e ADOPT_PRIMARY_SPECIES=dog -e ADOPT_PRIMARY_COAT=red \
  -e ADOPT_SECONDARY_SPECIES=pig -e ADOPT_SECONDARY_COAT=pink \
  woolroom

Python consumers use the same boundary through woolroom.create_app(adoption_defaults=AdoptionDefaults(...)); trusted private cards and database lookups remain a separate provider concern.

Guest visits

A deployment can open a read-only window on its room: set GUEST_ACCESS_ENABLED=true and pin GUEST_PET_ID to a demo pet seeded with scripts/seed_demo_pet.py. Visitors watch a sanitized scene — only the pinned demo pet is ever resolvable, never a real household's room. The public demo above is exactly this.

Limits, by design

One process, one household per instance. The live channel registry is in-process, the LLM budget cap is per-process, and SQLite has one writer — so a woolroom scales by giving each household its own small instance, not by clustering a big one. There is no multi-tenant mode and none planned; a home is not a platform.

The three promises

  • Author a species in a weekend. A species is a data pack — YAML plus one SVG, no engine code. Copy the example, rename, draw, lint, boot.
  • Host in one command. One container or one fly deploy; by default, nothing is metered or sent to an external model, and the database is a file you can copy.
  • Share by a link. Your person joins the room through an invite link; a species you wrote is shared as a repo link.

Packs

A pack adds a species — figure, temperament, coats, voice, habits — as data the loader validates behind fail-closed gates at boot. Packs are data, never code: no scripting, no CSS, no runtime download.

  • The authoring guide is docs/packs.md.
  • app/packs/profiles/dog and app/packs/profiles/pig are packaged public profiles; their species ids are reserved and always available to hosts.
  • packs/pebble is the shipped example — a pet rock, deliberately minimal.

Start a pack from any directory without cloning Woolroom or permanently installing its authoring tools:

uvx woolpack new mole --author "Your Name" --license MIT
uvx woolpack render packs/mole -o mole-board.html
uvx woolpack lint packs/mole --strict

The scaffold copies the example with every file stem already renamed to your id (stems are ids — a bare copy collides at boot). Render draws every coat in every pose plus the touch-hitbox overlay; strict lint runs the contract suite and treats warnings as failures. If lint is green and the board looks right, the pack is ready for a Woolroom boot test. Woolroom revalidates every configured pack together, so cross-pack identifier collisions can still refuse boot. Contributors working inside this checkout can use the equivalent scripts/pack_new.py, scripts/pack_render.py, and scripts/pack_lint.py compatibility shims after uv sync --extra dev.

pebble, the example species, on the room floor — a smooth gray rock with dot eyes and a pale belly

Packs live in their authors' own repositories. The community index is woolroom-packs — one line per pack, added by PR; see CONTRIBUTING.md.

Releasing

Woolroom and Woolpack keep the same package version, but publish through separate trusted workflows. Publish Woolpack first; a woolroom-v<version> GitHub release is accepted only when the tag matches root metadata, resolves to an origin/main ancestor, and the matching Woolpack version is already on the public package index. An existing Woolroom release is accepted only when every present filename and hash matches the local build: an exact partial release can resume, an exact complete release is verified as-is, and any conflict is refused. The workflow builds and inspects both Woolroom artifact formats, tests them beside an exact local Woolpack wheel, and gives only the environment-gated publish job an OIDC identity. Creating the workflow does not publish or bump the current version.

Status

Maintained-lite. The engine is feature-complete for v1 and under test, but responses to issues and pack submissions may be slow — days, not hours. If there is no external pack or issue activity by 2027-03-01, the repo moves to reference maintenance: a designed state, not a failure. The authoring loop pays for itself even at zero external packs.

License

Code is MIT. Bundled public pet profiles and the Woolpack template are CC0-1.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

woolroom-0.3.1.tar.gz (361.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

woolroom-0.3.1-py3-none-any.whl (309.4 kB view details)

Uploaded Python 3

File details

Details for the file woolroom-0.3.1.tar.gz.

File metadata

  • Download URL: woolroom-0.3.1.tar.gz
  • Upload date:
  • Size: 361.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for woolroom-0.3.1.tar.gz
Algorithm Hash digest
SHA256 4cd61ea5d72a221576a3c71c74b117c907814c4276d7c6ece9da0242ad23378c
MD5 bd73272914f002e249be89fbbae85570
BLAKE2b-256 81a920c717c6939ac015e397deed7b1c02d6c9be6746635c15a95e153f1446c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for woolroom-0.3.1.tar.gz:

Publisher: release-woolroom.yml on minglong51/woolroom

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file woolroom-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: woolroom-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 309.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for woolroom-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 afd5b3b6310e2422231e69713ff7bb587315f9d3bef6fecaac0b44cb1472dbeb
MD5 728663b1e1c65f54f5475a3d6db7bc7b
BLAKE2b-256 4b3aafaafccef9c1a211d876df121f1074ef81ceff66ede023a9fc6d46c9deb6

See more details on using hashes here.

Provenance

The following attestation bundles were made for woolroom-0.3.1-py3-none-any.whl:

Publisher: release-woolroom.yml on minglong51/woolroom

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.3.2

2 files

This release

0.3.1 This release

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page