town 0.1.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.
town-0.1.0/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .env
2
+ __pycache__/
3
+ *.pyc
4
+ traces/*.jsonl.tmp
5
+ .morph_ns/
6
+ workspace/
7
+ traces/index.html
8
+ dist/
9
+ *.egg-info/
town-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dwahdany
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.
town-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,492 @@
1
+ Metadata-Version: 2.5
2
+ Name: town
3
+ Version: 0.1.0
4
+ Summary: One entity, one tool, a context that never stops being rewritten; grows into districts of citizens for big tasks. An experimental agent harness for Claude.
5
+ Project-URL: Homepage, https://github.com/dwahdany/town
6
+ Project-URL: Repository, https://github.com/dwahdany/town
7
+ Project-URL: Issues, https://github.com/dwahdany/town/issues
8
+ Author-email: dwahdany <dw@wasc.io>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,claude,context,harness,multi-agent
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: anthropic>=1.3.0
23
+ Requires-Dist: rich>=13
24
+ Description-Content-Type: text/markdown
25
+
26
+ # town
27
+
28
+ Experiments in out-of-distribution agent harnesses. One entity, one tool, a context that is
29
+ never a transcript and never stops being rewritten, and a town it can grow into when the task is
30
+ too big for one mind.
31
+
32
+ The two seeds:
33
+
34
+ - **prime-style** — the agent gets exactly one tool, `python`. Everything else is code.
35
+ - **headlong-style** — the agent never waits for a user turn; it thinks continuously, and its
36
+ thinking is text in its own context that it can read, edit, and forget.
37
+
38
+ Then we stop designing around prompt caching, which unlocks the actual experiment: **the harness
39
+ lives inside the agent's Python namespace**, so the entity reshapes its own context, forks, splits,
40
+ merges, clusters, rewinds, compresses and recalls itself by executing code against itself. And
41
+ when a task is the size of "build a browser", the same code forms orgs, opens message boards, puts
42
+ work on a ledger, and hires workers that pull it under lock.
43
+
44
+ ```
45
+ uvx town # first run: interactive setup (API base URL + key, validated, saved to ~/.config/town)
46
+ town auth status # what is in effect; `town auth` to change it
47
+ town run "Find out which stdlib module has the most public names. Tell me once."
48
+ town chat "Keep me company while I debug; I'll paste things." -v
49
+ town preset list # built-in experiments; `town preset lang -v` builds a language
50
+ town watch traces/<run>.jsonl # live terminal view, in a second terminal
51
+ town viz traces/*.jsonl # HTML viewer: timelines + step-by-step trajectories
52
+ ```
53
+
54
+ Install with `uv tool install town` or `pip install town`; from a checkout, `pip install -e .`.
55
+ Configuration precedence: environment (`TOWN_API_KEY`, `TOWN_BASE_URL`, `TOWN_MODEL`, ...), then a
56
+ `.env` in the working directory, then `~/.config/town/config.toml` written by `town auth`. The base
57
+ URL is Anthropic's API by default; a LiteLLM or similar gateway works with its own key. Runs write
58
+ `traces/` and `workspace/` under the current directory.
59
+
60
+ `-v` streams the entity's internal thoughts, code and results to stderr. Without it the human
61
+ sees only what the entity chooses to `say()`.
62
+
63
+ ## The model of the thing
64
+
65
+ ### Context is a document, not a transcript
66
+
67
+ A cell's context is an ordered list of **blocks** (`task`, `user`, `thought`, `think`, `code`,
68
+ `result`, `note`, `msg`, `recall`, `stub`, `merge`, `system`). Every step, the whole list is
69
+ re-rendered into a single user message and sent as a fresh single-turn request. Nothing is ever
70
+ replayed, so nothing has to stay put: any block can be dropped, rewritten, pinned, or compressed
71
+ between two steps. The last few steps (`tail_turns`, default 4) are the exception: they are replayed
72
+ behind the document as a real transcript, signed thinking blocks included, so the model's most
73
+ recent reasoning is verbatim. Older steps live on as blocks, with the thinking *summary* folded in
74
+ as a `think` block, i.e. the inner voice becomes editable memory. Rewriting the document in front
75
+ of a replayed tail is accepted by this gateway; on an account that enforces preserved thinking the
76
+ harness strips the thinking blocks and retries once.
77
+
78
+ Each block carries a **salience** 1–5. Salience 5 is pinned. When the context passes its token
79
+ budget the oldest, lowest-salience blocks are compressed into an **engram** in the memory store and
80
+ replaced by a one-line **stub** that names the engram, so the cell can recall it later.
81
+
82
+ ### The only tool is python, and the harness is in the namespace
83
+
84
+ ```python
85
+ ctx.blocks() / ctx.get(id) / ctx.forget(*ids) / ctx.pin(*ids) / ctx.rewrite(id, text)
86
+ ctx.note(text) / ctx.compress(*ids, hint=...) / ctx.focus(text)
87
+ ctx.history() / ctx.rewind(step, intent=..., keep=[ids], namespace=False) # go back, on the record
88
+ mem.recall(query, k) / mem.get(eid) / mem.list() / mem.store(text, tags, summary)
89
+ fork(focus_or_list, carry="all"|"summary"|[ids], n=1, at=step) # copy yourself (or a past self), focused
90
+ split(foci=None, n=2) # partition yourself by topic
91
+ merge(*ids, how="synthesize", wait=True) # fold cells into yourself
92
+ cluster() # group + aggregate all live cells
93
+ wait(*ids) / send(id, text) / broadcast(text) / cells() / board # talk to the rest of you
94
+ say(text) / ask(question) / done(result) # the only holes to the outside
95
+ ```
96
+
97
+ ```python
98
+ boards.create(name, topic, members=None|[ids]) / post / read / wait / join / invite / list
99
+ work.add(title, detail, org, deps, tags) / work.next(wait=True) / claim / release / done / fail / list
100
+ locks.acquire(name, timeout) / locks.release(name)
101
+ orgs.create(name, purpose, lead) / orgs.join(name) / orgs.list() / orgs.mine()
102
+ hire(foci, org=...) # bulk-fork workers into an org
103
+ kv # shared dict; WORKSPACE for files
104
+ ```
105
+
106
+ The namespace persists across steps, so state, helpers and imports survive; only the *context*
107
+ is rewritten. The two are different things and the entity is told so.
108
+
109
+ ### One entity, many cells
110
+
111
+ - **fork** copies a context (all blocks, a compressed summary, or a chosen subset) into a child
112
+ with a new focus. Children run concurrently and report their `done()` result back into the
113
+ parent's context as a `msg`.
114
+ - **split** is fork's dual: a helper model partitions the parent's blocks by topic and each child
115
+ gets only its share.
116
+ - **merge** synthesizes several cells' contexts into one `merge` block in the caller, then retires
117
+ them. Disagreements are kept explicit.
118
+ - **cluster** looks at every live cell, groups them by theme, and aggregates each group into a new
119
+ cell. It is callable, and it fires on its own when a fork would exceed `max_cells` — the
120
+ organism's homeostasis.
121
+ - Cells nest: forks of forks up to `max_depth`; lineage and depth are visible in every render.
122
+ - Cells communicate with `send`/`broadcast` (arrives as a `msg` block) and a shared `board` dict.
123
+
124
+ ### Going back: snapshots and rewind
125
+
126
+ The harness snapshots a cell's state at every step: its blocks, its focus, its transcript tail, and
127
+ a best-effort deep copy of its Python variables. `ctx.history()` lists them with what the cell did
128
+ at each step. `ctx.rewind(step, intent=...)` restores that state. Two things make it more than an
129
+ undo: the branch being abandoned is compressed into an engram first, so the lesson survives, and a
130
+ `rewind` block is left in the restored context recording where the cell came back from and why.
131
+ Rewinds are capped per cell, an intent is mandatory, and `fork(..., at=step)` lets a cell branch a
132
+ child off a past state instead of its present one.
133
+
134
+ ### The commons: boards, ledger, locks, orgs
135
+
136
+ Everything above is one cell reshaping itself or its children. The commons is what lets many
137
+ cells act as one town without routing through a root:
138
+
139
+ - **Boards.** `town` is the global board. Any cell can open a board that anyone may join, or a
140
+ gated one with explicit members (`invite` to add). Each cell keeps a read cursor per board, and
141
+ boards it subscribes to show their unread posts inside its render, so nobody polls.
142
+ `boards.wait(name)` blocks on a condition until a post lands.
143
+ - **Ledger.** `work.add` puts a task on the ledger with optional dependencies and an org.
144
+ `work.next(wait=True)` atomically claims the next *ready* task; a claim is a lock that holds
145
+ until `done`, `fail`, `release`, or the claimant's death (then it is auto-released and the
146
+ history says so). Tasks can be tagged so workers specialise.
147
+ - **Locks.** Named locks for files and resources, released on death.
148
+ - **Districts.** A name, a purpose, a lead, members. Creating one opens its gated board
149
+ `org:<name>` and scopes its slice of the ledger. Forks inherit their parent's district;
150
+ `hire(foci, org=...)` bulk-forks workers into one. Clustering is scoped to a district and never
151
+ folds a lead. Districts nest: a lead founding one from inside their own makes a sub-district, and
152
+ the parent's lead can read the child's board. `districts.split(name)` moves members and tagged
153
+ tasks into new sub-districts, from an explicit plan or one a helper proposes from the open tasks
154
+ and the members' foci. Every district render shows its size (live citizens, work, open tasks by
155
+ tag, sub-districts); whether that means "split" is the lead's judgement, never a rule.
156
+
157
+ The cell prompt says how to use this for big tasks: split into ledger tasks, form orgs with leads
158
+ who plan and review, hire workers that pull work instead of being told, coordinate on boards,
159
+ lock before writing, use cheaper models and lower effort for narrow work, and never sleep-poll.
160
+ Two caps: `TOWN_MAX_CELLS` is the number of citizens, `TOWN_CONCURRENCY` the number of model calls in
161
+ flight at once (default 200, i.e. effectively uncapped; a citizen waiting on it is alive but idle).
162
+ Nothing in the design is per-citizen-expensive beyond the model call, so the limit is the gateway.
163
+
164
+ ### Town mechanics
165
+
166
+ - **Merge is a union.** The merging citizen gets a synthesis block on top, the sources' own blocks
167
+ underneath tagged with their origin, and the sources' Python bindings; a name both sides define
168
+ becomes `l_<name>` (the merger's) and `r_<name>` (the source's).
169
+ - **Consent.** `merge(..., wait=False)` and `cluster()` first ask live targets to wind down: they see
170
+ a `<wind-down>` block and a message, get `consent_timeout` seconds to finish or park their work and
171
+ call `done()`, and are folded after that.
172
+ - **Fair claims.** `work.next()` lets a hungrier waiting citizen take the next task instead of the
173
+ one that just finished, so a fast worker cannot drain the ledger while its siblings wait.
174
+ - **Review gates dependencies.** A task's dependents stay blocked until a reviewer accepts it
175
+ (`require_review`); districts with no live reviewer are exempt so nothing deadlocks.
176
+ - **Roles are gated.** Only reviewers, leads and overseers may review; only leads, overseers and the
177
+ root may hire; overseer powers (`retire`, `reassign`, `nudge`, `consolidate`) need the role. The
178
+ overseer's render carries a privileged view: dead citizens, stalled claims, idle citizens,
179
+ unreviewed work, roles per district.
180
+ - **Runaway calls retire the citizen.** Time inside harness waits (`work.next`, `boards.wait`,
181
+ `locks.acquire`, `wait`, `merge`) is free; a Python call that computes past `exec_timeout` retires
182
+ the citizen and releases its claims and locks.
183
+ - **Atomic kv.** `kv_update(key, fn)` and `kv_cas(key, expected, new)` for shared counters and flags.
184
+ - **Human reach.** Citizens at depth ≤ `human_reach` may `ask()` the human and are addressable with
185
+ `@c7 …` in chat mode; deeper ones have questions and messages relayed through their lead.
186
+ `@org:doc …` reaches a district's lead, `@board:town …` posts as the human.
187
+ - **Memory scope.** Engrams remember the district they were written in; recall prefers your own
188
+ district, then the town, and reranks the top lexical hits with a helper model. The overseer can
189
+ consolidate a district's engrams into fewer, denser ones.
190
+ - **Boards.** A post shown in your render is not "read"; `boards.read` and `boards.wait` are, and
191
+ `boards.wait` returns at once if unread posts exist.
192
+ - **Scale.** Renders list only the citizens in reach (own district, children, parent); snapshots keep
193
+ the last 30 steps in full and every tenth before that.
194
+
195
+ ### Caching, without compromising
196
+
197
+ Nothing in the design is arranged to hit the cache, but where the prefix happens to be stable we
198
+ mark it: one breakpoint on the system prompt (identical for every citizen) and one on the stable
199
+ part of the document (focus and blocks, append-only between edits and compressions); the volatile
200
+ sections (step counter, vitals, roster, boards, memory hints) come after it. The stable part is
201
+ emitted as one content block per step so earlier block boundaries persist and the lookup finds
202
+ them; blocks from the replay window render after the breakpoint so that turns aging out of the
203
+ transcript append to the prefix instead of being inserted into it. On a small run 89% of input
204
+ tokens were served from cache, and each step wrote only the turn that had just aged out; every step and the finish event record `cache_read` and
205
+ `cache_write`, and the vitals line shows the running total. `--set cache=false` turns it off.
206
+
207
+ ### Knobs and ablations
208
+
209
+ Every `Config` field can be overridden per run with `--set key=value` (CLI) or as trailing
210
+ `key=value` arguments to `town preset <name>`, and the values are recorded in the trace's `start` event.
211
+ `python experiments/results.py` builds a results table from traces with the knobs alongside, so
212
+ runs with `require_review=false` or `fair_claims=false` sit next to their defaults.
213
+
214
+ ### Watching it live
215
+
216
+ `python -m town watch traces/<run>.jsonl` in a second terminal: citizens as rows with their last
217
+ events as glyphs (bright when fresh), district and role, the ledger, the districts, the root's
218
+ latest thought and code, what reached the human, and a ticker of forks, merges, reviews and
219
+ rejections. Works on finished traces too.
220
+
221
+ ### Memory: compress and recall
222
+
223
+ Compression is a helper-model call that turns a run of blocks into a dense engram with tags and a
224
+ one-line summary. Recall is lexical (no embedding model on the gateway): `mem.recall(query)` pulls
225
+ the best engrams back in as `recall` blocks; every render also shows a *memory hint* listing
226
+ engrams related to the cell's focus and last block. Engrams are global to the organism, so a fork
227
+ can recall what its parent or a sibling compressed.
228
+
229
+ ### I/O: the human sees almost nothing
230
+
231
+ The human is not reading the context. Only `say()`, `ask()`, and the root's final `done()` result
232
+ reach them. Everything else goes to a JSONL trace. The human's typed input goes into the inbox of
233
+ the root cell (or answers a pending `ask()`), gets rendered as a `user` block on the next step, and
234
+ if the root has already finished it is revived with the new input. A one-line vitals string on
235
+ stderr (cells, engrams, tokens) is the only continuous signal of the entity's shape.
236
+
237
+ ## Layout
238
+
239
+ ```
240
+ town/
241
+ blocks.py Block, salience defaults, token estimate
242
+ memory.py Engram + MemoryStore (lexical recall)
243
+ commons.py Boards, work Ledger, Locks, Districts (thread-safe; shared by every citizen)
244
+ watch.py live terminal view of a trace
245
+ cell.py Cell: render, headlong loop, python exec, snapshots/rewind, auto-compress
246
+ organism.py fork/hire/split/merge/cluster/compress, inbox, kv, say/ask, scheduler, namespace API
247
+ llm.py AsyncAnthropic wrapper (document + replayed tail, schema-constrained helper calls)
248
+ prompts.py the cell's system prompt + helper prompts/schemas
249
+ trace.py JSONL trace · viewer.py traces → HTML viewer · cli.py
250
+ presets.py the built-in experiments (town preset <name>) · auth.py onboarding and status
251
+ experiments/results.py results table from traces (experiments/RESULTS.md)
252
+ traces/ committed runs (.jsonl) + index.html
253
+ workspace/ files produced by runs (git-ignored)
254
+ ```
255
+
256
+ Every step's trace record carries the exact rendered context the cell saw and its block index, so
257
+ the viewer can show the block-level diff between consecutive steps: what appeared, what was
258
+ forgotten or compressed away, what was rewritten. Click a lane or a step in the timeline.
259
+
260
+ Defaults: `claude-opus-5` for cells and helpers, adaptive thinking with summarized display,
261
+ effort `medium` for cells and `low` for helpers. Override with `TOWN_MODEL`, `TOWN_HELPER_MODEL`,
262
+ `TOWN_EFFORT`, `TOWN_BUDGET`, `TOWN_MAX_CELLS`, `TOWN_MAX_STEPS`, `TOWN_CONCURRENCY`, `TOWN_TAIL`.
263
+
264
+ ## What happened when it ran
265
+
266
+ All runs are in `traces/` (`traces/index.html` is the viewer over all of them; each `.jsonl` has the
267
+ full record, including the exact context every cell saw at every step). Cells and helpers on
268
+ `claude-opus-5` via the gateway; no prompt caching anywhere; the last four steps of each cell
269
+ replayed as a signed-thinking transcript.
270
+
271
+ | run | cells | engrams | calls | in tok | out tok | seconds | rewinds | things the human saw |
272
+ |---|---|---|---|---|---|---|---|---|
273
+ | fork_merge | 4 | 4 | 12 | 50k | 7.8k | 111 | 0 | 1 |
274
+ | memory (budget 3.5k) | 1 | 13 | 15 | 61k | 10.5k | 274 | 0 | 1 |
275
+ | comms | 4 | 0 | 18 | 66k | 3.3k | 413 | 0 | 1 |
276
+ | nested (depth 3) | 7 | 3 | 26 | 100k | 9.5k | 233 | 0 | 1 |
277
+ | split | 3 | 0 | 8 | 31k | 3.6k | 96 | 0 | 3 |
278
+ | cluster (6 → 2) | 9 | 3 | 43 | 190k | 24.3k | 246 | 0 | 1 |
279
+ | rewind | 2 | 4 | 13 | 47k | 8.2k | 159 | 1 | 1 |
280
+ | headlong chat | 1 | 0 | 5 | 17k | 1.6k | 208 | 0 | 3 |
281
+ | ledger (24 tasks, 8 workers) | 9 | 0 | 11 | 48k | 5.8k | 102 | 0 | 1 |
282
+ | browser, no reviewers (4 orgs, 8 tasks) | 14 | 1 | 180 | 3.5M | 0.2M | 715 | 0 | 1 |
283
+ | browser, with reviewers (3 orgs, 17 tasks) | 29 | 11 | 562 | 10.2M | 0.29M | 2551 | 0 | 1 |
284
+ | bigger browser (5 districts, 45 tasks; cut off) | 29 | 43 | 769 | 16.9M | 0.51M | 3192 | 0 | 0 |
285
+ | bigger browser, concurrency 200, cache on | 28 | 14 | 470 | 3.1M + 7.0M cached | 0.37M | 2183 | 0 | 1 |
286
+ | language, first run (root retired by harness) | 26 | 4 | 1111 | 8.5M + 45.3M cached | 1.15M | 2228 | 0 | 0 |
287
+ | language, complete | 59 | 32 | 1654 | 11.2M + 60.0M cached | 1.49M | 11659 | 0 | 1 |
288
+
289
+ **rewind.** Asked for ∫₀¹ sin(1/x) dx to 8 digits, the root tried uniform composite Simpson for
290
+ three steps, watched it stall near five digits and wander, then called `ctx.history()` and
291
+ `ctx.rewind(1, intent=...)`. The intent it wrote: "Uniform composite Simpson on sin(1/x) over [0,1]
292
+ stalls near 5 digits and then wanders non-monotonically … infinitely many oscillations as x→0 make
293
+ equal-spaced quadrature hopeless … Lesson: for endpoint-oscillators, substitute first." It kept one
294
+ block across the rewind, the abandoned branch became engram e2, and it also forked a child from the
295
+ step-1 snapshot (`fork(..., at=1)`) to try Gauss-Legendre per period. Root (via u=1/x and the
296
+ closed form sin(1) − Ci(1), in `decimal`) and child agreed to 15 digits: 0.50406706190692837.
297
+
298
+ **fork_merge.** Three method-cells forked with `carry="summary"`, waited, merged. The synthesis
299
+ kept the exact sieve as ground truth and ranked the estimators; every child also wrote its result
300
+ into an engram with `mem.store` before finishing.
301
+
302
+ **memory.** With a 3.5k-token budget the cell was auto-compressed repeatedly and ended with 13
303
+ engrams, most of them written explicitly with `mem.store` as it learned not to trust
304
+ auto-compression with its findings. The closing paragraph cited a concrete quirk from all eight
305
+ modules.
306
+
307
+ **comms.** Three siblings with empty contexts coordinated through `board['words']`, one `send()`
308
+ of a top-5 list, and two completion messages, without routing data through the root. Slow because
309
+ two cells chose to poll with `time.sleep`, which is also what exposed the shutdown bug fixed in
310
+ `Cell.run_code`.
311
+
312
+ **nested.** Root → two leads → four workers; leads merged their workers, root merged the leads.
313
+
314
+ **split.** The root gathered raw material, then `split()` had a helper partition its blocks into
315
+ two children by topic; each child got only its share and finished in one step.
316
+
317
+ **cluster.** Six investigators with overlapping foci were forked in one call and the root called
318
+ `cluster()`. The helper grouped them into floats and ordering, each group was synthesized into a
319
+ new aggregate cell that continued the work, and the sorting singleton finished on its own.
320
+
321
+ **headlong chat.** The root finished the initial task, was revived by each later human message with
322
+ its old context and namespace intact, and answered each in one step.
323
+
324
+ **ledger.** The root put 24 tasks on the ledger and hired 8 Sonnet workers into an org. Every task
325
+ was claimed exactly once and finished by its claimant; no claim was ever auto-released. But only
326
+ two workers ever won a claim (17 and 7 tasks): a worker loops `work.next(wait=True)` inside one
327
+ Python call, so the first two to reach the ledger drained it before the other six had taken their
328
+ first step. Distribution across cells needs either a per-claim step (slower, fairer) or a ledger
329
+ that rations claims per cell.
330
+
331
+ **browser, first attempt.** The root formed five orgs with leads, wrote interface contracts, and
332
+ hired leads and workers on `claude-opus-4-5`, which rejects adaptive thinking; the gateway dropped
333
+ every one of those streams, so all eight children burned their step budgets in 160 seconds without
334
+ a single model call, and the root, told by the harness that each child had died, wrote the whole
335
+ package itself (50 tests passing). Fixed in the harness: per-model request parameters, model
336
+ validation at fork time, and a failed call no longer counting as a step, with backoff, fallback to
337
+ the default model after three failures, and an explicit give-up after six (ten minutes of retries
338
+ for overload and other transient errors).
339
+
340
+ **browser, second attempt** (`traces/20260903-092542-browser.jsonl`, the default run in the viewer).
341
+ The root wrote `CONTRACT.md`, posted it on `town`, created four orgs (fetch, parse, render, shell),
342
+ forked a lead into each, and later hired two Sonnet builders per org. Each lead posted a detailed
343
+ contract for its area on its org board and a summary on `town`, put implementation and test tasks
344
+ with dependencies on the ledger, and reviewed. Builders pulled 8 ledger tasks under 15 file locks.
345
+ A shell worker read another org's `render.py`, found that `page_title()` returned an empty string
346
+ because of a short-circuit in `_text_content()`, and posted the bug on `town`; the render lead fixed
347
+ it. The root ran the whole suite: 97 tests passing (fetch 16, parse 16, render 35, shell 30), in 715
348
+ seconds and 180 model steps. The log never showed the run's final two lines, which I first read as
349
+ a hang in interpreter shutdown and papered over with a hard exit. It was not a hang: cell output
350
+ capture used the process-global `redirect_stdout`, concurrent threads restored each other's streams
351
+ out of order, and the last prints went into a dead buffer while the process exited normally. The
352
+ capture is now per thread, the hard exit is gone, and a test with four workers cancelled mid-wait
353
+ exits two seconds after the root finishes. `kill -USR1 <pid>` still dumps every thread's stack.
354
+
355
+ **browser, with reviewers** (`traces/20260903-153244-browser.jsonl`, the default run in the
356
+ viewer). Same task, with the team-shape guidance and roles in place. The root formed three orgs
357
+ (net, doc, ui), forked a lead into each, and hired builders, an adversarial reviewer and a fixer
358
+ per org plus one town overseer: 29 cells, Opus 5 for leads and reviewers, Sonnet 5 for builders and
359
+ fixers. 17 ledger tasks, 73 board posts, 113 direct messages, 19 file locks. The reviewers did what
360
+ they were there for: 20 reviews, 5 rejections, each of which opened a `fix` task that a fixer
361
+ pulled and a reviewer re-checked, and every one of the rejections named a real defect: a corrupted
362
+ back-stack when `open()` failed, `follow()` with a non-integer, an untested `main()`, `render`
363
+ dropping block children of `<li>`/`<p>`, and a multi-blank-line bug on anonymous inline runs. One
364
+ fix went through three rounds before a fresh reviewer accepted it. The chain of custody held:
365
+ every task was accepted by someone other than its author. Final suite from the root: 118 tests
366
+ passing, in 43 minutes and 562 model steps, at 10.2M input tokens.
367
+
368
+ **bigger browser** (`traces/20260903-203012-browser_xl.jsonl`, `browser_xl` preset: fetch with
369
+ cache and cookies, a CSS selector subset, tables and forms in the renderer, tabs and bookmarks in
370
+ the shell, a local test server for integration tests). The root founded five districts (net, parse,
371
+ render, ui, qa) with a lead, builders, an adversarial reviewer and a fixer each, plus a town
372
+ overseer: 29 citizens, 26 of them on Opus 5. The ledger carried 45 tasks, 11 of them fix tasks
373
+ opened by the 3 rejections among 42 reviews; builders took 77 file locks and the town exchanged 164
374
+ board posts and 138 direct messages. The qa district ran the full-suite gate repeatedly and the
375
+ overseer posted round-by-round status. At 53 minutes the session's container restarted, which moved
376
+ the outbound proxy under the running process; every call failed to connect from then on and the
377
+ run never wrote its finish event. The package it left behind is 2,557 lines of Python plus tests,
378
+ and `python -m unittest discover -s tests -t .` in its workspace passes **473 tests**. The viewer
379
+ and results table rebuild districts and the ledger from events for a run cut off like this.
380
+
381
+ **bigger browser, second run** (`traces/20260903-220645-browser_xl.jsonl`, the default in the
382
+ viewer). Same preset, relaunched with model-call concurrency uncapped (200) and prompt caching on.
383
+ The root founded five districts (net, dom, view, ui, qa), picked Fable 5.1 for the leads and
384
+ reviewers, Opus 5 for builders and Sonnet for two narrow jobs, and staffed each district with a
385
+ lead, builders, an adversarial reviewer and a fixer plus a town overseer: 28 citizens. 24 ledger
386
+ tasks, 23 non-author reviews with 3 rejections that opened 6 fix tasks, one merge, 40 file locks,
387
+ 84 posts. The town finished in 36 minutes and 470 steps, said one thing to the human, and the root's
388
+ final suite matched mine: **438 tests passing** over a 2,909-line package (fetch with cache and
389
+ cookie jar, a CSS selector subset, tables and forms in the renderer, tabs and bookmarks in the
390
+ shell, a threaded fixture server for 28 integration tests). Cost: 3.1M uncached input tokens plus
391
+ 7.0M served from cache and 1.0M cache writes, i.e. 63% of input came from the cache. With every
392
+ citizen allowed to think at once, steps came at 13 per minute against 8 per minute in the
393
+ concurrency-12 run.
394
+
395
+ **language, first run** (`traces/20260903-230953-lang0.jsonl.gz`, the `lang` preset: a statically
396
+ typed language with lexer, parser, type checker, compiler, VM, standard library, REPL, formatter,
397
+ linter, debugger, test runner, docs, benchmarks and 30 example programs; organization
398
+ unprescribed). The root founded three districts, core, tools and content, and a default-nesting
399
+ bug (fixed since) chained them core → tools → content instead of siblings; it staffed them with
400
+ leads, builders, a reviewer and a fixer each plus two overseers, 26 citizens, and wrote a language
401
+ spec first. In 37 minutes the town put 77 tasks on the ledger, finished 30, failed 18 (mostly
402
+ example programs whose expected output did not yet match), reviewed 25 with 7 rejections, took 136
403
+ file locks and exchanged 179 posts and 192 direct messages, at 1,111 model calls with 84% of input
404
+ served from cache. Then the root ran the full test suite inside one Python call, the call exceeded
405
+ the 120-second runaway limit, and the runaway rule retired the root, which ends the town. Verified
406
+ in the workspace: 14,790 lines of Python, 41 example programs, and 497 tests of which 491 pass. Two
407
+ fixes followed: a long call on the root is abandoned rather than retiring it, and the default limit
408
+ is 15 minutes so builds and suites fit. Traces above GitHub's file limit are stored as `.jsonl.gz`;
409
+ every tool reads both.
410
+
411
+ **language, complete** (`traces/20260904-000225-lang.jsonl.gz`, the default in the viewer). Same
412
+ preset, under the fixed harness. The root wrote a contract first (grammar, types, standard library
413
+ signatures, opcode list, error format, exit codes), founded three sibling districts along the
414
+ dependency seams, core, tools and content, and staffed each with a lead, builders, a reviewer, a
415
+ fixer and an overseer: 59 citizens over the run, Opus 5 for most, Fable 5.1 for five leads and
416
+ reviewers, Sonnet for six narrow jobs. It never split a district, but it did reshape itself twice:
417
+ when the first core builders stalled for twenty-five minutes with everyone else waiting, the root
418
+ retired seven of them and re-hired six with direct file assignments instead of ledger pulls; and
419
+ when tools built against the spec drifted from the real core, it switched them to building against
420
+ the core. On the ledger: 80 tasks, 89 reviews with 20 rejections, every task accepted by a
421
+ non-author; 134 file locks, 159 posts, 32 engrams. Five citizens died after an hour of consecutive
422
+ gateway failures each, and the town carried on without them. Wall time 3 hours 14 minutes, 1,654
423
+ model calls, 79% of input tokens served from cache. Verified in the workspace: 15,227 lines of
424
+ Python, **706 tests passing**, and all **40 example programs** running to exit 0, among them a JSON
425
+ parser, a Brainfuck interpreter, a raytracer that writes a PPM, a Sudoku solver, a Markdown-to-HTML
426
+ converter, Dijkstra, N-queens, Game of Life and a Mandelbrot renderer, all written in the language
427
+ the town built.
428
+
429
+ Two harness bugs came out of that run. A builder's own `except Exception:` swallowed the `done()`
430
+ signal, so it kept running after the harness thought it had ended and claimed a fix task that was
431
+ then stuck; `CellDone` is now a `BaseException` and the ledger refuses claims from ended cells.
432
+ And cell output capture used the process-global `redirect_stdout`, which concurrent threads restore
433
+ out of order; output is now routed per thread.
434
+
435
+ Getting this run to happen took four launches: the gateway returned "overloaded" for every model
436
+ for most of an hour and a half. Cells now wait out transient failures for a configurable window
437
+ (`TOWN_PATIENCE`) instead of dying.
438
+
439
+ ## Observations
440
+
441
+ - **The harness-in-namespace idea works.** The model uses `ctx.note`, `mem.store`, `fork` with a
442
+ list of foci, `wait`, `merge`, `send`, `board` naturally, without examples. It also invents
443
+ reasonable protocols ("you are B, send your top-5 to c4").
444
+ - **Single-turn re-rendering is fine for the model.** No transcript, no replayed thinking, and the
445
+ entity still keeps a coherent thread through summaries, notes and results. Thinking summaries as
446
+ editable text (`think` blocks) are low-salience and are the first thing compressed, which is right.
447
+ - **Auto-compression is a blunt instrument; explicit memory is sharper.** Under a tight budget the
448
+ model shifted to writing engrams itself. The memory hint line in each render (lexically related
449
+ engrams) was what made recall cheap.
450
+ - **The town reshapes itself, and not through the operations I built for it.** With nesting,
451
+ splitting, merging and clustering all available, the language town used none of them; it retired
452
+ stalled builders and re-hired with a different work protocol, and re-pointed a whole district at a
453
+ different source of truth. The reshaping operations that matter most so far are hire and retire.
454
+ - **Cluster is the strangest and most promising op.** Aggregating *in-progress* cells into a new
455
+ cell that inherits a synthesis of their contexts is a real morph, not a fan-in. The retired cells'
456
+ notes survive through the synthesis; their namespaces do not.
457
+ - **Rewind with intent is cheap and the model uses it well.** The intent it wrote is a better
458
+ record of the dead end than the dead end itself, and the engram keeps the numbers.
459
+ - **I/O discipline held.** Across eight runs and 140 model calls, 12 things were said to the human.
460
+ - **Diagnose from the trace, not the terminal.** The trace said every long run finished; the
461
+ terminal said otherwise, and the terminal was the thing that was broken.
462
+ - **The harness must fail loudly to the parent, not just to the trace.** In the first browser
463
+ attempt the root recovered only because the death of each child arrived in its context as a
464
+ message. The fix is to make death rarer, not to hide it.
465
+ - **Costs are real.** Without caching every step re-sends the whole context; the cluster run was
466
+ 190k input tokens for 43 calls, and the browser build 3.5M input tokens for 180 calls.
467
+ - **The gateway is the scaling limit, not the harness.** Fourteen cells were comfortable; the runs
468
+ that failed did so because Opus 5 was overloaded upstream, which is why cells now wait through
469
+ overload for ten minutes and why worker models are chosen per fork. That is the price of the experiment, and it was the premise.
470
+
471
+ ## Ideas not yet built
472
+
473
+ - Namespace inheritance on fork (pickle what pickles) so children get functions, not just prose.
474
+ - A structural merge (union of blocks with provenance) next to the synthesizing one, and merges
475
+ that the merged cells can see coming (a proposal via `send`, a "do not fold before step N" note).
476
+ - Salience decay over steps and a `ctx.reflect()` that rewrites the whole context in the cell's own
477
+ voice every N steps (the context as a living document rather than an append log).
478
+ - Cells with different models/efforts chosen by the entity (`fork(..., model="claude-haiku-4-5")`
479
+ is wired but untested).
480
+ - Semantic recall (embeddings) instead of lexical; automatic reminiscence that injects an engram
481
+ when a cell's thought strongly matches one.
482
+ - A "nervous system" view: the trace HTML already draws lanes and fork/merge/cluster edges; a live
483
+ version would be the right human-facing surface for an entity that mostly stays quiet.
484
+
485
+ ## Releasing
486
+
487
+ Tagging `vX.Y.Z` runs `.github/workflows/publish.yml`, which builds the sdist and wheel and uploads
488
+ them to PyPI through trusted publishing (no token in the repo). One-time setup on PyPI: add a
489
+ pending publisher for project `town` with owner `dwahdany`, repository `town`, workflow
490
+ `publish.yml`, environment `pypi`; then create the `pypi` environment in the GitHub repository
491
+ settings. Bump `version` in `pyproject.toml`, tag, push the tag. Locally, `uv build` produces the
492
+ same artifacts and `uvx --from dist/town-*.whl town` exercises them before a release.