Skip to content

Commit 0cb3d54

Browse files
Update docs for proposal 0009 per-instance fan-out resume
Bring the user-facing docs in sync with the engine changes: - concepts/checkpointing.md: reverse the atomic-restart language to describe saves firing at fan-out instance internal events and the per-instance resume contract; update the CheckpointRecord shape to show fan_out_progress as tuple[FanOutProgress, ...] (default empty tuple) instead of the None placeholder; update the field framing to describe the populated shape and cross-link to fan-out's resume section. - concepts/fan-out.md: rewrite the Resume semantics section. New text covers the three-state per-instance machine (completed, in_flight, not_started), the append reducer no-double-merge guarantee, error_policy composition (fail_fast vs collect), and the optional fan-out-internal save batching knob with the InMemoryCheckpointer example. - CHANGELOG.md: add entries under Unreleased for the per-instance resume contract, the new public types (FanOutProgress, FanOutInstanceProgress, FanOutInternalSaveBatching), the CheckpointRecord field promotion, and the v0.18.0 pin bump. - examples/05-fan-out-with-retry.md: add a "Composing with checkpointing" section noting per-instance resume + attempt_index reset on resume, with cross-refs. The example itself doesn't use a Checkpointer; just documenting the composition shape. A new example demonstrating per-instance fan-out resume end-to-end and a verify-reference-docs-render pass are deferred to a follow-up PR (out of scope for this one).
1 parent 677cad2 commit 0cb3d54

4 files changed

Lines changed: 106 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The
88

99
### Added
1010

11+
- **Per-instance fan-out resume contract** (proposal 0009 / spec v0.18.0). The engine now writes a checkpoint record at every `completed` event inside a fan-out instance (in addition to the existing outermost-graph + subgraph-internal + fan-out node completion saves). On resume the engine consults the saved record's `fan_out_progress` field and treats each instance as `completed` (skip, contribution rolls forward), `in_flight` (re-run from subgraph entry), or `not_started` (dispatch normally). The `append` reducer's no-double-merge guarantee holds across resume because `completed` is a one-shot accumulator state.
12+
- **`FanOutProgress` and `FanOutInstanceProgress` public dataclasses** on `openarmature.checkpoint`. The `CheckpointRecord.fan_out_progress` field is now `tuple[FanOutProgress, ...]` (default empty tuple), with per-instance state, result, and `completed_inner_positions` observability. Was a `None` placeholder under proposal 0008.
13+
- **`FanOutInternalSaveBatching` config** on `InMemoryCheckpointer`. Backends MAY opt into batching scoped to fan-out instance internal saves to bound the write volume of high-instance-count fan-outs. Outermost-graph, subgraph-internal, and the fan-out node's own completion save remain synchronous regardless. Default off. Buffered-but-unflushed saves are lost on crash by design; on resume, instances whose `completed` state was only buffered revert and re-run. Surfaces a new optional `save_fan_out_internal` / `save_fan_out_in_flight_failure` Checkpointer Protocol seam; backends that don't implement either fall back to the standard `save`.
1114
- **Patterns docs section** at `docs/patterns/`, sibling to Concepts. Seeded with four recipes drawn from downstream usage and proposal 0008's alternatives section: parameterized entry point, tool-dispatch-as-node, session-as-checkpoint-resume, and bypass-if-output-exists. Patterns are user-level how-to recipes composing existing primitives, not framework contracts; new patterns can be added without spec coordination. Each page follows a problem / approach / snippet / when this is the right pattern / when it isn't / cross-references structure.
1215

16+
### Changed
17+
18+
- **Fan-out resume behavior** flipped from atomic restart (0008's v1 contract) to per-instance resume. A crash mid-fan-out used to re-run the entire fan-out on resume; now only the instances that did not complete-and-record their contribution re-run. The economics matter for large fan-outs of expensive work (LLM calls, long extractions): an 80% complete fan-out crash now restores 80% of its results rather than discarding them.
19+
- **`SQLiteCheckpointer` schema** picks up a new `fan_out_progress_blob` column (added via `ALTER TABLE` for backward compatibility with pre-0009 databases). Pre-0009 rows back-fill as NULL on load and round-trip as the empty-tuple default. Both `pickle` and `json` serialization modes round-trip the new field.
20+
1321
### Notes
1422

15-
- **Pinned spec version bumped to v0.17.1.** Proposal 0019 (multi-provider wire-format extension) reframes llm-provider §8 as a catalog of wire-format mappings, with the existing OpenAI-compatible body nested under §8.1. Purely textual on the spec side — no behavioral change, no fixture changes. Code and doc references to §8.X updated to match the new structure (§8.1 → §8.1.1, §8.2 → §8.1.2, §8.3 → §8.1.3, §8.5.1 → §8.1.5.1, §8.1.1 → §8.1.1.1). All existing conformance fixtures continue to pass.
23+
- **Pinned spec version bumped from v0.17.1 to v0.18.0.** Proposal 0009 (per-instance fan-out resume) is the v0.18.0 driver: pipeline-utilities §10.3 / §10.7 revised, §10.11 added with per-instance state machine + composition rules + configurable batching. The `append` reducer no-double-merge invariant from §10.11.1 is the load-bearing correctness story.
24+
- **Pinned spec version bumped to v0.17.1.** Proposal 0019 (multi-provider wire-format extension) reframes llm-provider §8 as a catalog of wire-format mappings, with the existing OpenAI-compatible body nested under §8.1. Purely textual on the spec side, no behavioral change, no fixture changes. Code and doc references to §8.X updated to match the new structure (§8.1 to §8.1.1, §8.2 to §8.1.2, §8.3 to §8.1.3, §8.5.1 to §8.1.5.1, §8.1.1 to §8.1.1.1). All existing conformance fixtures continue to pass.
1625

1726
## [0.8.0] — 2026-05-23
1827

docs/concepts/checkpointing.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ graph = (
2626
```
2727

2828
The engine writes a record at every `completed` event for outermost-
29-
graph nodes and subgraph-internal nodes. **Fan-out instance internal
30-
events do NOT save** in the shipping version. Atomic-restart is the
31-
fan-out contract.
29+
graph nodes, subgraph-internal nodes, and fan-out instance internal
30+
nodes. **Per-instance fan-out resume** is the contract: on resume the
31+
engine re-runs only the instances that did not complete-and-record
32+
their contribution into the fan-out accumulator in the prior run;
33+
completed instances skip and their contributions roll forward to the
34+
fan-in step.
3235

3336
## Saves are synchronous-by-contract
3437

@@ -79,8 +82,8 @@ class CheckpointRecord:
7982
completed_positions: tuple[NodePosition, ...]
8083
parent_states: tuple[Any, ...]
8184
last_saved_at: float
82-
schema_version: str = CHECKPOINT_SCHEMA_VERSION
83-
fan_out_progress: None = field(default=None)
85+
schema_version: str = ""
86+
fan_out_progress: tuple[FanOutProgress, ...] = field(default=())
8487
```
8588

8689
Field framing worth getting right:
@@ -103,9 +106,19 @@ Field framing worth getting right:
103106
Outermost first; empty for an outer-level save. Inner-node saves
104107
populate it so resume can re-enter a subgraph from the right
105108
depth without re-projecting.
106-
- **`fan_out_progress: None` is reserved** for a future per-instance
107-
fan-out resume mode (planned, not yet shipped). In the shipping
108-
version it's always `None`.
109+
- **`fan_out_progress` carries per-fan-out-node progress** when one
110+
or more fan-outs are in flight at save time. Each `FanOutProgress`
111+
entry records the fan-out's name, namespace, instance count, and a
112+
per-instance state machine (`not_started` / `in_flight` /
113+
`completed`) plus the recorded contribution for finalized
114+
instances. On resume the engine consults this field to decide
115+
which instances skip (their contributions roll forward) vs re-run
116+
(re-execute from the inner subgraph's declared entry node). Empty
117+
tuple when no fan-outs are in flight. See
118+
[Resume semantics](fan-out.md#resume-semantics) on the fan-out
119+
page for the full per-instance contract including reducer
120+
composition, error_policy semantics, and the optional
121+
fan-out-internal save batching.
109122

110123
## The Checkpointer Protocol
111124

docs/concepts/fan-out.md

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,59 @@ namespace.
125125
## Resume semantics
126126

127127
A fan-out node's `completed` event triggers a save like any other
128-
outermost-graph or subgraph-internal node. **Per-instance internal
129-
events do NOT save** in the shipping version; on resume, the
130-
fan-out re-runs end-to-end if it hadn't completed (atomic restart).
131-
132-
A per-instance fan-out resume mode is planned but not yet shipped.
133-
The `fan_out_progress` field on `CheckpointRecord` is reserved for
134-
its eventual contents. Until it lands, atomic restart is the
135-
shipping behavior.
128+
outermost-graph or subgraph-internal node. Per-instance internal
129+
events also save, and the resume contract is **per-instance**: the
130+
engine consults the saved record's `fan_out_progress` entry for
131+
this fan-out and treats each instance as one of three states:
132+
133+
- **`completed`**: the instance ran to completion in the prior run
134+
and recorded its contribution into the accumulator. The engine
135+
skips re-execution on resume; the contribution rolls forward to
136+
the fan-in step.
137+
- **`in_flight`**: the instance began execution but its terminal
138+
inner node had not yet fired `completed` at save time, so no
139+
contribution was recorded. On resume the engine re-runs the
140+
instance from the subgraph's declared entry node.
141+
`completed_inner_positions` on the saved record are observational
142+
only; they do NOT serve as a per-inner-node resume point.
143+
- **`not_started`**: the instance was not dispatched at save time.
144+
On resume the engine dispatches it normally.
145+
146+
The `append` reducer's no-double-merge guarantee holds because
147+
`completed` is a one-shot accumulator state: every completed
148+
instance's contribution rolls forward exactly once at fan-in.
149+
150+
Under `error_policy: collect`, a failed instance's error record IS
151+
a `completed` contribution (the error rolls forward through the
152+
`errors_field` bucket rather than `target_field`). Under
153+
`error_policy: fail_fast`, a failed instance leaves the saved
154+
record with that instance in `in_flight` state; cancelled siblings
155+
are `in_flight` or `not_started`. None are `completed`, so resume
156+
re-runs them all.
157+
158+
Per-instance saves can be high-volume in fan-outs with many
159+
instances or many inner nodes per instance. `Checkpointer` backends
160+
MAY opt into **configurable batching** scoped to fan-out instance
161+
internal saves; outermost-graph, subgraph-internal, and the fan-out
162+
node's own completion save remain synchronous. The in-memory
163+
backend exposes the knob via:
164+
165+
```python
166+
from openarmature.checkpoint import (
167+
InMemoryCheckpointer,
168+
FanOutInternalSaveBatching,
169+
)
170+
171+
cp = InMemoryCheckpointer(
172+
fan_out_internal_save_batching=FanOutInternalSaveBatching(flush_every=10),
173+
)
174+
```
175+
176+
Buffered-but-unflushed saves are lost on crash by design:
177+
instances whose `completed` state was only buffered revert to
178+
`in_flight` / `not_started` on resume and re-run. The trade-off is
179+
explicit (fewer writes per fan-out instance vs some redundant
180+
re-execution on crash recovery); default is no batching.
136181

137182
## When to reach for fan-out
138183

docs/examples/05-fan-out-with-retry.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ sentinel headline that always raises `ProviderUnavailable`; under
4646
`error_policy` at runtime. Inner-instance events carry
4747
`fan_out_index` but not the config.
4848

49+
## Composing with checkpointing
50+
51+
This example doesn't register a `Checkpointer`, but the fan-out
52+
pattern composes cleanly with checkpoint resume. When a fan-out
53+
runs under a registered backend, the resume contract is
54+
**per-instance**: instances that completed in the prior run skip
55+
re-execution and their contributions roll forward through the
56+
fan-in step; instances that were `in_flight` at save time re-run
57+
from the subgraph's entry node; not-started instances dispatch
58+
normally. The `append` reducer's no-double-merge guarantee holds
59+
across resume because `completed` is a one-shot accumulator state.
60+
61+
Composition with `instance_middleware` (retry): on resume, an
62+
instance's `attempt_index` resets to 0 (a fresh retry budget) per
63+
spec graph-engine §6's resume semantics. So a retry-exhausted
64+
instance whose `in_flight` state was saved gets a fresh budget on
65+
the resumed run.
66+
67+
See [Resume semantics in fan-out](../concepts/fan-out.md#resume-semantics)
68+
and the [Checkpointing concept page](../concepts/checkpointing.md)
69+
for the full contract.
70+
4971
## How to run
5072

5173
```bash

0 commit comments

Comments
 (0)