Skip to content

feat(examples): add 05-08 (fan-out, parallel-branches, multimodal-prompt, checkpointing)#51

Merged
chris-colinsky merged 6 commits into
mainfrom
feature/examples-new-demos
May 18, 2026
Merged

feat(examples): add 05-08 (fan-out, parallel-branches, multimodal-prompt, checkpointing)#51
chris-colinsky merged 6 commits into
mainfrom
feature/examples-new-demos

Conversation

@chris-colinsky
Copy link
Copy Markdown
Member

Summary

Second wave of the Examples Review phase. After #50 scrubbed and re-themed the existing examples, this PR fills in the remaining gaps so every major v0.6.0 surface has a home in examples/. Four new demos plus a moon-themed sweep across the existing ones.

New examples

# Demo Major concepts
05 fan-out-with-retry Fan-out (items_field mode), extra_outputs collecting a parallel per-instance list, instance_middleware=(RetryMiddleware, TimingMiddleware), concurrency cap
06 parallel-branches M-way heterogeneous dispatch, BranchSpec per branch with input/output projection, per-branch middleware (sentiment branch has retry, others run bare)
07 multimodal-prompt PromptManager + FilesystemPromptBackend loading a Jinja2 template from disk; with_active_prompt context-var propagation; multimodal UserMessage(content=[TextBlock, ImageBlock])
08 checkpointing-and-migration SQLiteCheckpointer(serialization="json"), save-on-completed-event, State.schema_version, with_state_migration, invoke(resume_invocation=...)

All four are LLM-using against the same OpenAIProvider pattern the other demos use. Each carries a build_graph() factory; smoke test list grows to nine demos.

Moon-themed subject matter (across 00-06)

After the scrub PR, Chris flagged that all subject matter should align with the company name (Lunar Command). One commit in this PR sweeps 00-05 to moon topics and 06's article was authored as a moon topic from the start. Specifically:

  • 00 hello-world: query → "why did Apollo 13 abort its lunar landing?"
  • 01 routing-and-subgraphs: default question → "why is the lunar south pole strategically important?"
  • 02 explicit-subgraph-mapping: default topic pair → Apollo 11 vs Apollo 17
  • 03 observer-hooks: second default question → "explain why NASA is returning to the moon with Artemis"
  • 04 nested-subgraphs: corpus → Apollo 11, Apollo 13, Artemis II (the third doc replaces the pre-cancellation Lunar Gateway entry)
  • 05 fan-out-with-retry: five lunar-mission headlines (Artemis II splashdown, Lunar Gateway pause, IM-3 prep, LRO crater find, south-pole water-ice confirmation); classify tag set tightened to lunar categories (crew / lander / orbiter / science / hardware / policy / other)
  • 06 parallel-branches: sample article → Artemis II splashdown narrative

Factual accuracy as of 2026-05-17: Artemis II splashed down 2026-04-10; the Lunar Gateway program was paused 2026-03-24 in favor of a lunar surface base; Intuitive Machines IM-2 ended on its side in March 2025 and IM-3 is scheduled for second half of 2026. Web-searched and double-checked before commit so the moon-themed content doesn't ship stale.

Commits

  1. feat(examples): add 05-fan-out-with-retry
  2. feat(examples): add 06-parallel-branches
  3. chore(examples): moon-themed subject matter (sweep across 00-05)
  4. feat(examples): add 07-multimodal-prompt
  5. feat(examples): add 08-checkpointing-and-migration

Concept coverage after this PR

Concept Example
State + reducers, edges, END 00, 01, 02, 04, 05, 06, 08
Conditional routing 00, 01
Subgraphs + projection 01, 02, 04
ExplicitMapping 02, 04
Custom ProjectionStrategy 01
Observer hooks (plain + OTel) 03
Depth invariants + namespace chain 04
LLM provider + structured output 00
Fan-out 05
Middleware (retry, timing) 05, 06
Parallel branches 06
Branch middleware 06
Prompt management 07
Multimodal user messages (image blocks) 07
Checkpointing (save / resume) 08
State migration (schema_version, with_state_migration) 08

Every major v0.6.0 surface has a home.

Test plan

  • uv run pytest tests/ -q — 9 example smoke tests included; full suite green.
  • uv run pyright examples/ — clean.
  • uv run ruff check examples/ tests/ — clean.
  • Manual: 05, 06, 07, 08 each invoked end-to-end against a real LLM endpoint on the branch and produced the expected output shape.

Next phase

After this merges, the Examples Review is wrapped. Next phase per the standing plan is the docs-site review (mkdocs.yml nav, concepts pages, reference pages re-read against the v0.6.0 surface). Sessions-capability scoping is captured in the local coord directory for a future spec proposal.

Realistic fan-out shape: summarize and classify a batch of news
headlines in parallel.

- ``GraphBuilder.add_fan_out_node`` with ``items_field`` mode and
  ``extra_outputs`` collecting two parallel per-instance lists
  (summary + topic).
- ``instance_middleware=(RetryMiddleware(3 attempts, deterministic
  backoff), TimingMiddleware(on_complete=...))`` wraps each instance's
  whole subgraph invocation — retries are per-instance, timings are
  captured per-instance.
- ``concurrency=3`` caps how many instances run in flight at once.
- Final printout shows per-instance durations in completion order
  alongside a wall-clock total vs sum-of-durations comparison, so
  the speedup from concurrency is visible.

Per-instance subgraph is ``summarize → classify``; both nodes hit the
LLM via the shared ``OpenAIProvider`` pattern the rest of the demos
use. Smoke test list grows to six demos.
Enrich a lunar-mission news article with three independent analyses
(summary, sentiment, topic tags) running concurrently.

- ``GraphBuilder.add_parallel_branches_node`` registers M
  ``BranchSpec``s under named keys (``summary`` / ``sentiment`` /
  ``topics``). Each spec carries its own compiled subgraph, its own
  input/output projection, and optionally its own middleware.
- The three branches have DIFFERENT state schemas — each is scoped
  to its analysis's inputs and outputs. The projection mapping
  translates the parent's ``article`` field into each branch's input
  field name.
- The sentiment branch wraps its subgraph in ``RetryMiddleware`` to
  show per-branch middleware composition. The other two run bare.
- Wall-clock total prints alongside the results so the parallelism
  benefit is visible.

Sample article is a narrative of Artemis II's splashdown on April 10,
2026 — the first crewed flight beyond low Earth orbit since Apollo
17.

Also tightens the 05 entry in examples/README.md to drop a stale
mention of fan-out-index correlation (the demo doesn't claim that
anymore; the timing record carries no index field). Smoke test list
grows to seven demos.
Sweep across 00-05 so every example's queries, articles, baked-in
corpora, and headlines are moon-related. The company is Lunar
Command; consistent lunar framing makes the demo set feel like a
coherent surface rather than a grab bag.

- 00 hello-world: query → "why did Apollo 13 abort its lunar
  landing?"
- 01 routing-and-subgraphs: default question → "why is the lunar
  south pole strategically important?" (the moon-landing-year
  question stays)
- 02 explicit-subgraph-mapping: default topic pair → Apollo 11 vs
  Apollo 17; docstring and Run-with section updated
- 03 observer-hooks: second default question → "explain why NASA is
  returning to the moon with Artemis" (the moon-landing-year
  question stays)
- 04 nested-subgraphs: corpus → Apollo 11 (kept), Apollo 13 (new),
  Artemis II (new — narrative of the April 2026 splashdown);
  default questions updated to match
- 05 fan-out-with-retry: five headlines all swapped for accurate
  lunar-mission news (Artemis II splashdown, Lunar Gateway pause,
  IM-3 prep, LRO crater find, south-pole water-ice confirmation);
  classify tag set tightened to lunar-relevant categories
  (crew / lander / orbiter / science / hardware / policy / other)

Factual accuracy checked as of 2026-05-17: Artemis II splashed
down 2026-04-10; the Lunar Gateway program was paused 2026-03-24
in favor of a lunar surface base; Intuitive Machines IM-2 ended on
its side in March 2025 and IM-3 is scheduled for second half of
2026.
Caption a historical lunar photograph using a versioned prompt
template plus a multimodal user message.

- ``FilesystemPromptBackend`` loads
  ``prompts/production/caption-lunar-image.j2`` from disk. The
  layout is ``<root>/<label>/<name>.j2`` so prompts can live next to
  the code, be diffed in PRs, and version off their template hash.
- ``PromptManager(backend).get(name, variables={...})`` fetches and
  renders in one call. The returned ``PromptResult`` carries the
  rendered text in ``messages[0]`` plus identifiers
  (``template_hash``, ``rendered_hash``, ``version``) for downstream
  attribution.
- The node pulls the rendered text out of the ``PromptResult``,
  composes a multimodal ``UserMessage(content=[TextBlock(text=...),
  ImageBlock(source=ImageSourceURL(url=...))])``, and passes it to
  ``OpenAIProvider.complete`` — one call carries both the instructions
  and the image.
- ``with_active_prompt(rendered)`` wraps the LLM call so OTel
  observers (none attached in the demo, but the pattern is the same
  in production) stamp ``openarmature.prompt.*`` attributes onto the
  LLM-call span.

Sample: the iconic Apollo 11 photograph of Buzz Aldrin on the lunar
surface, hosted on Wikimedia Commons. ``IMAGE_URL`` env var overrides
the default for users who want to point at their own image.

Smoke test list grows to eight demos.
A lunar-mission planning pipeline that checkpoints after every step,
then resumes the saved record under an upgraded state schema with a
v1→v2 migration.

Phase 1 — v1 invoke:

- ``MissionPlanStateV1`` declares ``schema_version: ClassVar[str] =
  "v1"`` and has four user-facing fields (destination, objective,
  crew_size, timeline) plus trace.
- ``SQLiteCheckpointer(path, serialization="json")`` writes records
  to a temp DB. JSON mode is the migration-eligible serialization —
  pickle mode can't bridge schemas.
- v1 graph: define_objective → size_crew → draft_timeline → END.
  Each completed node fires a save stamped with schema_version="v1".
- Invoked with a deterministic correlation_id so phase 2 can look
  up the invocation by ``CheckpointFilter(correlation_id=...)``.

Phase 2 — v2 resume:

- ``MissionPlanStateV2`` adds a ``risk_assessment`` field and
  ``schema_version = "v2"``.
- ``GraphBuilder.with_state_migration("v1", "v2",
  migrate_v1_to_v2)`` registers the migration. ``migrate_v1_to_v2``
  is a pure function that backfills ``risk_assessment=""`` for v1
  records.
- v2 graph adds an ``assess_risks`` node at the end of the v1
  topology.
- ``invoke(state, resume_invocation=<v1 id>)`` loads the v1 record,
  applies the migration, re-deserializes as ``MissionPlanStateV2``,
  and continues at ``assess_risks`` (the first node not in
  completed_positions).

Final state has the v1 work (objective, crew_size, timeline) AND
the v2 risk_assessment, all under the upgraded schema. Smoke test
list grows to nine demos.
Copilot AI review requested due to automatic review settings May 18, 2026 18:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds four new runnable demos under examples/ to cover additional v0.6.0 surfaces (fan-out, parallel branches, prompt management + multimodal messages, and checkpointing + state migration), and updates existing demos’ sample subject matter to lunar themes.

Changes:

  • Add new examples 0508 and document them in examples/README.md.
  • Extend tests/test_examples_smoke.py to compile-load the new demos.
  • Update existing examples (00–04) with moon-themed default prompts/content.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_examples_smoke.py Adds new demo folders to the compile-load smoke test list.
examples/README.md Documents demos 05–08 and their key concepts.
examples/00-hello-world/main.py Updates default query to lunar-themed prompt.
examples/01-routing-and-subgraphs/main.py Updates default question + usage examples to lunar-themed prompts.
examples/02-explicit-subgraph-mapping/main.py Updates use case text + default topics + CLI examples to lunar-themed prompts.
examples/03-observer-hooks/main.py Updates CLI example prompt to lunar-themed content.
examples/04-nested-subgraphs/main.py Updates CLI examples + embedded corpus docs to lunar-themed content.
examples/05-fan-out-with-retry/main.py New fan-out demo showing per-instance retry + timing middleware and concurrency cap.
examples/06-parallel-branches/main.py New parallel-branches demo showing heterogeneous branch schemas + per-branch middleware.
examples/07-multimodal-prompt/main.py New prompt-management + multimodal message demo using filesystem prompts and with_active_prompt.
examples/07-multimodal-prompt/prompts/production/caption-lunar-image.j2 New Jinja2 prompt template used by the multimodal prompt demo.
examples/08-checkpointing-and-migration/main.py New checkpoint + resume + schema migration demo using SQLiteCheckpointer(serialization="json").
Comments suppressed due to low confidence (2)

examples/08-checkpointing-and-migration/main.py:290

  • Using assert summaries for required runtime control flow is unsafe because asserts are stripped under python -O, and it turns a user-facing error into a later IndexError. Raise a clear exception (e.g., RuntimeError) when no checkpoints are found, and include the correlation_id/run_id in the message.
    summaries = list(await checkpointer.list(CheckpointFilter(correlation_id=run_id)))
    assert summaries, "expected at least one saved checkpoint"
    invocation_id = summaries[-1].invocation_id

examples/08-checkpointing-and-migration/main.py:283

  • graph_v1.invoke() / graph_v1.drain() (and the v2 equivalents) aren’t protected by try/finally. If an exception occurs during invoke/printing, the graph may not be drained and the provider may not be closed, which can leave background tasks or sockets open. Wrap each phase (or the whole main) in try/finally to guarantee drain() and aclose() run.
    graph_v1 = build_graph_v1(checkpointer)
    initial_v1 = MissionPlanStateV1(destination=destination)
    final_v1 = await graph_v1.invoke(initial_v1, correlation_id=run_id)
    await graph_v1.drain()


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread examples/08-checkpointing-and-migration/main.py Outdated
Comment thread examples/07-multimodal-prompt/main.py Outdated
Comment thread examples/05-fan-out-with-retry/main.py
- 05 fan-out-with-retry: _timings.clear() at the top of main() so a
  REPL or repeated-main() driver doesn't accumulate timings across
  invocations. Module-level retention was an oversight.
- 07 multimodal-prompt: replace the two type/shape asserts in
  caption() with an explicit isinstance check that raises
  RuntimeError. Asserts strip under python -O; the new shape narrows
  for pyright AND fails loudly if PromptManager's return contract
  drifts.
- 08 checkpointing-and-migration: switch main() from tempfile.mkdtemp
  to tempfile.TemporaryDirectory wrapping the phase 1/2 logic. The
  SQLite DB + temp folder are now cleaned up on both the happy path
  and any raised exception, instead of leaving /tmp/oa-checkpoint-demo-*
  behind across runs.
@chris-colinsky chris-colinsky merged commit ba9c4cf into main May 18, 2026
5 checks passed
@chris-colinsky chris-colinsky deleted the feature/examples-new-demos branch May 18, 2026 19:18
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.

2 participants