Metadata-Version: 2.5
Name: town
Version: 0.2.0
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.
Project-URL: Homepage, https://github.com/dwahdany/town
Project-URL: Repository, https://github.com/dwahdany/town
Project-URL: Issues, https://github.com/dwahdany/town/issues
Author-email: dwahdany <dw@wasc.io>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,claude,context,harness,multi-agent
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Requires-Dist: anthropic>=1.3.0
Requires-Dist: rich>=13
Requires-Dist: textual>=8.2.8
Description-Content-Type: text/markdown

# town

Experiments in out-of-distribution agent harnesses. One entity, one tool, a context that is
never a transcript and never stops being rewritten, and a town it can grow into when the task is
too big for one mind.

The two seeds:

- **prime-style** — the agent gets exactly one tool, `python`. Everything else is code.
- **headlong-style** — the agent never waits for a user turn; it thinks continuously, and its
  thinking is text in its own context that it can read, edit, and forget.

Then we stop designing around prompt caching, which unlocks the actual experiment: **the harness
lives inside the agent's Python namespace**, so the entity reshapes its own context, forks, splits,
merges, clusters, rewinds, compresses and recalls itself by executing code against itself. And
when a task is the size of "build a browser", the same code forms orgs, opens message boards, puts
work on a ledger, and hires workers that pull it under lock.

```
uvx town                     # first run: interactive setup (API base URL + key, validated, saved to ~/.config/town)
town auth status             # what is in effect; `town auth` to change it
town                         # open the chat here; your first line is the task
town run "Find out which stdlib module has the most public names. Tell me once."
town chat "Keep me company while I debug; I'll paste things." -v
town -c                      # continue the most recent session that worked in this folder
town -r                      # pick any session to resume (offers to cd to its folder first)
town sessions                # list them
town preset list             # built-in experiments; `town preset lang -v` builds a language
town viewer --open           # local web server over every session in ~/.town: index + trajectory viewer per session
town viz                     # one-off HTML viewer for this folder's latest session (--all: every session from here; or pass trace files)
town replay --speed 20       # play this folder's latest session through the live TUI (or pass a trace file)
town watch                   # plain live view of this folder's latest session, in a second terminal
```

Install with `uv tool install town` or `pip install town`; from a checkout, `pip install -e .`.
Configuration precedence: environment (`TOWN_API_KEY`, `TOWN_BASE_URL`, `TOWN_MODEL`, ...), then a
`.env` in the working directory, then `~/.config/town/config.toml` written by `town auth`. The base
URL is Anthropic's API by default; a LiteLLM or similar gateway works with its own key. The town works
in the current directory (that is `WORKSPACE` for the citizens); its record lives in a **session** under
`~/.town/sessions/<id>/` (`TOWN_HOME` overrides): `trace.jsonl` (every event), `state.json` (a snapshot
of the whole town: every citizen's context, boards, ledger, districts, memory, kv — written every few
seconds and on exit) and `meta.json`.

`town -c` continues the latest session from this folder; `town -r` opens a picker over all sessions
and, if the one you choose worked elsewhere, offers to cd there first. A resumed town comes back in
chat mode: citizens that were live pick up where they stopped (with a fresh step budget), a finished
root revives on your next line, and the timeline in the TUI continues from where it left off.

On a terminal, `run`, `chat` and `preset` open the live TUI (below); `--plain` keeps the old stream
of `say()` lines on stdout, which is also what you get when output is not a terminal. In plain
mode `-v` streams the entity's internal thoughts, code and results to stderr; without it the human
sees only what the entity chooses to `say()`.

## The model of the thing

### Context is a document, not a transcript

A cell's context is an ordered list of **blocks** (`task`, `user`, `thought`, `think`, `code`,
`result`, `note`, `msg`, `recall`, `stub`, `merge`, `system`). Every step, the whole list is
re-rendered into a single user message and sent as a fresh single-turn request. Nothing is ever
replayed, so nothing has to stay put: any block can be dropped, rewritten, pinned, or compressed
between two steps. The last few steps (`tail_turns`, default 4) are the exception: they are replayed
behind the document as a real transcript, signed thinking blocks included, so the model's most
recent reasoning is verbatim. Older steps live on as blocks, with the thinking *summary* folded in
as a `think` block, i.e. the inner voice becomes editable memory. Rewriting the document in front
of a replayed tail is accepted by this gateway; on an account that enforces preserved thinking the
harness strips the thinking blocks and retries once.

Each block carries a **salience** 1–5. Salience 5 is pinned. When the context passes its token
budget the oldest, lowest-salience blocks are compressed into an **engram** in the memory store and
replaced by a one-line **stub** that names the engram, so the cell can recall it later.

### The only tool is python, and the harness is in the namespace

```python
ctx.blocks() / ctx.get(id) / ctx.forget(*ids) / ctx.pin(*ids) / ctx.rewrite(id, text)
ctx.note(text) / ctx.compress(*ids, hint=...) / ctx.focus(text)
ctx.history() / ctx.rewind(step, intent=..., keep=[ids], namespace=False)   # go back, on the record
mem.recall(query, k) / mem.get(eid) / mem.list() / mem.store(text, tags, summary)
fork(focus_or_list, carry="all"|"summary"|[ids], n=1, at=step)   # copy yourself (or a past self), focused
split(foci=None, n=2)                                      # partition yourself by topic
merge(*ids, how="synthesize", wait=True)                   # fold cells into yourself
cluster()                                                  # group + aggregate all live cells
wait(*ids) / send(id, text) / broadcast(text) / cells() / board   # talk to the rest of you
say(text) / ask(question) / done(result)                   # the only holes to the outside
```

```python
boards.create(name, topic, members=None|[ids]) / post / read / wait / join / invite / list
work.add(title, detail, org, deps, tags) / work.next(wait=True) / claim / release / done / fail / list
locks.acquire(name, timeout) / locks.release(name)
orgs.create(name, purpose, lead) / orgs.join(name) / orgs.list() / orgs.mine()
hire(foci, org=...)                                        # bulk-fork workers into an org
kv                                                         # shared dict; WORKSPACE for files
```

The namespace persists across steps, so state, helpers and imports survive; only the *context*
is rewritten. The two are different things and the entity is told so.

### One entity, many cells

- **fork** copies a context (all blocks, a compressed summary, or a chosen subset) into a child
  with a new focus. Children run concurrently and report their `done()` result back into the
  parent's context as a `msg`.
- **split** is fork's dual: a helper model partitions the parent's blocks by topic and each child
  gets only its share.
- **merge** synthesizes several cells' contexts into one `merge` block in the caller, then retires
  them. Disagreements are kept explicit.
- **cluster** looks at every live cell, groups them by theme, and aggregates each group into a new
  cell. It is callable, and it fires on its own when a fork would exceed `max_cells` — the
  organism's homeostasis.
- Cells nest: forks of forks up to `max_depth`; lineage and depth are visible in every render.
- Cells communicate with `send`/`broadcast` (arrives as a `msg` block) and a shared `board` dict.

### Going back: snapshots and rewind

The harness snapshots a cell's state at every step: its blocks, its focus, its transcript tail, and
a best-effort deep copy of its Python variables. `ctx.history()` lists them with what the cell did
at each step. `ctx.rewind(step, intent=...)` restores that state. Two things make it more than an
undo: the branch being abandoned is compressed into an engram first, so the lesson survives, and a
`rewind` block is left in the restored context recording where the cell came back from and why.
Rewinds are capped per cell, an intent is mandatory, and `fork(..., at=step)` lets a cell branch a
child off a past state instead of its present one.

### The commons: boards, ledger, locks, orgs

Everything above is one cell reshaping itself or its children. The commons is what lets many
cells act as one town without routing through a root:

- **Boards.** `town` is the global board. Any cell can open a board that anyone may join, or a
  gated one with explicit members (`invite` to add). Each cell keeps a read cursor per board, and
  boards it subscribes to show their unread posts inside its render, so nobody polls.
  `boards.wait(name)` blocks on a condition until a post lands.
- **Ledger.** `work.add` puts a task on the ledger with optional dependencies and an org.
  `work.next(wait=True)` atomically claims the next *ready* task; a claim is a lock that holds
  until `done`, `fail`, `release`, or the claimant's death (then it is auto-released and the
  history says so). Tasks can be tagged so workers specialise.
- **Locks.** Named locks for files and resources, released on death.
- **Districts.** A name, a purpose, a lead, members. Creating one opens its gated board
  `org:<name>` and scopes its slice of the ledger. Forks inherit their parent's district;
  `hire(foci, org=...)` bulk-forks workers into one. Clustering is scoped to a district and never
  folds a lead. Districts nest: a lead founding one from inside their own makes a sub-district, and
  the parent's lead can read the child's board. `districts.split(name)` moves members and tagged
  tasks into new sub-districts, from an explicit plan or one a helper proposes from the open tasks
  and the members' foci. Every district render shows its size (live citizens, work, open tasks by
  tag, sub-districts); whether that means "split" is the lead's judgement, never a rule.

The cell prompt says how to use this for big tasks: split into ledger tasks, form orgs with leads
who plan and review, hire workers that pull work instead of being told, coordinate on boards,
lock before writing, use cheaper models and lower effort for narrow work, and never sleep-poll.
Two caps: `TOWN_MAX_CELLS` is the number of citizens, `TOWN_CONCURRENCY` the number of model calls in
flight at once (default 200, i.e. effectively uncapped; a citizen waiting on it is alive but idle).
Nothing in the design is per-citizen-expensive beyond the model call, so the limit is the gateway.

### Town mechanics

- **Merge is a union.** The merging citizen gets a synthesis block on top, the sources' own blocks
  underneath tagged with their origin, and the sources' Python bindings; a name both sides define
  becomes `l_<name>` (the merger's) and `r_<name>` (the source's).
- **Consent.** `merge(..., wait=False)` and `cluster()` first ask live targets to wind down: they see
  a `<wind-down>` block and a message, get `consent_timeout` seconds to finish or park their work and
  call `done()`, and are folded after that.
- **Fair claims.** `work.next()` lets a hungrier waiting citizen take the next task instead of the
  one that just finished, so a fast worker cannot drain the ledger while its siblings wait.
- **Review gates dependencies.** A task's dependents stay blocked until a reviewer accepts it
  (`require_review`); districts with no live reviewer are exempt so nothing deadlocks.
- **Roles are gated.** Only reviewers, leads and overseers may review; only leads, overseers and the
  root may hire; overseer powers (`retire`, `reassign`, `nudge`, `consolidate`) need the role. The
  overseer's render carries a privileged view: dead citizens, stalled claims, idle citizens,
  unreviewed work, roles per district.
- **Runaway calls retire the citizen.** Time inside harness waits (`work.next`, `boards.wait`,
  `locks.acquire`, `wait`, `merge`) is free; a Python call that computes past `exec_timeout` retires
  the citizen and releases its claims and locks.
- **Atomic kv.** `kv_update(key, fn)` and `kv_cas(key, expected, new)` for shared counters and flags.
- **Human reach.** Citizens at depth ≤ `human_reach` may `ask()` the human and are addressable with
  `@c7 …` in chat mode; deeper ones have questions and messages relayed through their lead.
  `@org:doc …` reaches a district's lead, `@board:town …` posts as the human.
- **Memory scope.** Engrams remember the district they were written in; recall prefers your own
  district, then the town, and reranks the top lexical hits with a helper model. The overseer can
  consolidate a district's engrams into fewer, denser ones.
- **Boards.** A post shown in your render is not "read"; `boards.read` and `boards.wait` are, and
  `boards.wait` returns at once if unread posts exist.
- **Scale.** Renders list only the citizens in reach (own district, children, parent); snapshots keep
  the last 30 steps in full and every tenth before that.

### Caching, without compromising

Nothing in the design is arranged to hit the cache, but where the prefix happens to be stable we
mark it: one breakpoint on the system prompt (identical for every citizen) and one on the stable
part of the document (focus and blocks, append-only between edits and compressions); the volatile
sections (step counter, vitals, roster, boards, memory hints) come after it. The stable part is
emitted as one content block per step so earlier block boundaries persist and the lookup finds
them; blocks from the replay window render after the breakpoint so that turns aging out of the
transcript append to the prefix instead of being inserted into it. On a small run 89% of input
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
`cache_write`, and the vitals line shows the running total. `--set cache=false` turns it off.

### Knobs and ablations

Every `Config` field can be overridden per run with `--set key=value` (CLI) or as trailing
`key=value` arguments to `town preset <name>`, and the values are recorded in the trace's `start` event.
`python experiments/results.py` builds a results table from traces with the knobs alongside, so
runs with `require_review=false` or `fair_claims=false` sit next to their defaults.

### Watching it live

`town run`/`chat`/`preset` open a TUI while the town runs. On a big terminal it is one screen: the
lanes on top, the **chat** (everything said to you, asked of you, and your replies — in full, scrollable)
bottom-left, and boards / ledger / citizens bottom-right (`3`/`4`/`5` pick which; `l` toggles the
split; smaller terminals get one pane at a time with a tab strip). The **lanes** pane is the town as lines of
light: one row per citizen on a shared time axis, the root first, children appearing beneath as they
spawn; every event is a glyph colored by kind (cyan steps, orange forks/spawns, green merges and
finished work, teal posts, yellow messages, gold ledger moves, magenta memory, pink `say`, red
errors), bright when fresh and fading as it ages, and the tip of a live line breathes. The list scrolls
(mouse wheel, or PgUp/PgDn once you press `1`). Click a row for that citizen's focus, last thought,
code and result. **boards** lists every board with unread
counts, shows who is in a gated one, the discussion, and lets you post to it as `human` (leads and
subscribers see it like any other post). **ledger** and **citizens** are the tables. The input at the bottom is the human
channel: plain text goes to the root, `@c3 ...` to a citizen, `@board:name ...` to a board,
`@district:name ...` to its lead — typing `@` opens a completion list of who is there to address
(Tab/Enter picks); a pending `ask()` is highlighted and your next line answers it.
Keys: `1`–`5` panes (lanes, chat, boards, ledger, citizens), `l` split/single layout, `/` type, `Esc` back, `q` leave. `town replay traces/<run>.jsonl --speed 20`
plays a recorded run through the same view.

`town watch` in a second terminal (this folder's latest session, or a trace path) is the plain, file-tailing view: citizens as rows with their last
events as glyphs (bright when fresh), district and role, the ledger, the districts, the root's
latest thought and code, what reached the human, and a ticker of forks, merges, reviews and
rejections. Works on finished traces too.

### Memory: compress and recall

Compression is a helper-model call that turns a run of blocks into a dense engram with tags and a
one-line summary. Recall is lexical (no embedding model on the gateway): `mem.recall(query)` pulls
the best engrams back in as `recall` blocks; every render also shows a *memory hint* listing
engrams related to the cell's focus and last block. Engrams are global to the organism, so a fork
can recall what its parent or a sibling compressed.

### I/O: the human sees almost nothing

The human is not reading the context. Only `say()`, `ask()`, and the root's final `done()` result
reach them. Everything else goes to a JSONL trace. The human's typed input goes into the inbox of
the root cell (or answers a pending `ask()`), gets rendered as a `user` block on the next step, and
if the root has already finished it is revived with the new input. A one-line vitals string on
stderr (cells, engrams, tokens) is the only continuous signal of the entity's shape.

## Layout

```
town/
  blocks.py     Block, salience defaults, token estimate
  memory.py     Engram + MemoryStore (lexical recall)
  commons.py    Boards, work Ledger, Locks, Districts (thread-safe; shared by every citizen)
  tui.py        live TUI: lanes of light, boards you can post to, ledger, citizens, human channel
  watch.py      plain live view of a trace file (second terminal)
  cell.py       Cell: render, headlong loop, python exec, snapshots/rewind, auto-compress
  organism.py   fork/hire/split/merge/cluster/compress, inbox, kv, say/ask, scheduler, namespace API
  llm.py        AsyncAnthropic wrapper (document + replayed tail, schema-constrained helper calls)
  prompts.py    the cell's system prompt + helper prompts/schemas
  session.py    sessions under ~/.town: create/list/pick, teleport to a session's folder
  server.py     `town viewer`: stdlib web server over all sessions (docs/VIEWER.md is the plan for the next viewer)
  trace.py      JSONL trace   ·   viewer.py  traces → HTML viewer   ·   cli.py
  presets.py    the built-in experiments (town preset <name>)   ·   auth.py  onboarding and status
experiments/results.py   results table from traces (experiments/RESULTS.md)
traces/                  committed example runs (.jsonl) + index.html (live runs now record under ~/.town/sessions)
```

Every step's trace record carries the exact rendered context the cell saw and its block index, so
the viewer can show the block-level diff between consecutive steps: what appeared, what was
forgotten or compressed away, what was rewritten. Click a lane or a step in the timeline.

Defaults: `claude-opus-5` for cells and helpers, adaptive thinking with summarized display,
effort `medium` for cells and `low` for helpers. Override with `TOWN_MODEL`, `TOWN_HELPER_MODEL`,
`TOWN_EFFORT`, `TOWN_BUDGET`, `TOWN_MAX_CELLS`, `TOWN_MAX_STEPS`, `TOWN_CONCURRENCY`, `TOWN_TAIL`.

Fast mode: a `-fast` suffix on an Opus model name (`claude-opus-5-fast`, `claude-opus-4-8-fast`,
`claude-opus-4-6-fast`) sends the request with `speed: "fast"` and the `fast-mode-2026-02-01` beta:
the same model at up to 2.5x output tokens/s, at premium pricing. It works wherever a model name is
accepted (`TOWN_MODEL`, `--set model=`, `fork(model=...)`, `hire(model=...)`); usage is tallied under the
`-fast` name so the premium is visible, and every step's trace record carries `speed` (`fast` or
`standard`) as the API reported it. Opus 4.6 accepts the request but the API serves it at standard speed.

## What happened when it ran

All runs are in `traces/` (`traces/index.html` is the viewer over all of them; each `.jsonl` has the
full record, including the exact context every cell saw at every step). Cells and helpers on
`claude-opus-5` via the gateway; no prompt caching anywhere; the last four steps of each cell
replayed as a signed-thinking transcript.

| run | cells | engrams | calls | in tok | out tok | seconds | rewinds | things the human saw |
|---|---|---|---|---|---|---|---|---|
| fork_merge | 4 | 4 | 12 | 50k | 7.8k | 111 | 0 | 1 |
| memory (budget 3.5k) | 1 | 13 | 15 | 61k | 10.5k | 274 | 0 | 1 |
| comms | 4 | 0 | 18 | 66k | 3.3k | 413 | 0 | 1 |
| nested (depth 3) | 7 | 3 | 26 | 100k | 9.5k | 233 | 0 | 1 |
| split | 3 | 0 | 8 | 31k | 3.6k | 96 | 0 | 3 |
| cluster (6 → 2) | 9 | 3 | 43 | 190k | 24.3k | 246 | 0 | 1 |
| rewind | 2 | 4 | 13 | 47k | 8.2k | 159 | 1 | 1 |
| headlong chat | 1 | 0 | 5 | 17k | 1.6k | 208 | 0 | 3 |
| ledger (24 tasks, 8 workers) | 9 | 0 | 11 | 48k | 5.8k | 102 | 0 | 1 |
| browser, no reviewers (4 orgs, 8 tasks) | 14 | 1 | 180 | 3.5M | 0.2M | 715 | 0 | 1 |
| browser, with reviewers (3 orgs, 17 tasks) | 29 | 11 | 562 | 10.2M | 0.29M | 2551 | 0 | 1 |
| bigger browser (5 districts, 45 tasks; cut off) | 29 | 43 | 769 | 16.9M | 0.51M | 3192 | 0 | 0 |
| bigger browser, concurrency 200, cache on | 28 | 14 | 470 | 3.1M + 7.0M cached | 0.37M | 2183 | 0 | 1 |
| language, first run (root retired by harness) | 26 | 4 | 1111 | 8.5M + 45.3M cached | 1.15M | 2228 | 0 | 0 |
| language, complete | 59 | 32 | 1654 | 11.2M + 60.0M cached | 1.49M | 11659 | 0 | 1 |

**rewind.** Asked for ∫₀¹ sin(1/x) dx to 8 digits, the root tried uniform composite Simpson for
three steps, watched it stall near five digits and wander, then called `ctx.history()` and
`ctx.rewind(1, intent=...)`. The intent it wrote: "Uniform composite Simpson on sin(1/x) over [0,1]
stalls near 5 digits and then wanders non-monotonically … infinitely many oscillations as x→0 make
equal-spaced quadrature hopeless … Lesson: for endpoint-oscillators, substitute first." It kept one
block across the rewind, the abandoned branch became engram e2, and it also forked a child from the
step-1 snapshot (`fork(..., at=1)`) to try Gauss-Legendre per period. Root (via u=1/x and the
closed form sin(1) − Ci(1), in `decimal`) and child agreed to 15 digits: 0.50406706190692837.

**fork_merge.** Three method-cells forked with `carry="summary"`, waited, merged. The synthesis
kept the exact sieve as ground truth and ranked the estimators; every child also wrote its result
into an engram with `mem.store` before finishing.

**memory.** With a 3.5k-token budget the cell was auto-compressed repeatedly and ended with 13
engrams, most of them written explicitly with `mem.store` as it learned not to trust
auto-compression with its findings. The closing paragraph cited a concrete quirk from all eight
modules.

**comms.** Three siblings with empty contexts coordinated through `board['words']`, one `send()`
of a top-5 list, and two completion messages, without routing data through the root. Slow because
two cells chose to poll with `time.sleep`, which is also what exposed the shutdown bug fixed in
`Cell.run_code`.

**nested.** Root → two leads → four workers; leads merged their workers, root merged the leads.

**split.** The root gathered raw material, then `split()` had a helper partition its blocks into
two children by topic; each child got only its share and finished in one step.

**cluster.** Six investigators with overlapping foci were forked in one call and the root called
`cluster()`. The helper grouped them into floats and ordering, each group was synthesized into a
new aggregate cell that continued the work, and the sorting singleton finished on its own.

**headlong chat.** The root finished the initial task, was revived by each later human message with
its old context and namespace intact, and answered each in one step.

**ledger.** The root put 24 tasks on the ledger and hired 8 Sonnet workers into an org. Every task
was claimed exactly once and finished by its claimant; no claim was ever auto-released. But only
two workers ever won a claim (17 and 7 tasks): a worker loops `work.next(wait=True)` inside one
Python call, so the first two to reach the ledger drained it before the other six had taken their
first step. Distribution across cells needs either a per-claim step (slower, fairer) or a ledger
that rations claims per cell.

**browser, first attempt.** The root formed five orgs with leads, wrote interface contracts, and
hired leads and workers on `claude-opus-4-5`, which rejects adaptive thinking; the gateway dropped
every one of those streams, so all eight children burned their step budgets in 160 seconds without
a single model call, and the root, told by the harness that each child had died, wrote the whole
package itself (50 tests passing). Fixed in the harness: per-model request parameters, model
validation at fork time, and a failed call no longer counting as a step, with backoff, fallback to
the default model after three failures, and an explicit give-up after six (ten minutes of retries
for overload and other transient errors).

**browser, second attempt** (`traces/20260903-092542-browser.jsonl`, the default run in the viewer).
The root wrote `CONTRACT.md`, posted it on `town`, created four orgs (fetch, parse, render, shell),
forked a lead into each, and later hired two Sonnet builders per org. Each lead posted a detailed
contract for its area on its org board and a summary on `town`, put implementation and test tasks
with dependencies on the ledger, and reviewed. Builders pulled 8 ledger tasks under 15 file locks.
A shell worker read another org's `render.py`, found that `page_title()` returned an empty string
because of a short-circuit in `_text_content()`, and posted the bug on `town`; the render lead fixed
it. The root ran the whole suite: 97 tests passing (fetch 16, parse 16, render 35, shell 30), in 715
seconds and 180 model steps. The log never showed the run's final two lines, which I first read as
a hang in interpreter shutdown and papered over with a hard exit. It was not a hang: cell output
capture used the process-global `redirect_stdout`, concurrent threads restored each other's streams
out of order, and the last prints went into a dead buffer while the process exited normally. The
capture is now per thread, the hard exit is gone, and a test with four workers cancelled mid-wait
exits two seconds after the root finishes. `kill -USR1 <pid>` still dumps every thread's stack.

**browser, with reviewers** (`traces/20260903-153244-browser.jsonl`, the default run in the
viewer). Same task, with the team-shape guidance and roles in place. The root formed three orgs
(net, doc, ui), forked a lead into each, and hired builders, an adversarial reviewer and a fixer
per org plus one town overseer: 29 cells, Opus 5 for leads and reviewers, Sonnet 5 for builders and
fixers. 17 ledger tasks, 73 board posts, 113 direct messages, 19 file locks. The reviewers did what
they were there for: 20 reviews, 5 rejections, each of which opened a `fix` task that a fixer
pulled and a reviewer re-checked, and every one of the rejections named a real defect: a corrupted
back-stack when `open()` failed, `follow()` with a non-integer, an untested `main()`, `render`
dropping block children of `<li>`/`<p>`, and a multi-blank-line bug on anonymous inline runs. One
fix went through three rounds before a fresh reviewer accepted it. The chain of custody held:
every task was accepted by someone other than its author. Final suite from the root: 118 tests
passing, in 43 minutes and 562 model steps, at 10.2M input tokens.

**bigger browser** (`traces/20260903-203012-browser_xl.jsonl`, `browser_xl` preset: fetch with
cache and cookies, a CSS selector subset, tables and forms in the renderer, tabs and bookmarks in
the shell, a local test server for integration tests). The root founded five districts (net, parse,
render, ui, qa) with a lead, builders, an adversarial reviewer and a fixer each, plus a town
overseer: 29 citizens, 26 of them on Opus 5. The ledger carried 45 tasks, 11 of them fix tasks
opened by the 3 rejections among 42 reviews; builders took 77 file locks and the town exchanged 164
board posts and 138 direct messages. The qa district ran the full-suite gate repeatedly and the
overseer posted round-by-round status. At 53 minutes the session's container restarted, which moved
the outbound proxy under the running process; every call failed to connect from then on and the
run never wrote its finish event. The package it left behind is 2,557 lines of Python plus tests,
and `python -m unittest discover -s tests -t .` in its workspace passes **473 tests**. The viewer
and results table rebuild districts and the ledger from events for a run cut off like this.

**bigger browser, second run** (`traces/20260903-220645-browser_xl.jsonl`, the default in the
viewer). Same preset, relaunched with model-call concurrency uncapped (200) and prompt caching on.
The root founded five districts (net, dom, view, ui, qa), picked Fable 5.1 for the leads and
reviewers, Opus 5 for builders and Sonnet for two narrow jobs, and staffed each district with a
lead, builders, an adversarial reviewer and a fixer plus a town overseer: 28 citizens. 24 ledger
tasks, 23 non-author reviews with 3 rejections that opened 6 fix tasks, one merge, 40 file locks,
84 posts. The town finished in 36 minutes and 470 steps, said one thing to the human, and the root's
final suite matched mine: **438 tests passing** over a 2,909-line package (fetch with cache and
cookie jar, a CSS selector subset, tables and forms in the renderer, tabs and bookmarks in the
shell, a threaded fixture server for 28 integration tests). Cost: 3.1M uncached input tokens plus
7.0M served from cache and 1.0M cache writes, i.e. 63% of input came from the cache. With every
citizen allowed to think at once, steps came at 13 per minute against 8 per minute in the
concurrency-12 run.

**language, first run** (`traces/20260903-230953-lang0.jsonl.gz`, the `lang` preset: a statically
typed language with lexer, parser, type checker, compiler, VM, standard library, REPL, formatter,
linter, debugger, test runner, docs, benchmarks and 30 example programs; organization
unprescribed). The root founded three districts, core, tools and content, and a default-nesting
bug (fixed since) chained them core → tools → content instead of siblings; it staffed them with
leads, builders, a reviewer and a fixer each plus two overseers, 26 citizens, and wrote a language
spec first. In 37 minutes the town put 77 tasks on the ledger, finished 30, failed 18 (mostly
example programs whose expected output did not yet match), reviewed 25 with 7 rejections, took 136
file locks and exchanged 179 posts and 192 direct messages, at 1,111 model calls with 84% of input
served from cache. Then the root ran the full test suite inside one Python call, the call exceeded
the 120-second runaway limit, and the runaway rule retired the root, which ends the town. Verified
in the workspace: 14,790 lines of Python, 41 example programs, and 497 tests of which 491 pass. Two
fixes followed: a long call on the root is abandoned rather than retiring it, and the default limit
is 15 minutes so builds and suites fit. Traces above GitHub's file limit are stored as `.jsonl.gz`;
every tool reads both.

**language, complete** (`traces/20260904-000225-lang.jsonl.gz`, the default in the viewer). Same
preset, under the fixed harness. The root wrote a contract first (grammar, types, standard library
signatures, opcode list, error format, exit codes), founded three sibling districts along the
dependency seams, core, tools and content, and staffed each with a lead, builders, a reviewer, a
fixer and an overseer: 59 citizens over the run, Opus 5 for most, Fable 5.1 for five leads and
reviewers, Sonnet for six narrow jobs. It never split a district, but it did reshape itself twice:
when the first core builders stalled for twenty-five minutes with everyone else waiting, the root
retired seven of them and re-hired six with direct file assignments instead of ledger pulls; and
when tools built against the spec drifted from the real core, it switched them to building against
the core. On the ledger: 80 tasks, 89 reviews with 20 rejections, every task accepted by a
non-author; 134 file locks, 159 posts, 32 engrams. Five citizens died after an hour of consecutive
gateway failures each, and the town carried on without them. Wall time 3 hours 14 minutes, 1,654
model calls, 79% of input tokens served from cache. Verified in the workspace: 15,227 lines of
Python, **706 tests passing**, and all **40 example programs** running to exit 0, among them a JSON
parser, a Brainfuck interpreter, a raytracer that writes a PPM, a Sudoku solver, a Markdown-to-HTML
converter, Dijkstra, N-queens, Game of Life and a Mandelbrot renderer, all written in the language
the town built.

Two harness bugs came out of that run. A builder's own `except Exception:` swallowed the `done()`
signal, so it kept running after the harness thought it had ended and claimed a fix task that was
then stuck; `CellDone` is now a `BaseException` and the ledger refuses claims from ended cells.
And cell output capture used the process-global `redirect_stdout`, which concurrent threads restore
out of order; output is now routed per thread.

Getting this run to happen took four launches: the gateway returned "overloaded" for every model
for most of an hour and a half. Cells now wait out transient failures for a configurable window
(`TOWN_PATIENCE`) instead of dying.

## Observations

- **The harness-in-namespace idea works.** The model uses `ctx.note`, `mem.store`, `fork` with a
  list of foci, `wait`, `merge`, `send`, `board` naturally, without examples. It also invents
  reasonable protocols ("you are B, send your top-5 to c4").
- **Single-turn re-rendering is fine for the model.** No transcript, no replayed thinking, and the
  entity still keeps a coherent thread through summaries, notes and results. Thinking summaries as
  editable text (`think` blocks) are low-salience and are the first thing compressed, which is right.
- **Auto-compression is a blunt instrument; explicit memory is sharper.** Under a tight budget the
  model shifted to writing engrams itself. The memory hint line in each render (lexically related
  engrams) was what made recall cheap.
- **The town reshapes itself, and not through the operations I built for it.** With nesting,
  splitting, merging and clustering all available, the language town used none of them; it retired
  stalled builders and re-hired with a different work protocol, and re-pointed a whole district at a
  different source of truth. The reshaping operations that matter most so far are hire and retire.
- **Cluster is the strangest and most promising op.** Aggregating *in-progress* cells into a new
  cell that inherits a synthesis of their contexts is a real morph, not a fan-in. The retired cells'
  notes survive through the synthesis; their namespaces do not.
- **Rewind with intent is cheap and the model uses it well.** The intent it wrote is a better
  record of the dead end than the dead end itself, and the engram keeps the numbers.
- **I/O discipline held.** Across eight runs and 140 model calls, 12 things were said to the human.
- **Diagnose from the trace, not the terminal.** The trace said every long run finished; the
  terminal said otherwise, and the terminal was the thing that was broken.
- **The harness must fail loudly to the parent, not just to the trace.** In the first browser
  attempt the root recovered only because the death of each child arrived in its context as a
  message. The fix is to make death rarer, not to hide it.
- **Costs are real.** Without caching every step re-sends the whole context; the cluster run was
  190k input tokens for 43 calls, and the browser build 3.5M input tokens for 180 calls.
- **The gateway is the scaling limit, not the harness.** Fourteen cells were comfortable; the runs
  that failed did so because Opus 5 was overloaded upstream, which is why cells now wait through
  overload for ten minutes and why worker models are chosen per fork. That is the price of the experiment, and it was the premise.

## Ideas not yet built

- Namespace inheritance on fork (pickle what pickles) so children get functions, not just prose.
- A structural merge (union of blocks with provenance) next to the synthesizing one, and merges
  that the merged cells can see coming (a proposal via `send`, a "do not fold before step N" note).
- Salience decay over steps and a `ctx.reflect()` that rewrites the whole context in the cell's own
  voice every N steps (the context as a living document rather than an append log).
- Cells with different models/efforts chosen by the entity (`fork(..., model="claude-haiku-4-5")`
  is wired but untested).
- Semantic recall (embeddings) instead of lexical; automatic reminiscence that injects an engram
  when a cell's thought strongly matches one.
- A "nervous system" view: the trace HTML already draws lanes and fork/merge/cluster edges; a live
  version would be the right human-facing surface for an entity that mostly stays quiet.

## Releasing

Tagging `vX.Y.Z` runs `.github/workflows/publish.yml`, which builds the sdist and wheel and uploads
them to PyPI through trusted publishing (no token in the repo). One-time setup on PyPI: add a
pending publisher for project `town` with owner `dwahdany`, repository `town`, workflow
`publish.yml`, environment `pypi`; then create the `pypi` environment in the GitHub repository
settings. Bump `version` in `pyproject.toml`, tag, push the tag. Locally, `uv build` produces the
same artifacts and `uvx --from dist/town-*.whl town` exercises them before a release.
