Skip to content

Run nested graphs on their own boards and publish child outcomes - #69

Merged
Enzx merged 2 commits into
mainfrom
hierarchy-visibility
Aug 15, 2026
Merged

Enzx merged 2 commits into
mainfrom
hierarchy-visibility

Conversation

@Enzx

@Enzx Enzx commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Two features for hierarchical graphs. Both keep sync/async parity and the 0 B hot path.

Publish a child's terminal outcome to declared keys

The single-child composites (nested machines, history states) accept optional outcome keys at construction. The DSL adds outcomeCode/outcomeName parameters to all four .SubGraph overloads. When the child run reaches a terminal, the composite writes the child machine's LastOutcome code and name through its stamped parent-level context. A parent can then branch on the child's outcome with the ordinary data-built switch.

  • A terminal with no declared outcome publishes 0 and the empty string.
  • Failure terminals also publish. A fault (escaped exception) publishes nothing.
  • A history lift-back re-entry publishes only when the resumed session ends.
  • The constructor rejects Node-scoped keys. The validator warns when the child graph declares no outcome codes.
  • Serialization payload version 11 carries the key names. Pre-v11 payloads read outcome-key-free.

Run a nested graph on its own board with declared ports

The machine-wrapping composites (nested machines, history states, parallel regions) accept an optional ports declaration: SubGraphPorts.OwnBoard().In(source, childKey).Out(childKey, parentKey). When present, the composite creates one blackboard from the child graph's own Graph schema and substitutes it when it stamps the child. Sibling sub-graphs stop colliding on same-named keys, and a child stops seeing the parent's whole board.

  • Inputs apply in declaration order at every fresh child start, after the owned board resets to its defaults. A history lift-back re-entry applies nothing.
  • Outputs copy in declaration order at the child's terminal, on success and on failure. Parallel composites re-apply outputs at the join, in region order.
  • Construction enforces per-pair type equality and rejects invalid keys by name.
  • Owned boards are durable: EnumerateOwnedBoards() walks them with node-index-path identity, so hosts persist and restore them around ResumeDeep.
  • Serialization payload version 12 carries the owns-board flag and the ports. Pre-v12 payloads read shared-board and port-free.

Verification

  • New parity scenarios pin both features across sync/async and full-run/stepped.
  • AllocationGate cases cover the new hot paths (0 B on both runtimes).
  • Deep suspend/resume and JSON/MessagePack round-trip tests cover the durable and wire boundaries.
  • Public API baselines record the new surface.

Enzx added 2 commits August 15, 2026 15:59
The single-child composites accept optional outcome keys at construction.
These composites are the nested machines (both runtimes) and the history
states. The DSL adds outcomeCode/outcomeName parameters to all four
.SubGraph overloads.

When the child run reaches a terminal, the composite writes the child
machine's LastOutcome code and name. It writes them through its stamped
parent-level context, before it returns its own Result. A parent can then
branch on the child's outcome with the ordinary data-built switch.

Rules:
- A terminal with no declared outcome publishes 0 and the empty string.
  Hosts route on non-zero codes.
- Failure terminals also publish.
- A history lift-back re-entry publishes only when the resumed session ends.
- An exception that escapes node logic is a fault, not a terminal.
  It publishes nothing.
- The constructor rejects Node-scoped keys and names the key.
- The validator warns when a key is declared but the child graph declares
  no outcome codes.

Serialization payload version 11: the key names ride the existing subgraph
entries and the history composite kinds. Only the names ride — the value
types are fixed (int/string), and the names are exclusive to the history
kinds. Deserialized composites rebuild name-bound through the new Unbound
factories. They resolve against the machine's bound boards at each publish,
with targeted miss and type-mismatch errors. Pre-v11 payloads read
outcome-key-free.

The publish is two typed Sets on the existing boards — 0 B on both
runtimes (AllocationGate cases). New parity scenarios pin the behavior
across sync/async and full-run/stepped; the parity harness gained
scenario-owned board binding. A deep suspend/resume case covers the
durable boundary. JSON and MessagePack round-trips cover the wire.
The public API baselines record the new surface.
The machine-wrapping composites accept an optional ports declaration:
SubGraphPorts.OwnBoard().In(source, childKey).Out(childKey, parentKey).
These composites are the nested machines (both runtimes), the history
states, and the parallel family per region. All four .SubGraph overloads
gain a ports argument; .Parallel gains ParallelRegion entries.

When a ports declaration is present, the composite creates one blackboard
from the child graph's own declared Graph schema at construction. It
substitutes that board for the Graph slot when it stamps the child, before
any schema validation runs. Global forwards unchanged; Node scratch was
already per-machine. Effects:
- Sibling sub-graphs no longer collide on same-named keys.
- A child no longer sees everything its parent holds.
- A graph authored standalone with its own schema nests under any parent.

Inputs are literal or parent-key sources. The composite resolves them in
the parent context. It applies them in declaration order at every fresh
child start, after the owned board resets to its registered defaults. A
history lift-back re-entry applies nothing — the board, like the position,
is the survived state. Outputs copy in declaration order at the child's
terminal, on success and on failure, before the composite returns. The
parallel composites re-apply outputs at the join, so several regions that
target one parent key resolve in region order.

Construction enforces per-pair type equality (generic pairs behind the
non-generic ISubGraphPort). Application is typed copies — 0 B on both
runtimes (AllocationGate cases). Construction rejections name the key:
ports over a child with no Graph schema, foreign target or source keys,
and Node-scoped parent-side keys.

Owned boards are durable artifacts. Owning composites implement
IOwnedBoardProvider. The machines expose a recursive EnumerateOwnedBoards()
walk with node-index-path identity. Hosts persist each board with
BlackboardSerializer and restore into a fresh machine's enumeration before
ResumeDeep. A full artifact loop proves this on both runtimes: suspend
mid-child, serialize the snapshot and the boards, restore, resume to
completion. New parity scenarios pin the behavior across sync/async and
full-run/stepped.

Serialization payload version 12: the subgraph entries gain the owns-board
flag and a ports list. Composites carry sparse per-region ports. Each port
rides as direction, source key name or field-model literal, target name,
and the runtime-stable value type name. The read side rebuilds everything
name-bound through new Unbound forms and resolves at each application with
targeted errors. A deserialized child graph carries no schema, so the host
binds a Graph-scoped board on the rebuilt child machine to supply the
board to own. Pre-v12 payloads read shared-board and port-free.

Validator:
- Info on owns-board presence.
- Warning when two output ports target the same parent key (regions of one
  parallel included).
- Best-effort Warning on an input target the child never reads statically.
- The conflicting-child-schema warning no longer fires for board-owning
  composites. Their child subtrees compare against the child's own schema.

The public API baselines record the new surface.
@Enzx
Enzx merged commit e113572 into main Aug 15, 2026
3 checks passed
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