feat(bridge): behavior_v2 run-log format, --convert CLI, log_control ack (PR 1 of 3) - #183
Open
mbreiser wants to merge 1 commit into
Open
feat(bridge): behavior_v2 run-log format, --convert CLI, log_control ack (PR 1 of 3)#183mbreiser wants to merge 1 commit into
mbreiser wants to merge 1 commit into
Conversation
…ack + hello levels (PR 1 of runlog-behavior-v2-plan) Bridge side of docs/development/runlog-behavior-v2-plan.md (Part 1). Nothing in the Studio or dashboard changes; old Studios never send behavior_v2 and ignore the new ack messages. - behavior_v2 (new default): the browser's arena_command echo (76 % of a v1 file) is written as ["a", t_off, dt, hex, status, rx_off(, error)] with ms offsets from the schema line's t0. Frame arrays are unchanged; runner / session / run_metadata / config lines stay verbatim objects. Lossless by construction: compact_arena_command verifies every invariant the expansion relies on (12-key set, dir, int t/rx_ms, spaced lowercase hex head, len, echo = command byte and ok = status===0 — or all three null on timeout) and writes a non-fitting echo verbatim instead. - bridge.py --convert IN OUT [--to v1|v2]: offline v1 ⇄ v2 re-encoding, direction auto-detected, .jsonl.gz on either side, strict (unknown arena key raises). Files without a v1 schema (pre-#140, full level) get a v2 schema with cols:null inserted; the reverse drops it. websockets import is now optional so --convert runs without the server deps. - log_control → log_control_ack {enabled, level, requested, file} with the level ACTUALLY in force (an unknown requested level is ignored, not applied); hello → hello_ack {bridge, levels, level, logging}. --log-level flag (--log-frames = alias for full). BRIDGE_VERSION 3.0. - tests/test-bridge-behavior.py: +99 checks — encode/decode invariants on real line samples (ok / status-1 / timeout / non-null error / other cmd), strict vs lenient fallbacks, file round trips (.jsonl + .gz), legacy path, LogWriter output per level, dispatcher acks. - scripts/runlog-v2-corpus.py: the corpus gate — round trip + known-shape check + size table over every log in a course-repo clone. 164/164 pass on cshl-2026-course origin/main (1281.6 MB v1 → 655.6 MB v2 → 211.3 MB v2.gz). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bridge side (PR 1 of 3) of docs/development/runlog-behavior-v2-plan.md. The Studio and the dashboard are not touched; old Studios never send
behavior_v2and ignore the new ack message types, so this is safe to deploy first. Benches needgit pull+ restartpixi run bridgeto pick it up (the ack now makes a stale bridge visible instead of silent).What changed (
fictrac-bridge/bridge.py, BRIDGE_VERSION 3.0)behavior_v2log level (new default). The browser'sarena_commandecho — one per closed-loop 0x70 frame command, 76 % of a v1 file — is written as["a", t_off, dt, hex, status, rx_off](+ a 7therrorstring when non-null), with ms offsets from the schema line'st0. Frame arrays[ms,fc,idx,ft,x,y,hd]are unchanged; runner / session / run_metadata / config lines stay verbatim objects. Schema line:{"type":"frame_schema","level":"behavior_v2","cols":[…],"arena_cols":["t_off","dt","hex","status","rx_off"],"t0":<epoch ms>}.compact_arena_commandverifies every invariant the expansion relies on (fixed 12-key set,dir, intt/rx_ms, spaced lowercase hexheadwithlen= byte count,echo= command byte andok=status === 0, or all three null on timeout). A line that does not fit is written verbatim by the live bridge (never dropped) and raises under--convert.bridge.py --convert IN OUT [--to v1|v2]— offline v1 ⇄ v2 re-encoding, direction auto-detected,.jsonl.gzon either side (reproducible gzip). Files without a v1 schema line (pre-Run-log: curated FicTrac column subset (middle ground) + optional size reduction #140 logs,fulllevel) get a v2 schema with"cols": nullinserted after the session line; the reverse drops it. Thewebsocketsimport is now optional so--convertand the tests run without the server.log_control→log_control_ack {enabled, level, requested, file}wherelevelis the one actually in force (an unknown requested level is ignored, not applied).hello→hello_ack {bridge, levels:["behavior_v2","behavior_v1","full"], level, logging}. New--log-levelflag;--log-framesstays as an alias forfull.status: null, echo: null, ok: null— notok: false. The PR 3 reader'sexpandV2Linemust reproduce that.run_metadata.log_formatis left to the Studio PR (the Studio composes that line and will know the acked level); injecting it in the bridge would break the exact round trip.Tests
tests/test-bridge-behavior.py(inpixi run test): 27 → 126 checks. Encode/decode on real line samples (ok, status-1 reject, timeout with null status/echo/ok + error, synthetic error-with-status, a 0xa0 command), 12 must-not-compact cases (unknown key, echo/ok mismatch, truncatedhead, floatt, …) in both strict and lenient modes, whole-file v1→v2→v1 round trips (in-memory,.jsonl,.jsonl.gz), the legacy no-schema path, LogWriter output per level, and the dispatcher'shello_ack/log_control_ack(incl. an unknown level and a missing websocket).pixi run testpasses.behavior_v2, confirm"a"lines in the file and that--convertround-trips it.Corpus gate —
scripts/runlog-v2-corpus.pyRun over a
git archiveof cshl-2026-course origin/main (164 logs; the local checkout is 9 commits / 15 logs behind and was left untouched). Every file: strict v1→v2→v1, canonical-JSON identity line by line, plus a known-line-shape check (any key outside the 2026-09-06 inventory fails the run). 164 / 164 pass.Round trip v1 → v2 → v1 (strict converter, canonical-JSON identity per line): 164 / 164 files pass. Formats: 149 behavior_v1, 14 legacy, 1 full. Wall time 36 s (4 workers).
gzip level 6 (≈ the browser's CompressionStream).
v1 on diskcounts the July logs' non-compact whitespace; compact v1 would be 1278.3 MB.Per rig
Largest 12 files
All files (MB)
🤖 Generated with Claude Code