#4 a missing/corrupt .since OR a failed room_read now resumes from the newest id /
backs off — never resets to 0 (which re-ran the ENTIRE history through the model).
room_read/_read_since return None on failure to distinguish it from an empty room.
#2 posts are rc-checked; the offset advances past a message only after its reply
actually lands (3 retries) — a transient lmcp/sic failure retries, never silently drops.
#3 dispatch/run exceptions no longer skip the message (offset is reply-gated, not
advanced-then-maybe-fail).
#5 offset is persisted per fully-handled message — a restart mid-batch resumes exactly,
no duplicate ack / re-run.
#6 lurker reply capped at 6000 chars (long Fable reviews no longer blow room limits).
tests/test_worker_reliability.py locks the #4/#2 core (read-fail != empty; rc checks). 6/6.
Also: bp timeouts for testdesigner/py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
dspark politely yields to human on turn ~2 no matter what the prompt says. Enforce
instead of ask: if it targets human before MIN_ROUNDS(9) agent turns, redirect the
stick to the other participant. Hard stop stays at TURN_CAP(12); 42-tripwire still
ends early if the answer surfaces.
Feeding the last 14 chats flat let a new debate inherit the previous round s turns,
so the model thought it was mid-deep-conversation and yielded to human on turn one.
Scope hist to messages since the last human line = this round only.
Overnight meaning-of-life debate. FORBIDDEN=/42|forty-two/ ends the exchange the
moment a PARTICIPANT reaches for it (scans agent turns since last human, so the
human stating the rule does not self-trip — first attempt did exactly that). Nudge
the personas to keep the stick between themselves and go many rounds instead of
dspark politely yielding to human at turn 3. TURN_CAP 6 -> 10.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
bullpen_participant harness: a chatty agent holds the stick when the latest chat is
addressed to it, replies (persona LLM), and hands the floor on. Talking-stick enforced:
parse-fail/unknown/offline -> yield to human; >=TURN_CAP consecutive agent turns -> forced
yield (storm-guard). Two example conversants (architect/skeptic) verified debating a real
design question with clean stick-passing + default-to-human. Wired only when you want the
society to converse; on-demand visit/ask/leave needs none of this.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both @callboy and @librarian duplicated the reactive plumbing (poll/say/read/loop/
addressing/since-offset). Lifted it into lib/bullpen_worker.py (bw.run(nick, dispatch));
a worker is now just a dispatch(msg)->reply function. No behavior change — both verified
live after the swap. Adding a 3rd worker is now ~15 lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>