Skip to content

runs: build_record, record_json, done_status for hosts that write their own run record - #122

Merged
guijacquemet merged 2 commits into
mainfrom
claude/runs-build-record
Oct 13, 2026
Merged

guijacquemet merged 2 commits into
mainfrom
claude/runs-build-record

Conversation

@guijacquemet

Copy link
Copy Markdown
Contributor

Small code PR, second of two (the first, docs and data, is #121). Found by napari phase 2 theme 1 (LabConstrictor-napari #51), which had to copy runs.record because a host that makes its own folder and never prunes could not use it.

What

Three functions in labconstrictor_tools.runs. None of them creates a folder, deletes anything or prunes.

  • build_record(app, tool_id, inputs, task, seconds, stderr="", *, run_id=None, started_utc=None, finished_utc=None) -> dict: the content of run.json, the same dict record writes. With run_id and started_utc the record also carries them; without them it has neither key, so the CLI's record is byte-for-byte what it was.
  • record_json(content) -> str: the text of run.json.
  • done_status(task_status, cancel_requested=False) -> str: the .lc-done.json status (COMPLETE ok, CANCELED cancelled, FAILED failed, CRASHED cancelled after Cancel and failed otherwise; SPEC 3.2, decision of 13 Oct in Record two decisions of 13 Oct: changed-size sentence, crash after Cancel (spec, data, fixtures) #121).

record is rebuilt from them. I did not add record(..., prune=False): record creates the folder at the end of the run, and a host (Napari) creates it when the run starts, owner marker first, so only the pure pieces help. retention.write_owner and write_done are unchanged; hosts keep starting the CLI prune as a subprocess.

Behaviour of runs.record / the CLI

Unchanged for every case the CLI can produce (tested for all four task statuses, the content, the owner host, the prune call, never raising). One difference, by the decision in #121: a task that is CRASHED with cancel_requested set gets cancelled instead of failed. client.run_once never calls Task.cancel and Ctrl-C already writes cancelled, so the CLI cannot reach it; a host calling runs.record itself can.

Compat: which Tools a host must require

  • The functions are in the first Tools release after 0.2.0rc1 (the release that also lists retention_prune); they are NOT in 0.2.0rc1 (_version.py is still 0.2.0rc1, tag v0.2.0-rc.1). A host that calls them requires that release as min_tools_version; until its number exists it can check hasattr(runs, "build_record"). Written in docs/PROTOCOL.md (retention section) and docs/OPERATIONS.md (the release procedure reminder to write the number and tell the hosts).
  • No features id. An id says a worker or CLI behaviour exists, something a Fiji or QuPath host can check in the schema. These are additive functions of the Python package that only a Python host imports; a version minimum states the same thing without a second list. FEATURES is unchanged (a test pins it).

Tests

  • tests/test_run_record_builder.py: content against a hand-written dict (keys and order), nothing written or pruned, record writes the same content, text equals what record writes, run_id/started_utc only when given, the stderr tail cut, non-JSON values, every status and Cancel combination of done_status, record keeps its behaviour, and the Napari flow end to end (owner first, finish without prune, later retention.prune leaves 50).
  • 15 sabotage rows (one existing row updated to the new line, 14 new), each caught by assertion.
  • Ledger row at the end of the Fixed table (Record two decisions of 13 Oct: changed-size sentence, crash after Cancel (spec, data, fixtures) #121's is at the top, so the two do not conflict).

For the napari follow-up

In napari_labconstrictor/_retention.py it can delete _DONE_STATUS and done_status (use runs.done_status(task.status, task.cancel_requested)), and in _widget.py::_record_run the 20-line content = {...} dict becomes runs.build_record(app, tool, request, task, seconds, stderr, run_id=snapshot.run_id, started_utc=snapshot.started); json.dumps(content, indent=2, default=str) in _finish_record becomes runs.record_json(content), and its STDERR_TAIL_CHARS import can go (runs.STDERR_TAIL_CHARS). It keeps RunRecord.start, _make_folder, ResultsFolder, discard_job_dir, start_prune and its retention_prune gate. It must raise its Tools minimum (or guard with hasattr) before it deletes the copies. Note one key-order detail: napari writes run_id first and started_utc after tool, and so does build_record; napari currently writes run_id and started_utc as null when it has no snapshot, build_record omits them.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN


Generated by Claude Code

…ir own run record

A host that makes its run-record folder itself (owner marker when the run
starts) and must never prune had to copy runs.record. Three pure functions
now carry what it needs: build_record (the content of run.json, exactly what
record writes; run_id and started_utc only when given), record_json (its
text) and done_status (the .lc-done.json status, SPEC 3.2: a crash after
Cancel is cancelled). record uses them; the CLI's record is unchanged except
that a crash after Cancel is cancelled, a case the CLI cannot produce.

No features id (an additive helper of the Python package); hosts require the
first Tools release after 0.2.0rc1. Documented in PROTOCOL.md and
OPERATIONS.md, with tests, sabotage rows and a ledger row.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN
@guijacquemet
guijacquemet merged commit 0f97d65 into main Oct 13, 2026
14 checks passed
@guijacquemet
guijacquemet deleted the claude/runs-build-record branch October 14, 2026 16:10
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.

2 participants