Skip to content

[REA-6855] Save each reported step as a folder of files - #238

Merged
Dere-Wah merged 2 commits into
mainfrom
dere/rea-6855-save-each-step-as-a-folder
Oct 5, 2026
Merged

Dere-Wah merged 2 commits into
mainfrom
dere/rea-6855-save-each-step-as-a-folder

Conversation

@Dere-Wah

@Dere-Wah Dere-Wah commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Why

A caller that runs a session for its output, rather than to watch it, needs each step's result kept somewhere it can collect it, including the last step of a session that closed itself at its steps. Step reports and the manifest block are in place; this is the part that keeps what they describe.

What Changed

When runtime.step_results is on, every step the model reports is saved as a folder under the session's own id, the id /clips/chunks uses:

<temp>/<session id>/<step>/
├── output.mp4      every track of the step's output, one stream each
├── prompt.txt      an extra file the step carried
└── result.json     written last
{
  "step": 1,
  "session_id": "9f1e2d3c-0000-4000-8000-00000000c0de",
  "files": [{"name": "output.mp4", "content_type": "video/mp4", "size": 5170}],
  "messages": [{"type": "progress", "data": {"percent": 50}}],
  "error": null,
  "save_error": null,
  "timings": {"generate_s": 0.012, "encode_s": 0.015}
}

Each folder is written under a hidden name, result.json last, and renamed to its step number once complete, replacing any folder a reused session id left there; a reader therefore never sees a result.json beside files still being written. messages are the messages the model broadcast since the step before; they are taken on the model's thread when the step is reported, so each lands with the step it was sent before, a late report from an earlier session leaves them to the current one, and the buffer keeps the latest 256 for a model that broadcasts without reporting steps. Each step report now carries the rate its media was emitted at, which the saved video plays at: the measured throughput for a model that does not pin its rate, or the rate of the last emission for a step reported with complete_step().

Saving never makes the model wait. A step is offered to the store when it is reported, and one that finds the queue full is not saved. step_completed says which, so saved: true always means a folder follows. A step that failed is saved as result.json alone, whatever output or files its report carried, and so is a step that produced nothing. A step whose save fails still gets its result.json, with the reason in save_error and no files, so an admitted step never leaves a gap. A step reported after the session stopped at its steps is saved too.

One worker saves steps in order and journals step_result_ready with {session_id, step, files}. The session id is in the detail because a folder can finish after its session has ended, and the next session may have started. Each folder is deleted five minutes after its result.json is written, while the session runs and after it ends, so disk use follows the step rate rather than the session's length; a folder still being written is never touched. Admission and shutdown share a lock, so a step admitted while the store closes is saved and nothing is admitted after; on shutdown, pending saves get ten seconds to finish. The session descriptor reports "step_results": {"enabled": ...} from here, now that steps are saved. Only a session id that is a UUID names a folder, so a caller-chosen id cannot point outside the root.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

[codex-review] - [P2] runner.py: stale step reports can discard current-session buffered messages.

  • [P2] store.py: shutdown can accept a step after the writer exits, leaving no result folder.

Scope: full (1c8fe61..4a3e9e0).

View workflow run.

Comment thread src/reactor_runtime/runner/runner.py Outdated
Comment thread src/reactor_runtime/step_results/store.py Outdated

@tempusfrangit tempusfrangit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for result integrity: saved video should preserve the emitted media's playout rate, and an existing completion marker must not expose files while they are being rewritten. I also reproduced the existing stale-report message loss and admission/shutdown race. Please address these cases with regression tests. The error-step payload behavior is a separate contract clarification noted inline.

Comment thread src/reactor_runtime/interface/internal/reactor_core.py Outdated
Comment thread src/reactor_runtime/step_results/store.py Outdated
Comment thread src/reactor_runtime/step_results/store.py Outdated
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-single-file-mp4-writer branch from 1c8fe61 to f5bf9c5 Compare October 4, 2026 16:39
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-save-each-step-as-a-folder branch 2 times, most recently from f03b104 to 2333bb0 Compare October 4, 2026 17:46
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-single-file-mp4-writer branch from f5bf9c5 to e6ae667 Compare October 4, 2026 17:46
@Dere-Wah

Dere-Wah commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

@tempusfrangit all addressed in 2333bb0, each with a regression test: the saved video takes the emission's rate (measured throughput for unpinned models); folders are written under a hidden name and swapped in whole, so a reused session id never exposes a half-rewritten step; error steps keep only result.json; admission and close share a lock; and a stale report no longer takes the current session's messages. Details on each inline thread.

@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-save-each-step-as-a-folder branch from 2333bb0 to d976a28 Compare October 4, 2026 18:28
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-single-file-mp4-writer branch 2 times, most recently from a510431 to 355b7ab Compare October 4, 2026 23:47
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-save-each-step-as-a-folder branch from d976a28 to 2c56ea6 Compare October 4, 2026 23:47
@Dere-Wah
Dere-Wah requested a review from tempusfrangit October 5, 2026 00:03
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-single-file-mp4-writer branch from 355b7ab to d7e067d Compare October 5, 2026 17:58
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-save-each-step-as-a-folder branch from 2c56ea6 to c0645cd Compare October 5, 2026 17:58
@Dere-Wah
Dere-Wah dismissed tempusfrangit’s stale review October 5, 2026 18:06

review was addressed

Dere-Wah commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Oct 5, 6:07 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Oct 5, 6:30 PM UTC: Graphite rebased this pull request as part of a merge.
  • Oct 5, 6:31 PM UTC: @Dere-Wah merged this pull request with Graphite.

Dere-Wah added a commit that referenced this pull request Oct 5, 2026
## Why

Whether each finished step is kept as a folder of files is the model's choice, the same way recording is: it decides what is worth keeping and how to encode it. So it belongs in the model's `reactor.yaml`, next to `runtime.recording`, and not in a session option a client could switch.

## What Changed

The manifest gains a `runtime.step_results` block:

```yaml
runtime:
  step_results:
    enabled: true
    video: {codec: h264, preset: veryfast, crf: 23}   # or codec: h265
    audio: {codec: aac, bitrate_kbps: 128}
    queue: 8
```

It names no tracks: every track of a step's output is kept as its own stream. `queue` bounds how many steps may wait to be saved, so a model that steps faster than its steps encode never waits on saving; a `queue` below 1 fails the load, because an unbounded queue is what that value would otherwise mean. A missing or malformed block leaves step results off at their defaults, and unknown keys are ignored, as for the recording block.

The block parses into a `StepResultsConfig` on `RuntimeConfig`, and the README documents it where the manifest is introduced, so the authoring surface lands with its documentation. Nothing is saved yet: the writer, the store, and the routes follow in the next PRs, and the session descriptor reports `"step_results"` only from #238, where steps are saved, so it never promises folders the runtime does not write.
@Dere-Wah
Dere-Wah changed the base branch from dere/rea-6855-single-file-mp4-writer to graphite-base/238 October 5, 2026 18:26
@Dere-Wah
Dere-Wah changed the base branch from graphite-base/238 to main October 5, 2026 18:28
Dere-Wah and others added 2 commits October 5, 2026 18:29
When a model's manifest turns step results on, every step it reports is
kept as a folder under the session's own id: the step's output as one
output.mp4, the extra files the model kept with it, and result.json,
which lists the files, the messages the model broadcast since the step
before, the step's error, and how long it took to generate and to save.
result.json is written last, through a rename, so a folder that has one
is complete.

Saving never makes the model wait. A step is offered to the store when
it is reported, and one that finds the queue full (runtime.step_results
.queue, eight by default) is not saved; the step_completed fact says
which, so saved: true always means a folder follows. A step whose save
fails still gets its result.json, with the reason in save_error and no
files. One worker saves steps in order and journals step_result_ready
with the session, the step, and the file names, after the session has
ended too, so the last step of a session that stopped at its steps still
arrives. Each folder is deleted five minutes after its result.json is
written, so disk use follows the step rate rather than the session's
length. On shutdown, pending saves get ten seconds to finish.

Each step report now carries the model's playout rate, which the saved
video plays at. Messages are taken on the model's thread when the step
is reported, so each lands with the step it was sent before; the buffer
keeps the latest 256 for a model that broadcasts without reporting steps.
Only a session id that is a UUID names a folder.

Signed-off-by: Dere-Wah <derexcontact@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
A saved step's video now plays at the rate its media was emitted at.
For a model that does not pin its rate the default loop paces each emit
by its measured throughput, so the report takes the same rate the emit
chose rather than the declared fps; a step reported with complete_step()
takes the rate of the model's last emission.

A step's folder is written under a hidden name and renamed into place
once complete, replacing any folder a reused session id left there, so a
reader never sees a result.json beside files still being rewritten. A
step that failed keeps only its result.json, as documented, even when
its report carried output or files.

Admission and close() now share a lock, so a step admitted while the
store closes is queued before the worker is told to drain and is saved,
and nothing is admitted after. A late report from an earlier session no
longer takes the messages the current session has sent, so they land in
its next step. The session descriptor reports step_results, now that
steps are saved.

Signed-off-by: Dere-Wah <derexcontact@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Dere-Wah
Dere-Wah force-pushed the dere/rea-6855-save-each-step-as-a-folder branch from c0645cd to f45ade0 Compare October 5, 2026 18:29
@Dere-Wah
Dere-Wah merged commit 1cb6ee6 into main Oct 5, 2026
10 checks passed
@Dere-Wah
Dere-Wah deleted the dere/rea-6855-save-each-step-as-a-folder branch October 5, 2026 18:31
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.

3 participants