Repository navigation
Stream large skeleton packets and reports, and name each skeleton phase on stderr - #516
Conversation
…cords once Packets, the report and output-tree identities are produced and hashed in pieces, so writing no longer holds whole packets. Fingerprints escape each string once per extraction, and roots share one trusted declaration and one safety check per shared probe record. Output bytes and hashes are unchanged.
…view still requires
…test streaming directly The assembling line drops its count, which could name skeletons that end up unresolved, and the docs say the build must not change once probes end. Formalize names the paused-evidence exception at its import-edit baseline.
|
Review at b9617c4: changes requested. This is a static review, the first on this PR; test results are left to CI, where all 9 checks pass. Two things block: the branch conflicts with main, and the phase lines can still change a run's outcome when stderr is closed or broken. The streaming itself holds up. Built on 8155f23, the PR's shared-closure fixture gives the pinned tree digest ( Blocks
Notes
Posted by Claude Code (session jackmccarthy-e8). |
#501 moved the managed-output helpers into autoform_cli/_managed_output.py. This branch's edits to two of them move there too: output_identity reads files in 1 MiB blocks, and stage_output takes str or Iterable[str] and writes it with writelines, keeping main's docstrings. skeleton.py keeps main's imports plus Iterable and Iterator, and _stage_report_output calls stage_output with the report's JSON pieces.
Review block 2 on #516. Started with fd 2 closed, Python sets sys.stderr to None and print(file=None) writes to stdout, so --json output began with the phase lines. On a pipe whose reader has gone, the suppressed BrokenPipeError left the line in stderr's buffer, the flush at exit failed again, and the process exited 120 instead of 0. progress now returns when there is no stderr, and after a failed write points fd 2 at /dev/null, the pattern Python's signal documentation gives for SIGPIPE.
Review note 2 on #516: Formalize repeated "the skeleton evidence" after a rebuild even when a pause had skipped the baseline, and "Unless paused" did not say who pauses. Agent Review's "record no skeleton hash" read as the first of three hashes. Both files keep their line counts.
|
Jack asked me to take this PR's conflict, and I applied my own review's findings while there:
Called directly on macOS: Posted by Claude Code (session jackmccarthy-e8). |
|
Delta check at cc73b8d: merge. This checks only what changed since jackmccarthy-e8's review at b9617c4: the main merge (f7d59a9), 15bb607 and cc73b8d. Both blockers from that review are resolved.
Checks
Posted by Claude Code (session jackmccarthy-eb). |
Fixes #497.
Problem
The run in #497, over a large shared closure, wrote a 1,965,660,266-byte report
and 24,430,589,343 bytes of packets, 12,215,294,696 of them in one joint article
packet. It took 126 minutes, one RSS sample was near 120 GiB, and nothing in its
output told probing apart from assembly or writing. That run used 0.5.0. #461
has since removed the repeated hashing, but main still holds every packet in
memory:
write_packets' list of files, which holds every packet until the last oneis written;
+ "\n"copy, plus its encoding;and concatenates it, several times per publication;
partial-declaration check, and re-escapes its whole closure for both
fingerprints.
Change
and written from those pieces, so no packet is cached or held whole.
blind_text()still returns the full text forreview.JSONEncoder.iterencode,with the same options as
to_json.json.dumps(material, sort_keys=True, ensure_ascii=False)writes, escapingeach string once per extraction. Extraction computes every declaration's
fingerprints with one shared memo, which is freed when it returns.
TrustedDeclarationper shared probe record and checkseach record's safety once, the same way
parse_probe_outputalreadyvalidates shared entries once.
autoform skeleton --jsonwrites the report to stdout in pieces too, withthe same bytes as before.
autoform skeletonnames each phase on stderr as it starts:skeleton: probing <n> modules...when Lean probes start, thenskeleton: assembling...once none is running, thenskeleton: writing...before it writes
--outputor--packets. Onceassemblingappears no Leanprocess remains, so an orchestrator can release its Lean slot; the build must
still stay unchanged until the command exits, as before. A failed write of a
phase line cannot change a run's outcome: with no stderr (fd 2 closed) the
line is skipped, so
--jsonstdout stays the report alone, and after afailed write, as to a pipe whose reader has gone, fd 2 is pointed at
/dev/nullso the flush at exit cannot fail.development still requires: build the target, run
#print axioms, runcheckandauditwith--lean-root, and compare the statement with itssource. Such a review records no skeleton hash. Formalize's import-edit
baseline names the same exception ("Unless paused"). Debriefs already have
their opt-out (
AUTOFORM_DEBRIEF=0). Human Review andreview checkextracttheir own evidence, so a pause cannot reach approval.
Output bytes and hashes are unchanged.
Memory
The numbers come from a synthetic shared-closure fixture,
_shared_closureintests/test_skeleton.py: one article of R roots, all trusting one chain of C
definitions, each with S characters of kernel material (quotes, escapes, and
characters outside the BMP). The probe output is built in Python, with no Lean.
Each run is a fresh process, and peak RSS comes from
ru_maxrss; about 50 to60 MiB of it is the interpreter.
Before, peak RSS was about six times the packet bytes, which matches 120 GiB
against 24 GB of packets. Now it stays at what the extracted report holds. The
timings were taken on a heavily loaded host, so they are rough. At
(16, 300, 6000), the work after the probe took 4.4 s before and 2.9 s after. At
(16, 400, 10000), fingerprinting went from 1.07 s to 0.36 s.
Considered and left out: a versioned package format
The joint packet repeats every declaration packet, and each of those repeats the
shared closure. In the #497 run the joint packet is half of the packet bytes
(12.2 GB); stating each trusted item once per article would shrink it to about
one closure. That would change evidence and review hashes, and so invalidate
every recorded review, so it needs its own format proposal. A note for whoever
takes it on: a 760 MB declaration packet is too large for any reader, and a
read-back card holds at most 4 MiB, so review at that scale fails before packet
size matters. The size comes from the canonical kernel material.
Public surface added
autoform skeleton:skeleton: probing <n> modules...,skeleton: assembling...andskeleton: writing..., one as each phasestarts;
probingonly when a module is probed. They let an orchestrator tailinga long run tell probing from what follows and release its Lean slot. They
use the same shape as
claim list: <step>.... Stdout and exit statuses areunchanged, and no command or flag changed.
progress: Callable[[str], None] | None = Noneonextract_skeletonsandextract_graph_skeletons. The CLI uses it to printthe lines.
Docs
its 65-line budget).
baseline; net 0).
Overlap
#501 merged and moved
_stage_output,_output_identityand_replace_outputsinto
autoform_cli/_managed_output.pyasstage_output,output_identityandreplace_outputs. Main is merged into this branch (f7d59a9), and the twoedits moved with them:
stage_outputaccepts pieces andoutput_identityreads in blocks. #506 and #521 also edit
_managed_output.py; each mergeswith this branch without conflict.
Not changed here:
review preparestill holds every packet as a string(
ReviewArticle.packet,ReviewDeclaration.packet), and its approval hashJSON-encodes them. Given the 4 MiB card limit above, that path cannot review
packets of this size anyway.
Verification
test_shared_closure_outputs_are_pinnedhashes the report, every packet,both manifests and the passage of a two-article shared-closure fixture. The
digest was recorded on main 8155f23 before any change, and it still matches.
The streamed report also equals
to_json()and the filewrite_skeleton_reportwrites.test_report_bytes_are_pinned(from Compute skeleton report hashes once during serialization #461)still matches.
test_writing_holds_no_whole_packet: the peak allocationtracemallocrecords while
write_packetswrites a report and packets stays under a thirdof the 8.8 MiB joint packet (about 2 MiB, against 74 MiB on main). The roots
also share one
TrustedDeclarationper shared record.test_fingerprint_encoding_matches_json_dumps: the fingerprint encoderwrites
json.dumps(..., sort_keys=True, ensure_ascii=False)bytes for emptycontainers, key order, control characters, U+007F, U+2028, and characters
beyond the BMP, both before and after its memo is filled.
test_cli_names_each_phase_on_stderr_as_it_starts:probingis printedbefore the probe runs, and
assemblingandwritingafter it; stdout isunchanged.
test_cli_json_prints_the_report_bytes:--jsonstdout equalsto_json()plus a newline.test_report_serialization_fingerprints_each_declaration_onceis updated:extraction computes the two fingerprints, and serializing twice adds none.
tests/test_skeleton.py, test_review_bundle.py, test_review_cli.py,
test_release.py, test_skill_examples.py and test_doc_budgets.py pass, and
ruff passes. CI runs the real-Lean tests. The new tests are not in the
Windows CI job, so the claim of unchanged Windows bytes rests on reading the
code: files are still opened in text mode, as before.
Posted by Claude Code (session cove-591664814).