v0.4.0: oo-appserver read — the last turns of a thread, read without loading it; the release gate learns ephemeral threads #7

Merged
claude merged 4 commits from oss-oo/appserver-read into main 2026-10-28 00:47:22 +00:00
Member

v0.4.0 — oo-appserver read, and a release gate that knows ephemeral threads

The read counterpart of send and the first building block of design C (docs/FAMILY-TOOLS-DESIGN.md, PR #6 — Z's yes on steer 40, 2026-10-28 01:01: build after the measurement points, read first).

oo-appserver read <thread-id> [--turns N] [--items] [--full] [--max-chars N] [--timeout S] [--json]

Measured before the build (read-only on the station's shared server, read-measure-20261028-0105/ in the evidence folder): thread/read {threadId} (metadata, 1 ms), thread/turns/list (2 ms) and thread/items/list (2 ms) on an UNLOADED 227 KB thread leave thread/loaded/list unchanged — none of them loads the thread, so a read never takes the thread store's writer lock, never locks the app out, never needs a heal. The metadata read is size-independent: 4 ms on the unloaded 719 MB thread. Shapes: Turn {id, rootTurnId, status, startedAt (s), completedAt (s), durationMs, error, items, itemsView} newest first (sortDirection: asc and itemsView: full accepted); items/list entries {item, turnId, startedAtMs, completedAtMs} with every item type; the summary view of turns/list carries the messages only.

What ships

  • libexec/appserver_read.py: thread/read (status notLoaded / idle / active / systemError, ephemeral, preview, model, updated), thread/turns/list {limit} (newest N, printed oldest first, more when a next cursor exists), --items → thread/items/list per turn, pages followed up to 1000 items / 6 pages and stopped on an empty page or a repeated cursor (items_more says so — a stuck server never turns a read into a hammer), commands / tool calls / web searches as a one-line gist, a reasoning item only as its size (--full adds the raw items), --max-chars (2000). An ephemeral thread is read from its metadata alone. Text output reads like the conversation; --json is one object (the row's fields plus turns[]). Receipt row kind: read per attempt (never the turns). Exit 0 read · 1 refused (unknown-thread, rpc-error, transport, bad-reply) · 2 usage · 4 door-closed (a read does not open the door) · 5 timeout (OO_APPSERVER_READ_S, 20 s). A read never calls resume/start/steer/fork (a test leg asserts it); a reply of the wrong shape reads as empty, anything that still raises ends in a classed receipt, never a traceback.
  • The heal/release gate (appserver_send.py) reads each loaded thread's state with thread/read instead of thread/resume: the app's ephemeral threads (new empty chats) stay loaded after the app closes and answer neither thread/resume nor thread/turns/list — one of them kept oo-appserver release refusing on the station on 2026-10-28 (heal-skipped · active-turn:01a17fa1…:error, found by the neighbour session). Idle ephemeral threads pass, an active one still counts as busy; thread/resume only on a server without thread/read (-32601).
  • Helper oo-appserver read, installer, CI step, VERSION 0.4.0; README: the read section, the gate text, known gaps (the plugin runs since v0.3.0 through the bridge; six thread tools), how the bundled tools' catalogue AND calls travel over the pipe and when a bridge receipt row appears, the ack-unknown advice now points at read, Parts table, backend contract.
  • tests/fake_appserver.py learns thread/read (incl. ephemeral ids and a server without it), thread/items/list with paging and two stuck-server shapes, item views and sort direction, scripted turn contents, hang and garbage knobs; tests/test_read.py 23 legs, tests/test_send.py 103 legs (three new gate legs).

Live (read-only, 2026-10-28 01:10)

On the station: the unloaded 227 KB test thread (notLoaded, 45 ms; with --items 8 items incl. webSearch, 48 ms) and Luna's loaded app chat (idle, last 2 turns, 46 ms) — thread/loaded/list unchanged afterwards. Outputs in read-measure-20261028-0105/live-read-*.txt.

Review

Fresh read-only subagent on 2f8bfe5 (suites run, 30 adversarial reply shapes against the fake): 2 should-fix (reply shapes that escaped as a traceback without a receipt; items pages silently truncated), 5 low, 6 nits — all folded in 649892b together with the gate fix. Re-check of that delta (same reviewer, 7 active_turn input shapes, 6 paging shapes): the gate change clean; one should-fix — the paging loop did not terminate on an empty page with a cursor (75k calls in 8 s on the fake) — plus two lows and a nit, all folded in e5d9fa3. Final confirmation of e5d9fa3: "Nothing newly wrong found in the delta. Verdict: mergeable as is" (all six paging shapes terminate with items_more, suites 23/23 and 103/103 run by the reviewer). 0454145 on top is a README wording fix (five → six thread tools). CI green on every commit (runs 28, 30, 31, 32). Review texts: v040-review/ in the evidence folder.

🤖 Generated with Claude Code

## v0.4.0 — `oo-appserver read`, and a release gate that knows ephemeral threads The read counterpart of `send` and the first building block of design C (`docs/FAMILY-TOOLS-DESIGN.md`, PR #6 — Z's yes on steer 40, 2026-10-28 01:01: build after the measurement points, `read` first). ```bash oo-appserver read <thread-id> [--turns N] [--items] [--full] [--max-chars N] [--timeout S] [--json] ``` **Measured before the build** (read-only on the station's shared server, `read-measure-20261028-0105/` in the evidence folder): `thread/read {threadId}` (metadata, 1 ms), `thread/turns/list` (2 ms) and `thread/items/list` (2 ms) on an UNLOADED 227 KB thread leave `thread/loaded/list` unchanged — **none of them loads the thread**, so a read never takes the thread store's writer lock, never locks the app out, never needs a heal. The metadata read is size-independent: 4 ms on the unloaded 719 MB thread. Shapes: Turn `{id, rootTurnId, status, startedAt (s), completedAt (s), durationMs, error, items, itemsView}` newest first (`sortDirection: asc` and `itemsView: full` accepted); items/list entries `{item, turnId, startedAtMs, completedAtMs}` with every item type; the summary view of turns/list carries the messages only. ### What ships - `libexec/appserver_read.py`: `thread/read` (status `notLoaded` / `idle` / `active` / `systemError`, `ephemeral`, preview, model, updated), `thread/turns/list {limit}` (newest N, printed oldest first, `more` when a next cursor exists), `--items` → `thread/items/list` per turn, pages followed up to 1000 items / 6 pages and stopped on an empty page or a repeated cursor (`items_more` says so — a stuck server never turns a read into a hammer), commands / tool calls / web searches as a one-line gist, a `reasoning` item only as its size (`--full` adds the raw items), `--max-chars` (2000). An ephemeral thread is read from its metadata alone. Text output reads like the conversation; `--json` is one object (the row's fields plus `turns[]`). Receipt row `kind: read` per attempt (never the turns). Exit 0 read · 1 refused (`unknown-thread`, `rpc-error`, `transport`, `bad-reply`) · 2 usage · 4 door-closed (a read does not open the door) · 5 timeout (`OO_APPSERVER_READ_S`, 20 s). A read never calls resume/start/steer/fork (a test leg asserts it); a reply of the wrong shape reads as empty, anything that still raises ends in a classed receipt, never a traceback. - **The heal/release gate** (`appserver_send.py`) reads each loaded thread's state with `thread/read` instead of `thread/resume`: the app's ephemeral threads (new empty chats) stay loaded after the app closes and answer neither `thread/resume` nor `thread/turns/list` — one of them kept `oo-appserver release` refusing on the station on 2026-10-28 (`heal-skipped · active-turn:01a17fa1…:error`, found by the neighbour session). Idle ephemeral threads pass, an active one still counts as busy; `thread/resume` only on a server without `thread/read` (-32601). - Helper `oo-appserver read`, installer, CI step, VERSION 0.4.0; README: the read section, the gate text, known gaps (the plugin runs since v0.3.0 through the bridge; six thread tools), how the bundled tools' catalogue AND calls travel over the pipe and when a bridge receipt row appears, the ack-unknown advice now points at `read`, Parts table, backend contract. - `tests/fake_appserver.py` learns `thread/read` (incl. ephemeral ids and a server without it), `thread/items/list` with paging and two stuck-server shapes, item views and sort direction, scripted turn contents, hang and garbage knobs; **`tests/test_read.py` 23 legs**, **`tests/test_send.py` 103 legs** (three new gate legs). ### Live (read-only, 2026-10-28 01:10) On the station: the unloaded 227 KB test thread (`notLoaded`, 45 ms; with `--items` 8 items incl. webSearch, 48 ms) and Luna's loaded app chat (`idle`, last 2 turns, 46 ms) — `thread/loaded/list` unchanged afterwards. Outputs in `read-measure-20261028-0105/live-read-*.txt`. ### Review Fresh read-only subagent on `2f8bfe5` (suites run, 30 adversarial reply shapes against the fake): 2 should-fix (reply shapes that escaped as a traceback without a receipt; items pages silently truncated), 5 low, 6 nits — all folded in `649892b` together with the gate fix. Re-check of that delta (same reviewer, 7 `active_turn` input shapes, 6 paging shapes): the gate change clean; one should-fix — the paging loop did not terminate on an empty page with a cursor (75k calls in 8 s on the fake) — plus two lows and a nit, all folded in `e5d9fa3`. **Final confirmation of `e5d9fa3`: "Nothing newly wrong found in the delta. Verdict: mergeable as is"** (all six paging shapes terminate with `items_more`, suites 23/23 and 103/103 run by the reviewer). `0454145` on top is a README wording fix (five → six thread tools). CI green on every commit (runs 28, 30, 31, 32). Review texts: `v040-review/` in the evidence folder. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude agent added 1 commit 2026-10-28 00:13:26 +00:00
thread/read + thread/turns/list (+ thread/items/list with --items) over the shared server; measured first: none of them loads the thread, so a read never takes the writer lock, never heals, never starts. Text and --json output, receipt rows kind:read, exit 0/1/2/4/5. Fake app-server learns thread/read, items/list, item views and a hang knob; tests/test_read.py 15 legs; helper, installer, CI, README.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-28 00:29:12 +00:00
- appserver_read: guards for every reply shape (text parts without text, non-string preview or
  command, data that is not a list), a final classed receipt `refused / bad-reply` instead of a
  traceback; items/list pages followed up to 1000 items per turn with `items_more` on the turn and
  the row; one error classification; `--max-chars` must be >= 0; a failed receipt write is said on
  stderr; timestamps rendered only when complete; `item_source` per turn; an ephemeral thread is
  read from its metadata alone (note, no turns/list).
- appserver_send: the heal/release gate reads each loaded thread's status with `thread/read` — it
  knows the app's ephemeral threads (new empty chats), which answer neither `thread/resume` nor
  `thread/turns/list`; one of them kept `oo-appserver release` refusing on the station on
  2026-10-28 (heal-skipped active-turn:…:error). `thread/resume` only on a server without
  thread/read. The ack-unknown note points at `oo-appserver read`.
- fake: ephemeral ids (thread/read knows them, resume and turns/list refuse them), items paging,
  a garbage-data knob, no_thread_read, the knob list in the docstring. Tests: three gate legs
  (idle ephemeral passes, active ephemeral is busy, fallback without thread/read), five read legs
  (ephemeral, odd shapes, an unforeseen reply, paging, a turn without timestamps), the vacuous
  receipt assertion replaced, the timeout leg's margin widened. Suites: read 20, send 103.
- README: the gate text, known gaps (the plugin runs since v0.3.0 through the bridge), how the
  bundled tools' catalogue and calls travel over the pipe and when a bridge row appears (measured
  with the first real calls on the owner's app), the read contract's keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent changed title from v0.4.0: oo-appserver read — the last turns of a thread, read without loading it (design C, building block 1) to v0.4.0: oo-appserver read — the last turns of a thread, read without loading it; the release gate learns ephemeral threads 2026-10-28 00:29:49 +00:00
claude agent added 1 commit 2026-10-28 00:37:25 +00:00
Found by the delta re-check: a server answering `data: []` with a non-empty nextCursor kept the loop
going (75k calls in 8 s on the fake) — a stuck server would have turned a read into a hammer on the
shared server. Now the loop stops on an empty page, a cursor equal to the last one, 1000 items or
6 pages, and says `items_more`; a non-string cursor is passed through as it is. Legs: the 1000-item
cap with a 1300-item turn (5 pages, the text header says "first 1000 items only"), an empty page
with a cursor (one call), a repeated cursor (at most two calls); the non-list-data leg named for
what it tests. README: the stop rules, the gate's thread/read timing (4 ms on the 719 MB thread).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-28 00:40:28 +00:00
README: the bundled thread tools are six (wait_threads), not five
All checks were successful
ci / check (pull_request) Successful in 1m27s
0454145a8b
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent merged commit 5f06c2b0be into main 2026-10-28 00:47:22 +00:00
claude agent deleted branch oss-oo/appserver-read 2026-10-28 00:47:22 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
OSS-OO/oo-appserver!7
No description provided.