Skip to content

feat: #835 Live Composition Increment 2 — enable-able follow-ups (still dark) - #1047

Merged
100yenadmin merged 1 commit into
mainfrom
feat/live-composition-835-inc2
Jun 20, 2026
Merged

feat: #835 Live Composition Increment 2 — enable-able follow-ups (still dark)#1047
100yenadmin merged 1 commit into
mainfrom
feat/live-composition-835-inc2

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented Jun 20, 2026

Copy link
Copy Markdown
Member

The #1043 review's follow-ups, still default OFF (WORLDOS_STREAM_BEATS=0). A: frontend now truly inert when off — server exposes streamBeats, the React /beat-stream poll is gated on it (proven: 0 requests when off). B: tailer killed on SIGINT/SIGTERM via pidfile+trap reaper, plus a self-bounding max-lifetime/idle cap so it can't orphan. C: beat-stream cursor resets on armPending (no dropped first chunks per beat). D: tailer captures message/content/note aliases (text-wins), matching the engine's log_event normalization. 29 tailer + 759 viewer + 13 lifecycle tests pass; OFF-default verified inert.

Summary by CodeRabbit

  • New Features

    • Narration streaming now accepts message, content, and note fields as alternatives to text.
    • Live beat-stream feature is now environment-controlled for flexible enablement.
  • Bug Fixes

    • Fixed orphaned stream-tailer processes when runs are interrupted.
    • Fixed live composition cursor not resetting between consecutive beats.
  • Tests

    • Added comprehensive lifecycle and feature-flag test coverage for stream-tailer and beat-stream functionality.

…ble + correct (still DARK)

Adversarial-review follow-ups on Increment 1 (merged). The feature stays DARK
(default WORLDOS_STREAM_BEATS=0); these fixes prepare it without enabling it.

FIX A (frontend OFF-path inertness): expose a server-visible `streamBeats` flag
on /openworlds/campaigns.json (mirrors WORLDOS_STREAM_BEATS via the new
_stream_beats_enabled(), default false). The OpenWorlds catalog poll threads it
into `state.streamBeats`, and useLiveSession now gates the /beat-stream poll
effect (the effect + fetch + interval + visibilitychange listener) on it. With
the feature off the viewer fires ZERO /beat-stream requests instead of polling
~500ms during every pending turn for a sidecar that never exists.

FIX B (orphaned tailer on signal, ON path): (1) persist the tailer PID to
$STATE_DIR/stream/tailer.pid in worldos_stream_tailer_start (the dm_turn $(...)
subshell can't export a global to the parent's trap), and reap it from the new
worldos_stream_tailer_kill_pidfile in play.sh/play_party.sh's EXIT/INT/TERM
cleanup traps — so a SIGINT/SIGTERM mid-beat no longer orphans the tailer.
(2) self-bounding defaults in stream_tailer.tail_stream: a hard wall-clock
lifetime cap (WORLDOS_STREAM_TAILER_MAX_S, default 1800s) and an idle/no-growth
cap (default 180s), so a missed stop signal can never leave it running forever.

FIX C (cross-beat cursor race): reset beatStreamCursor (+ composingText) to 0 in
armPending, not only on run change — the tailer truncates current.jsonl per beat,
so a continuing beat inheriting the prior beat's high cursor dropped the new
beat's opening preview chunks.

FIX D (text aliases): the engine's log_event normalizes message/content/note as
aliases for `text` (text or message or content or note). The Increment-1 scanner
only captured the literal `text`, so an alias beat streamed nothing. The scanner
now captures whichever of {text,message,content,note} (PROSE_TEXT_KEYS) appears
as the depth-1 prose value, with `text`-wins precedence; nested/non-alias keys
(e.g. meta) are never captured.

Tests (single-process, -p no:xdist):
  qa/test_stream_tailer.py — +8 FIX-D (each alias, alias-before-kind buffer,
    alias+non-prose-kind, text-wins, meta-negative, nested-alias-negative) +3
    FIX-B (idle cap, lifetime cap, env-bound parsing).
  qa/test_stream_tailer_lifecycle.sh — new shell test for the pidfile lifecycle
    (OFF no-op, ON persist, stop clears, signal-trap reaper kills by pidfile).
  viewer/tests/test_live_narration_stream.py — +3 (composing row ON, OFF-path
    ZERO fetches, FIX-C cursor reset proven discriminating).
  viewer/tests/test_openworlds_static.py — +1 (streamBeats reflects env) plus an
    assertion that it defaults false.
  bash -n clean on the touched scripts.

Feature still defaults OFF; the viewer no longer polls /beat-stream when off.
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

The PR delivers two enhancements to the live-composition pipeline. scripts/stream_tailer.py gains support for message/content/note prose alias keys (with precedence-based ownership in _PartialJsonScanner) and self-bounding termination caps (max_idle_s, max_lifetime_s). qa/lib_beat_driver.sh adds a pidfile mechanism to allow shell signal-trap cleanup to kill tailers orphaned inside $(...) subshells; play.sh and play_party.sh are wired to use it. viewer/server.py exposes a streamBeats env-flag to clients; viewer/openworlds/app.jsx gates /beat-stream polling on that flag and resets per-turn cursor state on each new armed turn.

Changes

Live Composition: prose aliases, self-bounding tailer, pidfile orphan cleanup, streamBeats gate

Layer / File(s) Summary
Prose alias key support in _PartialJsonScanner
scripts/stream_tailer.py, qa/test_stream_tailer.py
PROSE_TEXT_KEYS defines text/message/content/note precedence order; _PartialJsonScanner gains _prose_key_owns, _on_prose_value_start, and shrink-aware _maybe_emit alignment so alias keys stream identically to text. New FIX D decoder tests cover alias-before-kind buffering, non-prose kind gating, text precedence, non-alias rejection, and nested alias prevention.
Self-bounding termination in tail_stream and _env_bound
scripts/stream_tailer.py, qa/test_stream_tailer.py
DEFAULT_MAX_LIFETIME_S and DEFAULT_MAX_IDLE_S added; tail_stream gains a max_lifetime_s parameter and checks elapsed wall-clock time at the top of its loop before idle/stop logic. _env_bound parses WORLDOS_STREAM_TAILER_MAX_S/WORLDOS_STREAM_TAILER_IDLE_S, rejecting non-positive or invalid values. FIX B tests validate idle-cap self-termination, lifetime-cap self-termination during active growth, and _env_bound parsing semantics.
Pidfile lifecycle and signal-trap wiring
qa/lib_beat_driver.sh, scripts/play.sh, scripts/play_party.sh, qa/test_stream_tailer_lifecycle.sh
worldos_stream_tailer_start writes $STATE_DIR/stream/tailer.pid; worldos_stream_tailer_stop deletes it; new worldos_stream_tailer_kill_pidfile reads, validates, kills, and removes the pidfile for orphan recovery. _play_cleanup and _party_cleanup conditionally invoke this helper. A new bash lifecycle test validates no-op, start, orphan-kill, and no-pidfile no-op scenarios.
Server streamBeats feature flag
viewer/server.py, viewer/tests/test_openworlds_static.py
_stream_beats_enabled() returns True only when WORLDOS_STREAM_BEATS trims to "1"; streamBeats is added to the campaigns JSON response. Tests assert the field defaults to False and toggles correctly with the env variable.
Viewer /beat-stream gating, cursor reset, and tests
viewer/openworlds/app.jsx, viewer/tests/test_live_narration_stream.py
useLiveSession derives streamBeats from state; the /beat-stream effect exits immediately when off and depends on streamBeats. armPending resets beatStreamCursor and composingText per turn. loadCampaignCatalog mirrors payload.streamBeats into state. Tests cover composing-row rendering when on, inert behavior when off, and per-turn cursor reset semantics.

Sequence Diagram(s)

sequenceDiagram
  participant Server as viewer/server.py
  participant Viewer as viewer/openworlds/app.jsx
  participant BeatAPI as /beat-stream
  participant Tailer as scripts/stream_tailer.py
  Server->>Viewer: campaigns.json includes streamBeats
  Viewer->>BeatAPI: poll when streamBeats is true
  BeatAPI->>Tailer: read sidecar from since cursor
  Tailer-->>BeatAPI: chunks and next cursor
  BeatAPI-->>Viewer: composing chunks
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • electricsheephq/WorldOS#394: Overlaps directly with this PR's useLiveSession streaming changes—both modify the beat/narration live-streaming path in the viewer including polling effects and associated integration tests.
  • electricsheephq/WorldOS#1043: The initial tailer sidecar support that introduced worldos_stream_tailer_start/stop and the /beat-stream pipeline that this PR extends with pidfile cleanup and prose alias decoding.

Poem

🐇 Hoppity-hop through the signal traps I go,
No orphaned tailers left behind in the snow!
message, content, note—all aliases stream,
A pidfile stands guard while the subshells dream.
The streamBeats flag lights the composing row's gleam,
And the cursor resets fresh with each new DM scheme! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.98% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides comprehensive details of the four fixes (A-D), test counts, and enablement status, but omits the required CLA/licensing checklist and validation sections from the template. Add the Licensing/CLA checklist and Validation section listing the checks performed, as specified in the repository's PR description template.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary change: implementing Live Composition Increment 2 follow-ups with enablement/feature-gating, though slightly indirect with 'still dark' qualifier.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@100yenadmin
100yenadmin merged commit 9b7879f into main Jun 20, 2026
20 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant