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.
Files changed (149) hide show
  1. woolroom-0.3.0/LICENSE +21 -0
  2. woolroom-0.3.0/PKG-INFO +265 -0
  3. woolroom-0.3.0/README.md +235 -0
  4. woolroom-0.3.0/app/__init__.py +0 -0
  5. woolroom-0.3.0/app/api/__init__.py +0 -0
  6. woolroom-0.3.0/app/api/admin.py +396 -0
  7. woolroom-0.3.0/app/api/deps.py +78 -0
  8. woolroom-0.3.0/app/api/http.py +1221 -0
  9. woolroom-0.3.0/app/api/ws.py +232 -0
  10. woolroom-0.3.0/app/auth/__init__.py +0 -0
  11. woolroom-0.3.0/app/auth/session.py +46 -0
  12. woolroom-0.3.0/app/auth/site_access.py +128 -0
  13. woolroom-0.3.0/app/channels/__init__.py +0 -0
  14. woolroom-0.3.0/app/channels/base.py +17 -0
  15. woolroom-0.3.0/app/channels/webapp.py +341 -0
  16. woolroom-0.3.0/app/config.py +163 -0
  17. woolroom-0.3.0/app/data/__init__.py +0 -0
  18. woolroom-0.3.0/app/data/body_language.py +1015 -0
  19. woolroom-0.3.0/app/data/quirks_catalog.py +253 -0
  20. woolroom-0.3.0/app/data/species.py +83 -0
  21. woolroom-0.3.0/app/data/voice.py +302 -0
  22. woolroom-0.3.0/app/engine/__init__.py +0 -0
  23. woolroom-0.3.0/app/engine/aging.py +120 -0
  24. woolroom-0.3.0/app/engine/mood.py +132 -0
  25. woolroom-0.3.0/app/engine/outings.py +92 -0
  26. woolroom-0.3.0/app/engine/quirks.py +286 -0
  27. woolroom-0.3.0/app/eval/__init__.py +16 -0
  28. woolroom-0.3.0/app/eval/corpus.py +110 -0
  29. woolroom-0.3.0/app/eval/runner.py +255 -0
  30. woolroom-0.3.0/app/main.py +511 -0
  31. woolroom-0.3.0/app/memory/__init__.py +0 -0
  32. woolroom-0.3.0/app/memory/buffer.py +112 -0
  33. woolroom-0.3.0/app/memory/core.py +44 -0
  34. woolroom-0.3.0/app/memory/moments.py +206 -0
  35. woolroom-0.3.0/app/packs/__init__.py +52 -0
  36. woolroom-0.3.0/app/packs/lint.py +3 -0
  37. woolroom-0.3.0/app/packs/loader.py +237 -0
  38. woolroom-0.3.0/app/packs/profiles/dog/pack.yaml +5 -0
  39. woolroom-0.3.0/app/packs/profiles/dog/phrases/dog.yaml +536 -0
  40. woolroom-0.3.0/app/packs/profiles/dog/species/dog.svg +56 -0
  41. woolroom-0.3.0/app/packs/profiles/dog/species/dog.yaml +36 -0
  42. woolroom-0.3.0/app/packs/profiles/dog/voice.yaml +5 -0
  43. woolroom-0.3.0/app/packs/profiles/pig/pack.yaml +5 -0
  44. woolroom-0.3.0/app/packs/profiles/pig/phrases/pig.yaml +536 -0
  45. woolroom-0.3.0/app/packs/profiles/pig/species/pig.svg +53 -0
  46. woolroom-0.3.0/app/packs/profiles/pig/species/pig.yaml +30 -0
  47. woolroom-0.3.0/app/packs/profiles/pig/voice.yaml +4 -0
  48. woolroom-0.3.0/app/packs/sanitize.py +39 -0
  49. woolroom-0.3.0/app/room_contract.py +92 -0
  50. woolroom-0.3.0/app/runtime/__init__.py +0 -0
  51. woolroom-0.3.0/app/runtime/actions.py +336 -0
  52. woolroom-0.3.0/app/runtime/client.py +165 -0
  53. woolroom-0.3.0/app/runtime/llm_log.py +175 -0
  54. woolroom-0.3.0/app/runtime/pet_state.py +381 -0
  55. woolroom-0.3.0/app/runtime/prompt.py +103 -0
  56. woolroom-0.3.0/app/runtime/respond.py +235 -0
  57. woolroom-0.3.0/app/runtime/scene_fx.py +237 -0
  58. woolroom-0.3.0/app/runtime/shared_trace.py +38 -0
  59. woolroom-0.3.0/app/runtime/validator.py +75 -0
  60. woolroom-0.3.0/app/runtime/visits.py +78 -0
  61. woolroom-0.3.0/app/scheduler/__init__.py +0 -0
  62. woolroom-0.3.0/app/scheduler/jobs.py +319 -0
  63. woolroom-0.3.0/app/static/access.html +172 -0
  64. woolroom-0.3.0/app/static/app.js +70 -0
  65. woolroom-0.3.0/app/static/apple-touch-icon.png +0 -0
  66. woolroom-0.3.0/app/static/favicon.svg +14 -0
  67. woolroom-0.3.0/app/static/icon-192.png +0 -0
  68. woolroom-0.3.0/app/static/icon-512-maskable.png +0 -0
  69. woolroom-0.3.0/app/static/icon-512.png +0 -0
  70. woolroom-0.3.0/app/static/index.html +946 -0
  71. woolroom-0.3.0/app/static/js/api.js +632 -0
  72. woolroom-0.3.0/app/static/js/figures.js +245 -0
  73. woolroom-0.3.0/app/static/js/memory.js +55 -0
  74. woolroom-0.3.0/app/static/js/presence.js +188 -0
  75. woolroom-0.3.0/app/static/js/quirks.js +195 -0
  76. woolroom-0.3.0/app/static/js/sound.js +316 -0
  77. woolroom-0.3.0/app/static/js/state.js +117 -0
  78. woolroom-0.3.0/app/static/js/ui.js +86 -0
  79. woolroom-0.3.0/app/static/js/wool.js +615 -0
  80. woolroom-0.3.0/app/static/js/woolevents.js +508 -0
  81. woolroom-0.3.0/app/static/js/woolfx.js +329 -0
  82. woolroom-0.3.0/app/static/js/woolvisits.js +146 -0
  83. woolroom-0.3.0/app/static/js/ws.js +303 -0
  84. woolroom-0.3.0/app/static/manifest.json +17 -0
  85. woolroom-0.3.0/app/static/style.css +1647 -0
  86. woolroom-0.3.0/app/static/vendor/alpine-3.14.1.min.js +5 -0
  87. woolroom-0.3.0/app/storage/__init__.py +0 -0
  88. woolroom-0.3.0/app/storage/db.py +30 -0
  89. woolroom-0.3.0/app/storage/models.py +222 -0
  90. woolroom-0.3.0/app/storage/repo.py +707 -0
  91. woolroom-0.3.0/app/time.py +46 -0
  92. woolroom-0.3.0/packages/woolpack/LICENSE-CC0 +121 -0
  93. woolroom-0.3.0/pyproject.toml +69 -0
  94. woolroom-0.3.0/setup.cfg +4 -0
  95. woolroom-0.3.0/tests/test_access_client.py +279 -0
  96. woolroom-0.3.0/tests/test_adoption_defaults.py +266 -0
  97. woolroom-0.3.0/tests/test_aging.py +59 -0
  98. woolroom-0.3.0/tests/test_app_flows.py +1239 -0
  99. woolroom-0.3.0/tests/test_auth_namespace.py +255 -0
  100. woolroom-0.3.0/tests/test_client.py +94 -0
  101. woolroom-0.3.0/tests/test_config.py +54 -0
  102. woolroom-0.3.0/tests/test_container_entrypoint.py +199 -0
  103. woolroom-0.3.0/tests/test_database_boundary.py +477 -0
  104. woolroom-0.3.0/tests/test_demo_self_play.py +167 -0
  105. woolroom-0.3.0/tests/test_design_docs.py +323 -0
  106. woolroom-0.3.0/tests/test_distribution_boundary.py +167 -0
  107. woolroom-0.3.0/tests/test_dog_pack.py +111 -0
  108. woolroom-0.3.0/tests/test_guest_mode.py +410 -0
  109. woolroom-0.3.0/tests/test_household.py +319 -0
  110. woolroom-0.3.0/tests/test_llm_lane.py +155 -0
  111. woolroom-0.3.0/tests/test_migrations.py +76 -0
  112. woolroom-0.3.0/tests/test_mood.py +33 -0
  113. woolroom-0.3.0/tests/test_outings.py +41 -0
  114. woolroom-0.3.0/tests/test_overlay_provider.py +1142 -0
  115. woolroom-0.3.0/tests/test_pack_lint.py +338 -0
  116. woolroom-0.3.0/tests/test_pack_pebble.py +183 -0
  117. woolroom-0.3.0/tests/test_packs.py +772 -0
  118. woolroom-0.3.0/tests/test_packs_client.py +170 -0
  119. woolroom-0.3.0/tests/test_phrase_golden.py +263 -0
  120. woolroom-0.3.0/tests/test_pig_pack.py +131 -0
  121. woolroom-0.3.0/tests/test_quirk_registry.py +115 -0
  122. woolroom-0.3.0/tests/test_quirks.py +107 -0
  123. woolroom-0.3.0/tests/test_release_workflow.py +194 -0
  124. woolroom-0.3.0/tests/test_respond.py +112 -0
  125. woolroom-0.3.0/tests/test_room_client.py +296 -0
  126. woolroom-0.3.0/tests/test_room_contract.py +96 -0
  127. woolroom-0.3.0/tests/test_species_registry.py +74 -0
  128. woolroom-0.3.0/tests/test_time.py +23 -0
  129. woolroom-0.3.0/tests/test_voice_client.py +135 -0
  130. woolroom-0.3.0/tests/test_woolpack_cards.py +196 -0
  131. woolroom-0.3.0/tests/test_ws_auth.py +80 -0
  132. woolroom-0.3.0/woolroom/__init__.py +78 -0
  133. woolroom-0.3.0/woolroom/adoption.py +33 -0
  134. woolroom-0.3.0/woolroom/auth.py +54 -0
  135. woolroom-0.3.0/woolroom/database.py +753 -0
  136. woolroom-0.3.0/woolroom/migrations/README +3 -0
  137. woolroom-0.3.0/woolroom/migrations/__init__.py +1 -0
  138. woolroom-0.3.0/woolroom/migrations/env.py +114 -0
  139. woolroom-0.3.0/woolroom/migrations/script.py.mako +28 -0
  140. woolroom-0.3.0/woolroom/migrations/versions/48575234f9ca_initial_schema.py +196 -0
  141. woolroom-0.3.0/woolroom/migrations/versions/__init__.py +1 -0
  142. woolroom-0.3.0/woolroom/overlay.py +114 -0
  143. woolroom-0.3.0/woolroom/py.typed +1 -0
  144. woolroom-0.3.0/woolroom.egg-info/PKG-INFO +265 -0
  145. woolroom-0.3.0/woolroom.egg-info/SOURCES.txt +147 -0
  146. woolroom-0.3.0/woolroom.egg-info/dependency_links.txt +1 -0
  147. woolroom-0.3.0/woolroom.egg-info/entry_points.txt +2 -0
  148. woolroom-0.3.0/woolroom.egg-info/requires.txt +20 -0
  149. 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.
@@ -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).
@@ -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