Skip to content

Commit 107236b

Browse files
Bump spec to v0.22.1; refresh docs and CHANGELOG
Submodule pin advances from v0.20.1 to v0.22.1, absorbing proposals 0027 (result_is_error, v0.21.0), 0028 (schema_version canonical, v0.21.1), 0029 (count drift strict, v0.22.0), and 0030 (drain snapshot + timeout-input validation, v0.22.1 — purely textual; the python engine already implements both behaviors per the 0010 PR's review-round fix). CHANGELOG Unreleased section gains result_is_error and count-drift entries under Added; schema_version sourcing change noted under Changed; cumulative pin-bump summary updated to v0.17.0 -> v0.22.1 across ten spec versions absorbed this cycle. docs/concepts/checkpointing.md fan_out_progress framing updated with the explicit result_is_error discriminator and the count-drift strict-raise behavior; new "Canonical source for schema_version" subsection documents the declared-class rule.
1 parent bb02aff commit 107236b

6 files changed

Lines changed: 34 additions & 7 deletions

File tree

CHANGELOG.md

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

99
### Added
1010

11+
- **`FanOutInstanceProgress.result_is_error` field** (proposal 0027, accepted in spec v0.21.0). Explicit boolean discriminator on each per-instance entry in `CheckpointRecord.fan_out_progress``True` for `collect`-mode error contributions (roll forward into `errors_field`), `False` for success contributions (roll forward into `target_field`). The engine reads the explicit field on resume rather than inferring routing from `result`'s shape; the previous structural heuristic (`_looks_like_error_record`) is removed. Backward-compat path on load: pre-0027 records that omit the key default to `False`.
12+
- **Strict `CheckpointRecordInvalid` on fan-out count drift** (proposal 0029, accepted in spec v0.22.0). When the resumed run's resolved instance count differs from the saved `fan_out_progress` entry's `instance_count`, the engine raises `CheckpointRecordInvalid` before any fan-out instance work runs on the resumed path. Replaces the pre-0029 pad/truncate behavior which silently dropped `completed` contributions on shrink (breaking §10.11.1's exactly-once guarantee) and dispatched unsaved work on grow.
1113
- **`tool_choice` parameter on `Provider.complete()`** (proposal 0025, accepted in spec v0.20.0). Optional discriminated-union value constraining the model's tool-calling behavior — one of `"auto"`, `"required"`, `"none"`, or a `ForceTool(name=...)` record. Validation runs pre-send: `"required"` and `ForceTool` both demand non-empty `tools`, and `ForceTool.name` must appear in the supplied list; violations raise `ProviderInvalidRequest` (§7's existing category — no new error category). When `tool_choice` is `None` (the default) the wire field is omitted and the provider's own default applies, preserving pre-0025 behavior exactly. The `OpenAIProvider` maps the spec shape onto OpenAI's wire shape per §8.1.1 (the `ForceTool.type="tool"` renames to wire `type="function"`).
1214
- **`ForceTool` and `ToolChoice` public types** at `openarmature.llm.ForceTool` / `openarmature.llm.ToolChoice`. `ForceTool` is a frozen Pydantic model with `type: Literal["tool"] = "tool"` and `name: str`; `ToolChoice = Literal["auto", "required", "none"] | ForceTool` is the type alias used in `Provider.complete()`'s signature.
1315
- **`validate_tool_choice` public validator** at `openarmature.llm.validate_tool_choice`. Standalone validator covering the three §5 pre-send rules; useful for third-party `Provider` implementations that want to reuse the canonical validation logic.
@@ -20,14 +22,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The
2022

2123
### Changed
2224

25+
- **`CheckpointRecord.schema_version` sourcing** clarified per proposal 0028 (spec v0.21.1). Every save site within an invocation now reads `schema_version` from the declared graph state class — the class passed to `GraphBuilder(...)` — threaded as `context.state_cls`. Previously the outer dispatch save read from the declared class while fan-out instance internal saves read from `type(state)` at save time; the inconsistency only surfaced when a user passed a State subclass that shadowed `schema_version`, but the divergence made `§10.12` migration lookups unreliable across save sites. Now uniform across outer / subgraph-internal / fan-out instance internal saves.
2326
- **`Provider.complete()` signature** extended with an optional `tool_choice: ToolChoice | None = None` parameter (per proposal 0025 v0.20.0). Backward-compatible: callers that omit the new argument see no wire-shape change. Third-party `Provider` implementations MUST add the parameter to remain Protocol-conformant under strict type checking (and to accept calls that pass `tool_choice` without raising `TypeError`); they MAY ignore it in their wire-body emission, which is how "provider doesn't honor tool_choice" looks at the impl level. The `OpenAIProvider` wire mapping is implemented per §8.1.1.
2427
- **`CompiledGraph.drain()` return type** changed from `None` to `DrainSummary` (pre-1.0; per proposal 0010 v0.19.0 contract). Callers that ignored the return are unaffected — `await graph.drain()` discards the returned dataclass exactly as before. Callers that explicitly typed the return as `None` will need to update their annotation.
2528
- **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.
2629
- **`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.
2730

2831
### Notes
2932

30-
- **Pinned spec version bumped from v0.17.0 to v0.20.1 over this Unreleased cycle.** Six spec versions absorbed: v0.17.1 (proposal 0019, multi-provider wire-format extension — purely textual reframe of llm-provider §8 as a catalog of wire-format mappings; OpenAI-compatible body nested under §8.1), v0.18.0 (proposal 0009, per-instance fan-out resume — pipeline-utilities §10.3 / §10.7 revised, §10.11 added; the `append` reducer no-double-merge invariant is the load-bearing correctness story), v0.18.1 (fixture-only patch correcting an off-by-one literal in fixture 052's expected `results`), v0.19.0 (proposal 0010, bounded drain timeout — graph-engine §6 amended with the `timeout` parameter and `DrainSummary` return contract), v0.20.0 (proposal 0025, llm-provider `tool_choice` — §5 / §7 / §8.1.1 amended; see Added / Changed above), and v0.20.1 (proposal 0026, llm-provider §8.X wire-format mapping subsection template — purely textual §8 framing paragraph; the existing OpenAI §8.1 mapping is the template's reference shape so no python module-level work was needed). All existing conformance fixtures continue to pass.
33+
- **Pinned spec version bumped from v0.17.0 to v0.22.1 over this Unreleased cycle.** Ten spec versions absorbed: v0.17.1 (proposal 0019, multi-provider wire-format extension — purely textual reframe of llm-provider §8 as a catalog of wire-format mappings; OpenAI-compatible body nested under §8.1), v0.18.0 (proposal 0009, per-instance fan-out resume — pipeline-utilities §10.3 / §10.7 revised, §10.11 added; the `append` reducer no-double-merge invariant is the load-bearing correctness story), v0.18.1 (fixture-only patch correcting an off-by-one literal in fixture 052's expected `results`), v0.19.0 (proposal 0010, bounded drain timeout — graph-engine §6 amended with the `timeout` parameter and `DrainSummary` return contract), v0.20.0 (proposal 0025, llm-provider `tool_choice` — §5 / §7 / §8.1.1 amended), v0.20.1 (proposal 0026, llm-provider §8.X wire-format mapping subsection template — purely textual §8 framing paragraph; the existing OpenAI §8.1 mapping is the template's reference shape so no python module-level work was needed), v0.21.0 (proposal 0027, explicit `result_is_error` discriminator on `fan_out_progress` per-instance entries — see Added above), v0.21.1 (proposal 0028, canonical source for `schema_version` — declared graph state class wins over runtime subclass shadowing; see Changed above), v0.22.0 (proposal 0029, strict `CheckpointRecordInvalid` on fan-out count drift — see Added above), and v0.22.1 (proposal 0030, drain snapshot semantic + timeout-input validation — purely textual; python already implemented both behaviors per the 0010 impl PR, so no module-level work needed). All existing conformance fixtures continue to pass.
3134

3235
## [0.8.0] — 2026-05-23
3336

docs/concepts/checkpointing.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,27 @@ Field framing worth getting right:
113113
`completed`) plus the recorded contribution for finalized
114114
instances. On resume the engine consults this field to decide
115115
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
116+
(re-execute from the inner subgraph's declared entry node). Each
117+
per-instance entry carries an explicit `result_is_error` boolean
118+
that discriminates success contributions (roll forward into
119+
`target_field`) from `collect`-mode error contributions (roll
120+
forward into `errors_field`) — the engine reads the explicit field
121+
on resume rather than inferring routing from the shape of `result`.
122+
Empty tuple when no fan-outs are in flight. See
118123
[Resume semantics](fan-out.md#resume-semantics) on the fan-out
119124
page for the full per-instance contract including reducer
120125
composition, error_policy semantics, and the optional
121126
fan-out-internal save batching.
122127

128+
**Count drift between runs raises.** If the resumed run's resolved
129+
instance count differs from the saved entry's `instance_count`
130+
(e.g., the user shrunk or grew the `items_field` list between
131+
crash and resume), the engine raises `CheckpointRecordInvalid`
132+
before any fan-out instance work runs. The strict raise prevents
133+
silent contribution loss (under shrink) or dispatching unsaved
134+
work (under grow); the user either coheres the inputs or restarts
135+
cleanly.
136+
123137
## The Checkpointer Protocol
124138

125139
Four methods:
@@ -206,6 +220,16 @@ resolves a chain through the registry (BFS for the shortest path),
206220
applies each migration in order to the record's state, then
207221
deserializes the result into your current state class.
208222

223+
**Canonical source for `schema_version`.** The framework reads
224+
`schema_version` from the state class declared at graph construction
225+
time — the class passed to `GraphBuilder(...)`. If you pass a State
226+
subclass instance at runtime whose `schema_version` shadows the
227+
declared class's value, the saved record still carries the declared
228+
class's value. This rule keeps every save site within an invocation
229+
consistent (outer dispatch saves, subgraph-internal saves, fan-out
230+
instance internal saves all report the same version) and aligns
231+
with how the migration registry is keyed.
232+
209233
### Chain resolution
210234

211235
Registered migrations form a directed graph. Each

openarmature-spec

Submodule openarmature-spec updated 27 files

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Repository = "https://github.com/LunarCommand/openarmature-python"
4848
Specification = "https://github.com/LunarCommand/openarmature-spec"
4949

5050
[tool.openarmature]
51-
spec_version = "0.20.1"
51+
spec_version = "0.22.1"
5252

5353
[dependency-groups]
5454
dev = [

src/openarmature/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""OpenArmature: workflow framework for LLM pipelines and tool-calling agents."""
22

33
__version__ = "0.8.0"
4-
__spec_version__ = "0.20.1"
4+
__spec_version__ = "0.22.1"

tests/test_smoke.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
def test_package_versions() -> None:
1111
assert openarmature.__version__ == "0.8.0"
12-
assert openarmature.__spec_version__ == "0.20.1"
12+
assert openarmature.__spec_version__ == "0.22.1"
1313

1414

1515
def test_spec_version_matches_pyproject() -> None:

0 commit comments

Comments
 (0)