Skip to content

Commit 56cb5e4

Browse files
authored
Merge pull request #190 from Idleness76/work-041
0.5.0 changes
2 parents dcdb7cb + f1ebde2 commit 56cb5e4

39 files changed

Lines changed: 3007 additions & 136 deletions

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_dispatch:
55
inputs:
66
version:
7-
description: 'Version to release (e.g., 0.3.0)'
7+
description: 'Version to release (e.g., 0.5.0)'
88
required: true
99
type: string
1010
dry_run:
@@ -77,7 +77,7 @@ jobs:
7777
- name: Validate version format
7878
run: |
7979
if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
80-
echo "Error: Version must be in format X.Y.Z (e.g., 0.3.0)"
80+
echo "Error: Version must be in format X.Y.Z (e.g., 0.5.0)"
8181
exit 1
8282
fi
8383

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.5.0] - 2026-05-08
11+
12+
### Added
13+
- `AppRunner::create_iterative_session(...)` and `AppRunner::invoke_next(...)` for repeated graph invocations under one durable session lineage.
14+
- `RunnerError::InvalidIterativeEntry` for invalid iterative entry nodes.
15+
- Typed state-slot helpers: `StateKey<T>`, `StateSnapshot::get_typed(...)`, `StateSnapshot::require_typed(...)`, `VersionedState::add_typed_extra(...)`, `VersionedStateBuilder::with_typed_extra(...)`, and `NodePartial::with_typed_extra(...)`.
16+
- Runtime clock injection through `RuntimeConfig::with_clock(...)`, `AppRunnerBuilder::clock(...)`, and `NodeContext::now_unix_ms()`.
17+
- Optional node event metadata for `invocation_id` and `now_unix_ms` when runtime metadata is configured.
18+
- `INVOCATION_END_SCOPE` and `AppRunner::finish_iterative_session(...)` for long-lived iterative event streams.
19+
- Graph and run metadata helpers: `App::graph_metadata()`, `App::graph_definition_hash()`, `RuntimeConfig::config_hash()`, and `AppRunner::run_metadata()`.
20+
- `Reducer::definition_label(...)` so graph metadata can distinguish reducer implementations, not only reducer counts.
21+
- Replay conformance helpers in `weavegraph::runtimes::replay` for normalized event comparison, final-state comparison, and reusable replay assertions.
22+
23+
### Notes
24+
- This feedback package ships as `0.5.0` rather than `0.4.1` because it changes the public runtime surface, adds public error enum variants/types, and extends public structs.
25+
- New public metadata/context structs are marked `#[non_exhaustive]` where they are expected to grow before v1.
26+
1027
## [0.4.0] - 2026-04-01
1128

1229
### Added

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "weavegraph"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2024"
55
description = "Graph-driven, concurrent agent workflow framework with versioned state, deterministic barrier merges, and rich diagnostics."
66
license = "MIT"

README.md

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ Weavegraph lets you build robust, concurrent, stateful workflows using a graph-b
2121
- Concurrent graph execution with dependency resolution
2222
- Type-safe, role-based message system
2323
- Versioned state with snapshot isolation
24+
- Typed state slots for schema-versioned JSON payloads
2425
- Structured error handling and diagnostics
2526
- Built-in event streaming and observability
26-
- Flexible persistence: SQLite or in-memory
27+
- Flexible persistence: SQLite, PostgreSQL, or in-memory
2728
- Conditional routing and dynamic edges
29+
- Iterative checkpointed sessions for repeated invocations
30+
- Replay conformance helpers and deterministic graph/run metadata
2831
- Ergonomic APIs and comprehensive examples
2932

3033
## Install
@@ -33,10 +36,10 @@ Add to your `Cargo.toml`:
3336

3437
```toml
3538
[dependencies]
36-
weavegraph = "0.3"
39+
weavegraph = "0.5"
3740
```
3841

39-
> **Note:** Examples and instructions in this README are current as of 0.3.x. For upgrading from 0.2.x, see [MIGRATION.md](docs/MIGRATION.md).
42+
> **Note:** Examples and instructions in this README are current as of 0.5.x. For upgrade notes across pre-1.0 releases, see [MIGRATION.md](docs/MIGRATION.md).
4043
4144
## Dependency Compatibility
4245

@@ -64,40 +67,40 @@ See [Cargo.toml](Cargo.toml) for complete dependency versions and feature config
6467

6568
```rust
6669
use weavegraph::{
67-
graphs::GraphBuilder,
68-
message::Message,
69-
node::{Node, NodeContext, NodePartial},
70-
state::VersionedState,
70+
graphs::GraphBuilder,
71+
message::Message,
72+
node::{Node, NodeContext, NodePartial},
73+
state::VersionedState,
7174
};
7275
use async_trait::async_trait;
7376

7477
struct HelloNode;
7578

7679
#[async_trait]
7780
impl Node for HelloNode {
78-
async fn run(
79-
&self,
80-
_snapshot: weavegraph::state::StateSnapshot,
81-
_ctx: NodeContext,
82-
) -> Result<NodePartial, weavegraph::node::NodeError> {
83-
Ok(NodePartial::new().with_messages(vec![Message::assistant("Hello, world!")]))
84-
}
81+
async fn run(
82+
&self,
83+
_snapshot: weavegraph::state::StateSnapshot,
84+
_ctx: NodeContext,
85+
) -> Result<NodePartial, weavegraph::node::NodeError> {
86+
Ok(NodePartial::new().with_messages(vec![Message::assistant("Hello, world!")]))
87+
}
8588
}
8689

8790
#[tokio::main]
8891
async fn main() -> Result<(), Box<dyn std::error::Error>> {
89-
use weavegraph::types::NodeKind;
90-
let app = GraphBuilder::new()
91-
.add_node(NodeKind::Custom("hello".into()), HelloNode)
92-
.add_edge(NodeKind::Start, NodeKind::Custom("hello".into()))
93-
.add_edge(NodeKind::Custom("hello".into()), NodeKind::End)
94-
.compile()?;
95-
let state = VersionedState::new_with_user_message("Hi!");
96-
let result = app.invoke(state).await?;
97-
for message in result.messages.snapshot() {
98-
println!("{}: {}", message.role, message.content);
99-
}
100-
Ok(())
92+
use weavegraph::types::NodeKind;
93+
let app = GraphBuilder::new()
94+
.add_node(NodeKind::Custom("hello".into()), HelloNode)
95+
.add_edge(NodeKind::Start, NodeKind::Custom("hello".into()))
96+
.add_edge(NodeKind::Custom("hello".into()), NodeKind::End)
97+
.compile()?;
98+
let state = VersionedState::new_with_user_message("Hi!");
99+
let result = app.invoke(state).await?;
100+
for message in result.messages.snapshot() {
101+
println!("{}: {}", message.role, message.content);
102+
}
103+
Ok(())
101104
}
102105
```
103106
> NOTE: `NodeKind::Start` and `NodeKind::End` are virtual structural endpoints.
@@ -109,27 +112,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
109112
For testing and ephemeral workflows use the InMemory checkpointer:
110113

111114
```rust
115+
use weavegraph::runtimes::{AppRunner, CheckpointerType};
116+
112117
// After compiling the graph into an `App`:
113118
let runner = AppRunner::builder()
114-
.app(app)
115-
.checkpointer(CheckpointerType::InMemory)
116-
.build()
117-
.await;
119+
.app(app)
120+
.checkpointer(CheckpointerType::InMemory)
121+
.build()
122+
.await;
118123
```
119124

120125
Run the comprehensive test suite:
121126

122127
```bash
123-
# All tests with output
124-
cargo test --all -- --nocapture
128+
# Full integration test suite
129+
cargo nextest run
130+
131+
# Documentation examples
132+
cargo test --doc
133+
134+
# Lints used by CI
135+
cargo clippy --all-features --all-targets -- -D warnings
136+
cargo clippy --no-default-features --lib -- -D warnings
125137

126138
# Specific test categories
127-
cargo test schedulers:: -- --nocapture
128-
cargo test channels:: -- --nocapture
129-
cargo test integration:: -- --nocapture
139+
cargo test --test schedulers
140+
cargo test --test event_bus
141+
cargo test --test runtimes_runner
130142
```
131143

132-
Property-based testing with `proptest` ensures correctness across edge cases.
144+
Property-based testing with `proptest` and fuzz harnesses under [fuzz/](fuzz/) exercise edge cases across graph routing, event serialization, replay comparison, and typed state slots.
133145

134146
## CI Parity
135147

@@ -151,7 +163,7 @@ Before merging or cutting a release, run full local parity checks:
151163

152164
## Resources
153165

154-
- **[Migration Guide](docs/MIGRATION.md)** - Upgrade paths between releases (0.2.x → 0.3.x and beyond)
166+
- **[Migration Guide](docs/MIGRATION.md)** - Upgrade paths between pre-1.0 releases
155167
- **[Architecture Guide](docs/ARCHITECTURE.md)** - Deep dive into core design and internals
156168
- **[Examples Directory](examples/)** - Runnable patterns: graph execution, scheduling, streaming, persistence, and more
157169

docs/MIGRATION.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,116 @@ migration guidance for upgrading your code.
55

66
---
77

8+
## v0.5.0
9+
10+
### Overview
11+
12+
v0.5.0 is the recommended target for the WeaveQuant production feedback work. The changes add new public runtime APIs and a public `RunnerError` variant, so they should not ship as a `0.4.1` patch.
13+
14+
### New Runtime APIs
15+
16+
Use `AppRunner::create_iterative_session(...)` and `AppRunner::invoke_next(...)` when one durable session should process many logical inputs:
17+
18+
```rust
19+
runner
20+
.create_iterative_session(run_id.clone(), initial_state, NodeKind::Start)
21+
.await?;
22+
23+
runner
24+
.invoke_next(&run_id, input_patch, NodeKind::Start)
25+
.await?;
26+
```
27+
28+
`NodeKind::Start` resolves to the graph's normal Start outgoing frontier. A registered custom node can be supplied for narrower re-entry. `NodeKind::End` now returns `RunnerError::InvalidIterativeEntry` when used as an iterative entry.
29+
30+
When an `AppRunner` event stream is subscribed before iterative execution, each `invoke_next(...)` emits `INVOCATION_END_SCOPE` and keeps the stream open for the next logical input. Call `finish_iterative_session(...)` after the final input to emit the normal `STREAM_END_SCOPE` sentinel and close the stream.
31+
32+
### Typed State Slots
33+
34+
Typed state slots are a thin, JSON-compatible layer over `VersionedState.extra`. Define a reusable key in the domain crate, then read and write typed payloads without hand-rolled `serde_json` calls at every node boundary:
35+
36+
```rust
37+
use serde::{Deserialize, Serialize};
38+
use weavegraph::node::NodePartial;
39+
use weavegraph::state::{StateKey, StateSnapshot};
40+
41+
#[derive(Serialize, Deserialize)]
42+
struct PortfolioState {
43+
cash_cents: i64,
44+
}
45+
46+
const PORTFOLIO: StateKey<PortfolioState> = StateKey::new("wq", "portfolio", 1);
47+
48+
fn read(snapshot: &StateSnapshot) -> Result<Option<PortfolioState>, weavegraph::state::StateSlotError> {
49+
snapshot.get_typed(PORTFOLIO)
50+
}
51+
52+
fn write(value: PortfolioState) -> Result<NodePartial, weavegraph::state::StateSlotError> {
53+
NodePartial::new().with_typed_extra(PORTFOLIO, value)
54+
}
55+
```
56+
57+
The storage key is namespaced and versioned as `namespace:name:v{schema_version}`. Untyped `extra` remains available.
58+
59+
### Deterministic Runtime Clock
60+
61+
Use the existing `Clock` abstraction to inject deterministic time into nodes and emitted node-event metadata:
62+
63+
```rust
64+
use std::sync::Arc;
65+
use weavegraph::runtimes::{AppRunner, CheckpointerType};
66+
use weavegraph::utils::clock::MockClock;
67+
68+
let runner = AppRunner::builder()
69+
.app(app)
70+
.checkpointer(CheckpointerType::InMemory)
71+
.clock(Arc::new(MockClock::new(1_700_000_000)))
72+
.build()
73+
.await;
74+
```
75+
76+
Inside a node, call `ctx.now_unix_ms()` and `ctx.invocation_id()`. `NodeContext::new(...)` is now the easiest way to construct contexts in tests.
77+
78+
### Metadata Helpers
79+
80+
Compiled graphs and runners expose deterministic metadata helpers for audit labels and replay manifests:
81+
82+
```rust
83+
let graph = app.graph_metadata();
84+
let graph_hash = app.graph_definition_hash();
85+
let run = runner.run_metadata();
86+
```
87+
88+
The graph hash includes node kinds, edges, conditional edge registrations, and reducer definition labels. It does not inspect closure bodies for conditional predicates. Custom reducers can override `Reducer::definition_label(...)` when a durable audit label is preferable to the default Rust type path.
89+
90+
### Replay Conformance Helpers
91+
92+
Replay helpers live under `weavegraph::runtimes::replay` and are re-exported from `weavegraph::runtimes`:
93+
94+
```rust
95+
use weavegraph::runtimes::{ReplayRun, compare_replay_runs};
96+
97+
let expected = ReplayRun::new(expected_state, expected_events);
98+
let actual = ReplayRun::new(actual_state, actual_events);
99+
100+
compare_replay_runs(&expected, &actual).assert_matches()?;
101+
```
102+
103+
`normalize_event(...)` strips runtime timestamps. Use `compare_event_sequences_with(...)` or `compare_replay_runs_with(...)` when domain events need semantic normalization.
104+
105+
### Compatibility Notes
106+
107+
- `App::invoke(...)`, `AppRunner::create_session(...)`, and `AppRunner::run_until_complete(...)` keep their existing behavior.
108+
- `RunnerError` is an exhaustive public enum. Code that matches every variant must handle `InvalidIterativeEntry` after upgrading.
109+
- `GraphMetadata`, `RunMetadata`, `ReplayRun`, `NodeContext`, and `SchedulerRunContext` are `#[non_exhaustive]`; use provided constructors/builders instead of external struct literals.
110+
- `Reducer` gains a default `definition_label(...)` method for graph metadata. Existing reducer implementations do not need to change unless they want a custom stable label.
111+
- `RuntimeConfig` gains a public `clock` field. Code using struct literals should add `clock: None` or switch to `RuntimeConfig::default()` / builder-style methods.
112+
- `NodeContext` gains `clock` and `invocation_id` fields. Tests should prefer `NodeContext::new(...)` over struct literals.
113+
- Direct calls to `Scheduler::superstep(...)` must pass the optional clock and invocation ID arguments.
114+
- Iterative sessions keep step numbers monotonic across invocations and reload checkpoints through the existing checkpointer path.
115+
116+
---
117+
8118
## v0.4.0
9119

10120
### Overview

0 commit comments

Comments
 (0)