woolroom 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- woolroom-0.3.0/LICENSE +21 -0
- woolroom-0.3.0/PKG-INFO +265 -0
- woolroom-0.3.0/README.md +235 -0
- woolroom-0.3.0/app/__init__.py +0 -0
- woolroom-0.3.0/app/api/__init__.py +0 -0
- woolroom-0.3.0/app/api/admin.py +396 -0
- woolroom-0.3.0/app/api/deps.py +78 -0
- woolroom-0.3.0/app/api/http.py +1221 -0
- woolroom-0.3.0/app/api/ws.py +232 -0
- woolroom-0.3.0/app/auth/__init__.py +0 -0
- woolroom-0.3.0/app/auth/session.py +46 -0
- woolroom-0.3.0/app/auth/site_access.py +128 -0
- woolroom-0.3.0/app/channels/__init__.py +0 -0
- woolroom-0.3.0/app/channels/base.py +17 -0
- woolroom-0.3.0/app/channels/webapp.py +341 -0
- woolroom-0.3.0/app/config.py +163 -0
- woolroom-0.3.0/app/data/__init__.py +0 -0
- woolroom-0.3.0/app/data/body_language.py +1015 -0
- woolroom-0.3.0/app/data/quirks_catalog.py +253 -0
- woolroom-0.3.0/app/data/species.py +83 -0
- woolroom-0.3.0/app/data/voice.py +302 -0
- woolroom-0.3.0/app/engine/__init__.py +0 -0
- woolroom-0.3.0/app/engine/aging.py +120 -0
- woolroom-0.3.0/app/engine/mood.py +132 -0
- woolroom-0.3.0/app/engine/outings.py +92 -0
- woolroom-0.3.0/app/engine/quirks.py +286 -0
- woolroom-0.3.0/app/eval/__init__.py +16 -0
- woolroom-0.3.0/app/eval/corpus.py +110 -0
- woolroom-0.3.0/app/eval/runner.py +255 -0
- woolroom-0.3.0/app/main.py +511 -0
- woolroom-0.3.0/app/memory/__init__.py +0 -0
- woolroom-0.3.0/app/memory/buffer.py +112 -0
- woolroom-0.3.0/app/memory/core.py +44 -0
- woolroom-0.3.0/app/memory/moments.py +206 -0
- woolroom-0.3.0/app/packs/__init__.py +52 -0
- woolroom-0.3.0/app/packs/lint.py +3 -0
- woolroom-0.3.0/app/packs/loader.py +237 -0
- woolroom-0.3.0/app/packs/profiles/dog/pack.yaml +5 -0
- woolroom-0.3.0/app/packs/profiles/dog/phrases/dog.yaml +536 -0
- woolroom-0.3.0/app/packs/profiles/dog/species/dog.svg +56 -0
- woolroom-0.3.0/app/packs/profiles/dog/species/dog.yaml +36 -0
- woolroom-0.3.0/app/packs/profiles/dog/voice.yaml +5 -0
- woolroom-0.3.0/app/packs/profiles/pig/pack.yaml +5 -0
- woolroom-0.3.0/app/packs/profiles/pig/phrases/pig.yaml +536 -0
- woolroom-0.3.0/app/packs/profiles/pig/species/pig.svg +53 -0
- woolroom-0.3.0/app/packs/profiles/pig/species/pig.yaml +30 -0
- woolroom-0.3.0/app/packs/profiles/pig/voice.yaml +4 -0
- woolroom-0.3.0/app/packs/sanitize.py +39 -0
- woolroom-0.3.0/app/room_contract.py +92 -0
- woolroom-0.3.0/app/runtime/__init__.py +0 -0
- woolroom-0.3.0/app/runtime/actions.py +336 -0
- woolroom-0.3.0/app/runtime/client.py +165 -0
- woolroom-0.3.0/app/runtime/llm_log.py +175 -0
- woolroom-0.3.0/app/runtime/pet_state.py +381 -0
- woolroom-0.3.0/app/runtime/prompt.py +103 -0
- woolroom-0.3.0/app/runtime/respond.py +235 -0
- woolroom-0.3.0/app/runtime/scene_fx.py +237 -0
- woolroom-0.3.0/app/runtime/shared_trace.py +38 -0
- woolroom-0.3.0/app/runtime/validator.py +75 -0
- woolroom-0.3.0/app/runtime/visits.py +78 -0
- woolroom-0.3.0/app/scheduler/__init__.py +0 -0
- woolroom-0.3.0/app/scheduler/jobs.py +319 -0
- woolroom-0.3.0/app/static/access.html +172 -0
- woolroom-0.3.0/app/static/app.js +70 -0
- woolroom-0.3.0/app/static/apple-touch-icon.png +0 -0
- woolroom-0.3.0/app/static/favicon.svg +14 -0
- woolroom-0.3.0/app/static/icon-192.png +0 -0
- woolroom-0.3.0/app/static/icon-512-maskable.png +0 -0
- woolroom-0.3.0/app/static/icon-512.png +0 -0
- woolroom-0.3.0/app/static/index.html +946 -0
- woolroom-0.3.0/app/static/js/api.js +632 -0
- woolroom-0.3.0/app/static/js/figures.js +245 -0
- woolroom-0.3.0/app/static/js/memory.js +55 -0
- woolroom-0.3.0/app/static/js/presence.js +188 -0
- woolroom-0.3.0/app/static/js/quirks.js +195 -0
- woolroom-0.3.0/app/static/js/sound.js +316 -0
- woolroom-0.3.0/app/static/js/state.js +117 -0
- woolroom-0.3.0/app/static/js/ui.js +86 -0
- woolroom-0.3.0/app/static/js/wool.js +615 -0
- woolroom-0.3.0/app/static/js/woolevents.js +508 -0
- woolroom-0.3.0/app/static/js/woolfx.js +329 -0
- woolroom-0.3.0/app/static/js/woolvisits.js +146 -0
- woolroom-0.3.0/app/static/js/ws.js +303 -0
- woolroom-0.3.0/app/static/manifest.json +17 -0
- woolroom-0.3.0/app/static/style.css +1647 -0
- woolroom-0.3.0/app/static/vendor/alpine-3.14.1.min.js +5 -0
- woolroom-0.3.0/app/storage/__init__.py +0 -0
- woolroom-0.3.0/app/storage/db.py +30 -0
- woolroom-0.3.0/app/storage/models.py +222 -0
- woolroom-0.3.0/app/storage/repo.py +707 -0
- woolroom-0.3.0/app/time.py +46 -0
- woolroom-0.3.0/packages/woolpack/LICENSE-CC0 +121 -0
- woolroom-0.3.0/pyproject.toml +69 -0
- woolroom-0.3.0/setup.cfg +4 -0
- woolroom-0.3.0/tests/test_access_client.py +279 -0
- woolroom-0.3.0/tests/test_adoption_defaults.py +266 -0
- woolroom-0.3.0/tests/test_aging.py +59 -0
- woolroom-0.3.0/tests/test_app_flows.py +1239 -0
- woolroom-0.3.0/tests/test_auth_namespace.py +255 -0
- woolroom-0.3.0/tests/test_client.py +94 -0
- woolroom-0.3.0/tests/test_config.py +54 -0
- woolroom-0.3.0/tests/test_container_entrypoint.py +199 -0
- woolroom-0.3.0/tests/test_database_boundary.py +477 -0
- woolroom-0.3.0/tests/test_demo_self_play.py +167 -0
- woolroom-0.3.0/tests/test_design_docs.py +323 -0
- woolroom-0.3.0/tests/test_distribution_boundary.py +167 -0
- woolroom-0.3.0/tests/test_dog_pack.py +111 -0
- woolroom-0.3.0/tests/test_guest_mode.py +410 -0
- woolroom-0.3.0/tests/test_household.py +319 -0
- woolroom-0.3.0/tests/test_llm_lane.py +155 -0
- woolroom-0.3.0/tests/test_migrations.py +76 -0
- woolroom-0.3.0/tests/test_mood.py +33 -0
- woolroom-0.3.0/tests/test_outings.py +41 -0
- woolroom-0.3.0/tests/test_overlay_provider.py +1142 -0
- woolroom-0.3.0/tests/test_pack_lint.py +338 -0
- woolroom-0.3.0/tests/test_pack_pebble.py +183 -0
- woolroom-0.3.0/tests/test_packs.py +772 -0
- woolroom-0.3.0/tests/test_packs_client.py +170 -0
- woolroom-0.3.0/tests/test_phrase_golden.py +263 -0
- woolroom-0.3.0/tests/test_pig_pack.py +131 -0
- woolroom-0.3.0/tests/test_quirk_registry.py +115 -0
- woolroom-0.3.0/tests/test_quirks.py +107 -0
- woolroom-0.3.0/tests/test_release_workflow.py +194 -0
- woolroom-0.3.0/tests/test_respond.py +112 -0
- woolroom-0.3.0/tests/test_room_client.py +296 -0
- woolroom-0.3.0/tests/test_room_contract.py +96 -0
- woolroom-0.3.0/tests/test_species_registry.py +74 -0
- woolroom-0.3.0/tests/test_time.py +23 -0
- woolroom-0.3.0/tests/test_voice_client.py +135 -0
- woolroom-0.3.0/tests/test_woolpack_cards.py +196 -0
- woolroom-0.3.0/tests/test_ws_auth.py +80 -0
- woolroom-0.3.0/woolroom/__init__.py +78 -0
- woolroom-0.3.0/woolroom/adoption.py +33 -0
- woolroom-0.3.0/woolroom/auth.py +54 -0
- woolroom-0.3.0/woolroom/database.py +753 -0
- woolroom-0.3.0/woolroom/migrations/README +3 -0
- woolroom-0.3.0/woolroom/migrations/__init__.py +1 -0
- woolroom-0.3.0/woolroom/migrations/env.py +114 -0
- woolroom-0.3.0/woolroom/migrations/script.py.mako +28 -0
- woolroom-0.3.0/woolroom/migrations/versions/48575234f9ca_initial_schema.py +196 -0
- woolroom-0.3.0/woolroom/migrations/versions/__init__.py +1 -0
- woolroom-0.3.0/woolroom/overlay.py +114 -0
- woolroom-0.3.0/woolroom/py.typed +1 -0
- woolroom-0.3.0/woolroom.egg-info/PKG-INFO +265 -0
- woolroom-0.3.0/woolroom.egg-info/SOURCES.txt +147 -0
- woolroom-0.3.0/woolroom.egg-info/dependency_links.txt +1 -0
- woolroom-0.3.0/woolroom.egg-info/entry_points.txt +2 -0
- woolroom-0.3.0/woolroom.egg-info/requires.txt +20 -0
- woolroom-0.3.0/woolroom.egg-info/top_level.txt +2 -0
woolroom-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 woolroom contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
woolroom-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: woolroom
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A shared ambient pet — one quiet animal in a small room, kept by two people.
|
|
5
|
+
License-Expression: MIT AND CC0-1.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: packages/woolpack/LICENSE-CC0
|
|
10
|
+
Requires-Dist: fastapi>=0.115
|
|
11
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
12
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
13
|
+
Requires-Dist: aiosqlite>=0.20
|
|
14
|
+
Requires-Dist: alembic>=1.13
|
|
15
|
+
Requires-Dist: pydantic>=2.8
|
|
16
|
+
Requires-Dist: pydantic-settings>=2.4
|
|
17
|
+
Requires-Dist: anthropic>=0.40
|
|
18
|
+
Requires-Dist: apscheduler>=3.10
|
|
19
|
+
Requires-Dist: itsdangerous>=2.2
|
|
20
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: woolpack==0.3.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: tzdata>=2024.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# woolroom
|
|
32
|
+
|
|
33
|
+
**Anyone can have a pet that is really theirs on the internet — alive when
|
|
34
|
+
nobody is looking, shared with their people, running in a home they own.**
|
|
35
|
+
|
|
36
|
+
<img src=".github/assets/room-day.png" alt="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" />
|
|
37
|
+
|
|
38
|
+
woolroom is a self-hostable shared ambient pet: one quiet animal in a small
|
|
39
|
+
room, kept by two people. It runs on a rule-driven brain — mood drift,
|
|
40
|
+
memory, seeded daily outings, a phrasebook keyed to how it actually feels —
|
|
41
|
+
so it stays alive when the tab is closed and costs zero inference spend by
|
|
42
|
+
default. An optional LLM lane (Anthropic, or a local model via Ollama)
|
|
43
|
+
narrates richer utterances; it is opt-in, budget-capped, and the pet is
|
|
44
|
+
fully itself without a key. The design rationale is written up in
|
|
45
|
+
[a coherent virtual pet without an LLM](https://www.minglongpan.com/writing/a-coherent-virtual-pet-without-an-llm).
|
|
46
|
+
|
|
47
|
+
**Visit a room right now:** [woolroom-demo.fly.dev](https://woolroom-demo.fly.dev)
|
|
48
|
+
— tap *watch the room* to slip in as a read-only guest. No account, nothing
|
|
49
|
+
to install; it is a real instance of the engine below, breathing on its own.
|
|
50
|
+
|
|
51
|
+
There are no scores, streaks, meters, or notifications. That is not a
|
|
52
|
+
setting — the rig has no surface for them.
|
|
53
|
+
|
|
54
|
+
<table>
|
|
55
|
+
<tr>
|
|
56
|
+
<td width="50%"><img src=".github/assets/room-night.png" alt="the same room after nine at night — dark walls, a moon where the button sun was, the cat asleep, one lamp still warm" /></td>
|
|
57
|
+
<td width="50%"><img src=".github/assets/join-loop.gif" alt="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" /></td>
|
|
58
|
+
</tr>
|
|
59
|
+
</table>
|
|
60
|
+
|
|
61
|
+
*The room keeps its own hours, and it breathes whether or not anyone is
|
|
62
|
+
watching. On the right: the second human joining — the other lamp takes
|
|
63
|
+
her name, and the cat picks its head up for it.*
|
|
64
|
+
|
|
65
|
+
v1 is the pair: one pet, the same soul on every screen, two humans sharing a
|
|
66
|
+
room. No email, no passwords — your person joins by invite link and picks a
|
|
67
|
+
name. The data is a SQLite file on your own disk.
|
|
68
|
+
|
|
69
|
+
## Run it
|
|
70
|
+
|
|
71
|
+
### Docker
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
docker build -t woolroom .
|
|
75
|
+
docker run --rm -p 8000:8000 woolroom
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then open http://localhost:8000. To keep the pet's data across containers,
|
|
79
|
+
give it a volume:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
docker run --rm -p 8000:8000 \
|
|
83
|
+
-v woolroom-data:/data \
|
|
84
|
+
woolroom
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The image uses `/data/woolroom.db` for the app, migrations, and optional
|
|
88
|
+
Litestream replication. A downstream image can select another file and its own
|
|
89
|
+
composition module without replacing the entrypoint. For example, after that
|
|
90
|
+
image is built as `my-woolroom`:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
docker run --rm -p 8000:8000 \
|
|
94
|
+
-v woolroom-data:/data \
|
|
95
|
+
-e WOOLROOM_DB_PATH=/data/custom.db \
|
|
96
|
+
-e WOOLROOM_ASGI_APP=deployment.app:application \
|
|
97
|
+
my-woolroom
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`DATABASE_URL` remains accepted for compatibility. If it and
|
|
101
|
+
`WOOLROOM_DB_PATH` are both set, they must identify the same absolute SQLite
|
|
102
|
+
file or the container refuses to restore, migrate, or boot. The ASGI target is
|
|
103
|
+
always served with one worker.
|
|
104
|
+
|
|
105
|
+
### fly.io
|
|
106
|
+
|
|
107
|
+
The repo ships a ready template — `fly.toml`, `Dockerfile`, and
|
|
108
|
+
`litestream.yml` for continuous SQLite backup to your own object storage:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
fly apps create woolroom
|
|
112
|
+
fly volumes create woolroom_data --region sjc --size 1
|
|
113
|
+
fly storage create woolroom-litestream
|
|
114
|
+
fly secrets set SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
|
|
115
|
+
fly deploy
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Local development
|
|
119
|
+
|
|
120
|
+
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/):
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
uv sync --extra dev
|
|
124
|
+
.venv/bin/uvicorn app.main:app --reload
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Run the tests with `.venv/bin/python -m pytest tests -q` — the suite is
|
|
128
|
+
hermetic: no services, no keys, no network.
|
|
129
|
+
|
|
130
|
+
Every path above works with zero API keys. All configuration is environment
|
|
131
|
+
variables; [.env.example](.env.example) documents each one, including the
|
|
132
|
+
optional site-access password for a private deployment.
|
|
133
|
+
|
|
134
|
+
### Database upgrades and adoption
|
|
135
|
+
|
|
136
|
+
Container startup runs the same fail-closed migration API exposed by the
|
|
137
|
+
installed `woolroom-db` command. A normal upgrade accepts only a new empty
|
|
138
|
+
SQLite file or a database carrying exactly one revision known to the installed
|
|
139
|
+
Woolroom distribution. Read-only SQLite integrity and core foreign-key checks
|
|
140
|
+
must also pass:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
DATABASE_URL=sqlite+aiosqlite:///./woolroom.db woolroom-db inspect
|
|
144
|
+
DATABASE_URL=sqlite+aiosqlite:///./woolroom.db woolroom-db upgrade
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A current Woolroom schema created without Alembic is never stamped
|
|
148
|
+
automatically. Adoption first performs a read-only semantic comparison; the
|
|
149
|
+
command is a dry run unless `--apply` is explicit:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
DATABASE_URL=sqlite+aiosqlite:///./copied.db woolroom-db adopt
|
|
153
|
+
DATABASE_URL=sqlite+aiosqlite:///./copied.db woolroom-db adopt --apply
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The comparison covers core columns, defaults, ordered primary keys, logical
|
|
157
|
+
indexes and uniques, foreign keys, checks, and triggers while ignoring physical
|
|
158
|
+
column order and constraint names. Extra plugin-owned tables are allowed and
|
|
159
|
+
preserved; any change to a core table refuses adoption. Inspect and adopt a
|
|
160
|
+
copy before changing a deployed database.
|
|
161
|
+
|
|
162
|
+
Woolroom ships cat, dog, and pig as public core profiles. Cat remains the
|
|
163
|
+
default; a host can choose the two adoption identities without copying code or
|
|
164
|
+
supplying pack paths:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
docker run --rm -p 8000:8000 \
|
|
168
|
+
-e ADOPT_PRIMARY_SPECIES=dog -e ADOPT_PRIMARY_COAT=red \
|
|
169
|
+
-e ADOPT_SECONDARY_SPECIES=pig -e ADOPT_SECONDARY_COAT=pink \
|
|
170
|
+
woolroom
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Python consumers use the same boundary through
|
|
174
|
+
`woolroom.create_app(adoption_defaults=AdoptionDefaults(...))`; trusted private
|
|
175
|
+
cards and database lookups remain a separate provider concern.
|
|
176
|
+
|
|
177
|
+
### Guest visits
|
|
178
|
+
|
|
179
|
+
A deployment can open a read-only window on its room: set
|
|
180
|
+
`GUEST_ACCESS_ENABLED=true` and pin `GUEST_PET_ID` to a demo pet seeded with
|
|
181
|
+
`scripts/seed_demo_pet.py`. Visitors watch a sanitized scene — only the
|
|
182
|
+
pinned demo pet is ever resolvable, never a real household's room. The
|
|
183
|
+
public demo above is exactly this.
|
|
184
|
+
|
|
185
|
+
### Limits, by design
|
|
186
|
+
|
|
187
|
+
One process, one household per instance. The live channel registry is
|
|
188
|
+
in-process, the LLM budget cap is per-process, and SQLite has one writer —
|
|
189
|
+
so a woolroom scales by giving each household its own small instance, not by
|
|
190
|
+
clustering a big one. There is no multi-tenant mode and none planned; a
|
|
191
|
+
home is not a platform.
|
|
192
|
+
|
|
193
|
+
## The three promises
|
|
194
|
+
|
|
195
|
+
- **Author a species in a weekend.** A species is a data pack — YAML plus
|
|
196
|
+
one SVG, no engine code. Copy the example, rename, draw, lint, boot.
|
|
197
|
+
- **Host in one command.** One container or one `fly deploy`; by default,
|
|
198
|
+
nothing is metered or sent to an external model, and the database is a file
|
|
199
|
+
you can copy.
|
|
200
|
+
- **Share by a link.** Your person joins the room through an invite link;
|
|
201
|
+
a species you wrote is shared as a repo link.
|
|
202
|
+
|
|
203
|
+
## Packs
|
|
204
|
+
|
|
205
|
+
A pack adds a species — figure, temperament, coats, voice, habits — as data
|
|
206
|
+
the loader validates behind fail-closed gates at boot. Packs are data,
|
|
207
|
+
never code: no scripting, no CSS, no runtime download.
|
|
208
|
+
|
|
209
|
+
- The authoring guide is [docs/packs.md](docs/packs.md).
|
|
210
|
+
- `app/packs/profiles/dog` and `app/packs/profiles/pig` are packaged public
|
|
211
|
+
profiles; their species ids are reserved and always available to hosts.
|
|
212
|
+
- [packs/pebble](packs/pebble) is the shipped example — a pet rock,
|
|
213
|
+
deliberately minimal.
|
|
214
|
+
|
|
215
|
+
Start a pack from any directory without cloning Woolroom or permanently
|
|
216
|
+
installing its authoring tools:
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
uvx woolpack new mole --author "Your Name" --license MIT
|
|
220
|
+
uvx woolpack render packs/mole -o mole-board.html
|
|
221
|
+
uvx woolpack lint packs/mole --strict
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The scaffold copies the example with every file stem already renamed to your
|
|
225
|
+
id (stems are ids — a bare copy collides at boot). Render draws every coat in
|
|
226
|
+
every pose plus the touch-hitbox overlay; strict lint runs the contract suite
|
|
227
|
+
and treats warnings as failures. If lint is green and the board looks right,
|
|
228
|
+
the pack is ready for a Woolroom boot test. Woolroom revalidates every
|
|
229
|
+
configured pack together, so cross-pack identifier collisions can still refuse
|
|
230
|
+
boot. Contributors working inside this checkout can use the equivalent
|
|
231
|
+
`scripts/pack_new.py`, `scripts/pack_render.py`, and `scripts/pack_lint.py`
|
|
232
|
+
compatibility shims after `uv sync --extra dev`.
|
|
233
|
+
|
|
234
|
+
<img src=".github/assets/pebble.png" width="300" alt="pebble, the example species, on the room floor — a smooth gray rock with dot eyes and a pale belly" />
|
|
235
|
+
|
|
236
|
+
Packs live in their authors' own repositories. The community index is
|
|
237
|
+
[woolroom-packs](https://github.com/minglong51/woolroom-packs) — one line per
|
|
238
|
+
pack, added by PR; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
239
|
+
|
|
240
|
+
## Releasing
|
|
241
|
+
|
|
242
|
+
Woolroom and Woolpack keep the same package version, but publish through
|
|
243
|
+
separate trusted workflows. Publish Woolpack first; a `woolroom-v<version>`
|
|
244
|
+
GitHub release is accepted only when the tag matches root metadata, resolves to
|
|
245
|
+
an `origin/main` ancestor, and the matching Woolpack version is already on the
|
|
246
|
+
public package index. An existing Woolroom release is accepted only when every
|
|
247
|
+
present filename and hash matches the local build: an exact partial release can
|
|
248
|
+
resume, an exact
|
|
249
|
+
complete release is verified as-is, and any conflict is refused. The workflow
|
|
250
|
+
builds and inspects both Woolroom artifact formats, tests them beside an exact
|
|
251
|
+
local Woolpack wheel, and gives only the environment-gated publish job an OIDC
|
|
252
|
+
identity. Creating the workflow does not publish or bump the current version.
|
|
253
|
+
|
|
254
|
+
## Status
|
|
255
|
+
|
|
256
|
+
Maintained-lite. The engine is feature-complete for v1 and under test, but
|
|
257
|
+
responses to issues and pack submissions may be slow — days, not hours. If
|
|
258
|
+
there is no external pack or issue activity by 2027-03-01, the repo moves to
|
|
259
|
+
reference maintenance: a designed state, not a failure. The authoring loop
|
|
260
|
+
pays for itself even at zero external packs.
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
Code is [MIT](LICENSE). Bundled public pet profiles and the Woolpack template
|
|
265
|
+
are [CC0-1.0](packages/woolpack/LICENSE-CC0).
|
woolroom-0.3.0/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# woolroom
|
|
2
|
+
|
|
3
|
+
**Anyone can have a pet that is really theirs on the internet — alive when
|
|
4
|
+
nobody is looking, shared with their people, running in a home they own.**
|
|
5
|
+
|
|
6
|
+
<img src=".github/assets/room-day.png" alt="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" />
|
|
7
|
+
|
|
8
|
+
woolroom is a self-hostable shared ambient pet: one quiet animal in a small
|
|
9
|
+
room, kept by two people. It runs on a rule-driven brain — mood drift,
|
|
10
|
+
memory, seeded daily outings, a phrasebook keyed to how it actually feels —
|
|
11
|
+
so it stays alive when the tab is closed and costs zero inference spend by
|
|
12
|
+
default. An optional LLM lane (Anthropic, or a local model via Ollama)
|
|
13
|
+
narrates richer utterances; it is opt-in, budget-capped, and the pet is
|
|
14
|
+
fully itself without a key. The design rationale is written up in
|
|
15
|
+
[a coherent virtual pet without an LLM](https://www.minglongpan.com/writing/a-coherent-virtual-pet-without-an-llm).
|
|
16
|
+
|
|
17
|
+
**Visit a room right now:** [woolroom-demo.fly.dev](https://woolroom-demo.fly.dev)
|
|
18
|
+
— tap *watch the room* to slip in as a read-only guest. No account, nothing
|
|
19
|
+
to install; it is a real instance of the engine below, breathing on its own.
|
|
20
|
+
|
|
21
|
+
There are no scores, streaks, meters, or notifications. That is not a
|
|
22
|
+
setting — the rig has no surface for them.
|
|
23
|
+
|
|
24
|
+
<table>
|
|
25
|
+
<tr>
|
|
26
|
+
<td width="50%"><img src=".github/assets/room-night.png" alt="the same room after nine at night — dark walls, a moon where the button sun was, the cat asleep, one lamp still warm" /></td>
|
|
27
|
+
<td width="50%"><img src=".github/assets/join-loop.gif" alt="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" /></td>
|
|
28
|
+
</tr>
|
|
29
|
+
</table>
|
|
30
|
+
|
|
31
|
+
*The room keeps its own hours, and it breathes whether or not anyone is
|
|
32
|
+
watching. On the right: the second human joining — the other lamp takes
|
|
33
|
+
her name, and the cat picks its head up for it.*
|
|
34
|
+
|
|
35
|
+
v1 is the pair: one pet, the same soul on every screen, two humans sharing a
|
|
36
|
+
room. No email, no passwords — your person joins by invite link and picks a
|
|
37
|
+
name. The data is a SQLite file on your own disk.
|
|
38
|
+
|
|
39
|
+
## Run it
|
|
40
|
+
|
|
41
|
+
### Docker
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
docker build -t woolroom .
|
|
45
|
+
docker run --rm -p 8000:8000 woolroom
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then open http://localhost:8000. To keep the pet's data across containers,
|
|
49
|
+
give it a volume:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
docker run --rm -p 8000:8000 \
|
|
53
|
+
-v woolroom-data:/data \
|
|
54
|
+
woolroom
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The image uses `/data/woolroom.db` for the app, migrations, and optional
|
|
58
|
+
Litestream replication. A downstream image can select another file and its own
|
|
59
|
+
composition module without replacing the entrypoint. For example, after that
|
|
60
|
+
image is built as `my-woolroom`:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
docker run --rm -p 8000:8000 \
|
|
64
|
+
-v woolroom-data:/data \
|
|
65
|
+
-e WOOLROOM_DB_PATH=/data/custom.db \
|
|
66
|
+
-e WOOLROOM_ASGI_APP=deployment.app:application \
|
|
67
|
+
my-woolroom
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`DATABASE_URL` remains accepted for compatibility. If it and
|
|
71
|
+
`WOOLROOM_DB_PATH` are both set, they must identify the same absolute SQLite
|
|
72
|
+
file or the container refuses to restore, migrate, or boot. The ASGI target is
|
|
73
|
+
always served with one worker.
|
|
74
|
+
|
|
75
|
+
### fly.io
|
|
76
|
+
|
|
77
|
+
The repo ships a ready template — `fly.toml`, `Dockerfile`, and
|
|
78
|
+
`litestream.yml` for continuous SQLite backup to your own object storage:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
fly apps create woolroom
|
|
82
|
+
fly volumes create woolroom_data --region sjc --size 1
|
|
83
|
+
fly storage create woolroom-litestream
|
|
84
|
+
fly secrets set SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
|
|
85
|
+
fly deploy
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Local development
|
|
89
|
+
|
|
90
|
+
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/):
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
uv sync --extra dev
|
|
94
|
+
.venv/bin/uvicorn app.main:app --reload
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Run the tests with `.venv/bin/python -m pytest tests -q` — the suite is
|
|
98
|
+
hermetic: no services, no keys, no network.
|
|
99
|
+
|
|
100
|
+
Every path above works with zero API keys. All configuration is environment
|
|
101
|
+
variables; [.env.example](.env.example) documents each one, including the
|
|
102
|
+
optional site-access password for a private deployment.
|
|
103
|
+
|
|
104
|
+
### Database upgrades and adoption
|
|
105
|
+
|
|
106
|
+
Container startup runs the same fail-closed migration API exposed by the
|
|
107
|
+
installed `woolroom-db` command. A normal upgrade accepts only a new empty
|
|
108
|
+
SQLite file or a database carrying exactly one revision known to the installed
|
|
109
|
+
Woolroom distribution. Read-only SQLite integrity and core foreign-key checks
|
|
110
|
+
must also pass:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
DATABASE_URL=sqlite+aiosqlite:///./woolroom.db woolroom-db inspect
|
|
114
|
+
DATABASE_URL=sqlite+aiosqlite:///./woolroom.db woolroom-db upgrade
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A current Woolroom schema created without Alembic is never stamped
|
|
118
|
+
automatically. Adoption first performs a read-only semantic comparison; the
|
|
119
|
+
command is a dry run unless `--apply` is explicit:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
DATABASE_URL=sqlite+aiosqlite:///./copied.db woolroom-db adopt
|
|
123
|
+
DATABASE_URL=sqlite+aiosqlite:///./copied.db woolroom-db adopt --apply
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The comparison covers core columns, defaults, ordered primary keys, logical
|
|
127
|
+
indexes and uniques, foreign keys, checks, and triggers while ignoring physical
|
|
128
|
+
column order and constraint names. Extra plugin-owned tables are allowed and
|
|
129
|
+
preserved; any change to a core table refuses adoption. Inspect and adopt a
|
|
130
|
+
copy before changing a deployed database.
|
|
131
|
+
|
|
132
|
+
Woolroom ships cat, dog, and pig as public core profiles. Cat remains the
|
|
133
|
+
default; a host can choose the two adoption identities without copying code or
|
|
134
|
+
supplying pack paths:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
docker run --rm -p 8000:8000 \
|
|
138
|
+
-e ADOPT_PRIMARY_SPECIES=dog -e ADOPT_PRIMARY_COAT=red \
|
|
139
|
+
-e ADOPT_SECONDARY_SPECIES=pig -e ADOPT_SECONDARY_COAT=pink \
|
|
140
|
+
woolroom
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Python consumers use the same boundary through
|
|
144
|
+
`woolroom.create_app(adoption_defaults=AdoptionDefaults(...))`; trusted private
|
|
145
|
+
cards and database lookups remain a separate provider concern.
|
|
146
|
+
|
|
147
|
+
### Guest visits
|
|
148
|
+
|
|
149
|
+
A deployment can open a read-only window on its room: set
|
|
150
|
+
`GUEST_ACCESS_ENABLED=true` and pin `GUEST_PET_ID` to a demo pet seeded with
|
|
151
|
+
`scripts/seed_demo_pet.py`. Visitors watch a sanitized scene — only the
|
|
152
|
+
pinned demo pet is ever resolvable, never a real household's room. The
|
|
153
|
+
public demo above is exactly this.
|
|
154
|
+
|
|
155
|
+
### Limits, by design
|
|
156
|
+
|
|
157
|
+
One process, one household per instance. The live channel registry is
|
|
158
|
+
in-process, the LLM budget cap is per-process, and SQLite has one writer —
|
|
159
|
+
so a woolroom scales by giving each household its own small instance, not by
|
|
160
|
+
clustering a big one. There is no multi-tenant mode and none planned; a
|
|
161
|
+
home is not a platform.
|
|
162
|
+
|
|
163
|
+
## The three promises
|
|
164
|
+
|
|
165
|
+
- **Author a species in a weekend.** A species is a data pack — YAML plus
|
|
166
|
+
one SVG, no engine code. Copy the example, rename, draw, lint, boot.
|
|
167
|
+
- **Host in one command.** One container or one `fly deploy`; by default,
|
|
168
|
+
nothing is metered or sent to an external model, and the database is a file
|
|
169
|
+
you can copy.
|
|
170
|
+
- **Share by a link.** Your person joins the room through an invite link;
|
|
171
|
+
a species you wrote is shared as a repo link.
|
|
172
|
+
|
|
173
|
+
## Packs
|
|
174
|
+
|
|
175
|
+
A pack adds a species — figure, temperament, coats, voice, habits — as data
|
|
176
|
+
the loader validates behind fail-closed gates at boot. Packs are data,
|
|
177
|
+
never code: no scripting, no CSS, no runtime download.
|
|
178
|
+
|
|
179
|
+
- The authoring guide is [docs/packs.md](docs/packs.md).
|
|
180
|
+
- `app/packs/profiles/dog` and `app/packs/profiles/pig` are packaged public
|
|
181
|
+
profiles; their species ids are reserved and always available to hosts.
|
|
182
|
+
- [packs/pebble](packs/pebble) is the shipped example — a pet rock,
|
|
183
|
+
deliberately minimal.
|
|
184
|
+
|
|
185
|
+
Start a pack from any directory without cloning Woolroom or permanently
|
|
186
|
+
installing its authoring tools:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
uvx woolpack new mole --author "Your Name" --license MIT
|
|
190
|
+
uvx woolpack render packs/mole -o mole-board.html
|
|
191
|
+
uvx woolpack lint packs/mole --strict
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The scaffold copies the example with every file stem already renamed to your
|
|
195
|
+
id (stems are ids — a bare copy collides at boot). Render draws every coat in
|
|
196
|
+
every pose plus the touch-hitbox overlay; strict lint runs the contract suite
|
|
197
|
+
and treats warnings as failures. If lint is green and the board looks right,
|
|
198
|
+
the pack is ready for a Woolroom boot test. Woolroom revalidates every
|
|
199
|
+
configured pack together, so cross-pack identifier collisions can still refuse
|
|
200
|
+
boot. Contributors working inside this checkout can use the equivalent
|
|
201
|
+
`scripts/pack_new.py`, `scripts/pack_render.py`, and `scripts/pack_lint.py`
|
|
202
|
+
compatibility shims after `uv sync --extra dev`.
|
|
203
|
+
|
|
204
|
+
<img src=".github/assets/pebble.png" width="300" alt="pebble, the example species, on the room floor — a smooth gray rock with dot eyes and a pale belly" />
|
|
205
|
+
|
|
206
|
+
Packs live in their authors' own repositories. The community index is
|
|
207
|
+
[woolroom-packs](https://github.com/minglong51/woolroom-packs) — one line per
|
|
208
|
+
pack, added by PR; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
209
|
+
|
|
210
|
+
## Releasing
|
|
211
|
+
|
|
212
|
+
Woolroom and Woolpack keep the same package version, but publish through
|
|
213
|
+
separate trusted workflows. Publish Woolpack first; a `woolroom-v<version>`
|
|
214
|
+
GitHub release is accepted only when the tag matches root metadata, resolves to
|
|
215
|
+
an `origin/main` ancestor, and the matching Woolpack version is already on the
|
|
216
|
+
public package index. An existing Woolroom release is accepted only when every
|
|
217
|
+
present filename and hash matches the local build: an exact partial release can
|
|
218
|
+
resume, an exact
|
|
219
|
+
complete release is verified as-is, and any conflict is refused. The workflow
|
|
220
|
+
builds and inspects both Woolroom artifact formats, tests them beside an exact
|
|
221
|
+
local Woolpack wheel, and gives only the environment-gated publish job an OIDC
|
|
222
|
+
identity. Creating the workflow does not publish or bump the current version.
|
|
223
|
+
|
|
224
|
+
## Status
|
|
225
|
+
|
|
226
|
+
Maintained-lite. The engine is feature-complete for v1 and under test, but
|
|
227
|
+
responses to issues and pack submissions may be slow — days, not hours. If
|
|
228
|
+
there is no external pack or issue activity by 2027-03-01, the repo moves to
|
|
229
|
+
reference maintenance: a designed state, not a failure. The authoring loop
|
|
230
|
+
pays for itself even at zero external packs.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
Code is [MIT](LICENSE). Bundled public pet profiles and the Woolpack template
|
|
235
|
+
are [CC0-1.0](packages/woolpack/LICENSE-CC0).
|
|
File without changes
|
|
File without changes
|