Skip to content

Persist outputs to a store - #52

Open
SimonHeybrock wants to merge 4 commits into
freeze-and-selectfrom
store-and-persist
Open

SimonHeybrock wants to merge 4 commits into
freeze-and-selectfrom
store-and-persist

Conversation

@SimonHeybrock

Copy link
Copy Markdown
Member

Implements persisting, the third part of the design in #50: nothing is written unless persisted, and the store keeps what is. Stacked on #51, which is stacked on #50.

What changes

  • The backend takes a store (local(store=...), Backend(..., store=...)). ess.dispatch.testing.FakeStore holds values in memory; without a store, persisting is refused.
  • client.submit(..., persist=True | names) and client.freeze(acc, persist=...) hand the records to the store: the client does not keep them, the record finishes once the outputs named are written, and a failed write fails the record with the write's reason.
  • client.persist(records, *outputs) adds the store next to the client's hold. A failed write fails only that persist request; the client may ask again.
  • Every client of the proposal reads and references persisted outputs. A request or a read that may read an output only because it is persisted waits for the write, and fails if the write fails.
  • History logs persist requests and writes. A record of an accumulator's state is logged as the accumulator and its number of pushes, and its rows are listed when it is read, so history grows by a constant amount per read.
  • A restart cancels pending work that no persist request needs, runs persisted work whose inputs are datasets, written, or produced by work that runs, and fails the rest; records of states fail. A write of a completed record that had not ended has failed.
  • Closing a backend waits for pending writes. The trigger loop persists what a rule names, every output by default.
  • The story tests D1 to D3, D6, and D7 now persist as their text says; F2 uses the upgraded client. The story fixtures have a fake store, and upgrade takes over the history and the store after ending the old clients.

Why

The service must not keep values for unattended work, and must not write what nobody asked for (ADR 0005). Logging records of states by count gives the restart rule the marker it needs and keeps history linear in the number of pushes (ADR 0004).

ADR 0005 contradicted itself on a record persisted at submission whose write had not ended at a restart. The log cannot tell a write in progress from a workflow still running, so such a record follows the ordered restart rule and runs again; the docs now say so.

Test plan

  • pytest -n auto in packages/essdispatch (203 passed, 6 xfailed) and packages/essspec (99 passed), several runs
  • New tests/persist_test.py: persisting at submission, client.persist, failed writes, readers that wait for a write, freezing with persist=, closing, and the restart rules

🤖 Generated with Claude Code

SimonHeybrock and others added 2 commits October 7, 2026 12:39
…ount

A record of an accumulator's state, a freeze's included, is logged as the
accumulator and its number of pushes; the views list its rows when it is
read, from the template's typed values that opening now logs. This marks
records of states, which the restart rule needs.

The backend takes a store. submit(persist=), freeze(persist=), and
client.persist log persist requests; a worker writes the outputs named
when the workflow returns, or at once for a completed record, and logs
the write. A record persisted at submission finishes once written and
fails if the write fails; the client does not keep it. A failed write of
client.persist fails only that request. Persisted outputs are read and
referenced by every client of the proposal; a reader that relies on the
persist request waits for the write and fails with it. Closing waits for
pending writes. A restart cancels what nothing persisted needs, runs
persisted work whose inputs are written, and fails the rest.

The trigger loop persists what a rule names, every output by default.
The story fixtures get a fake store and an upgrade that takes over the
history; D1 to D3, D6, D7, and F2 take their documented form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR 0005 said a record persisted at submission fails with a write that had
not ended at a restart, while its ordered rule runs such a pending record
again; the log cannot tell a write in progress from a workflow that has
not returned. The sentence now covers writes of completed records only.

History's list of accumulators holds the template's values typed, which
give the plain request of a state with the pushes. system.md's status line
says persisting is implemented over a fake store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SimonHeybrock and others added 2 commits October 7, 2026 13:05
A record persisted at submission finishes once written, so its write
needs no event of its own: Finished carries the omitted outputs, and
Written is logged only for client.persist. The views hold one write
state per output, and _access states the read rule once for
submissions, stages, and reads.

- client.persist of a pending record no longer delays its completion,
  and a record lets go of its inputs before its write.
- A client reads the values it keeps from memory and the others from
  the store, so it never gets another client's value.
- Closing a client raises naming its client.persist writes that failed,
  and reading every output raises if one of their writes failed.
- A restart runs again a record that wrote but did not finish, and the
  reasons of records failed at a restart name a lost write or an output
  not returned.
- The plain request of a state comes from the views alone; _check_state
  returns only why it would be refused.
- Rule.persist names outputs or is None for every one.
- Story fixtures: upgrade defaults to every toy spec and connect follows
  it; D2 closes its client before the morning; E1 and E4 read with
  another client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A persist request made at submission is part of the record, and its
write ends with the record's finish. A client reads persisted values it
does not keep from the store. Closing a client raises naming its failed
client.persist writes. The store's writes replace, and it may hold
values no history names. The restart paragraph follows the ordered
rule. D2 closes its client, E1 and E4 read with another, and the
upgrade convention follows the fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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