Skip to content

feat(runtime): execute a plain flow as a streaming transfer - #465

Open
devin-ai-integration[bot] wants to merge 16 commits into
developfrom
feature/streaming-flows
Open

devin-ai-integration[bot] wants to merge 16 commits into
developfrom
feature/streaming-flows

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

What and why

A flow between two action parameters ran as a succession flow: applyDataFlows moved the source pin's value to the target pin when the source node completed, for both spellings. The parser kept the distinction (ast.Usage.IsSuccessionFlow) but lower.ObjectFlow carried no kind, so it was lost at lowering. A consumer that reads its pin before its producer completes got the wrong result.

  • lower.ObjectFlow.Kind (FlowStreaming, the zero value, or FlowSuccession) is set by lowerFlow from the declaration; succeedFlow reads the kind rather than the declaration to add the succession edge a succession flow also states. ast.ObjectFlowEdge (never a succession) lowers as streaming. The runtime never looks at Decl.
  • Streaming (runtime/action_frame.go): setFrameFeaturestreamFromstreamFlow carries each write to a source pin along the node's streaming flows, to the pin of every ongoing performance of the target (ongoing, the performances of a node under way). With no performance under way the value goes through deliverFlow and waits at the pin as before, one per performance, oldest first — which is what makes a target that has not begun, a stream inside a loop body and fork/join around the two nodes work. actionFrame.streamed records the pins streamed so applyDataFlows does not carry the last value again at completion.
  • Succession: unchanged — applyDataFlowsdeliverFlow at completion, and the target begins after the source.
  • Typed errors: a source that completes with the pin never written is ErrFlowSource (wrapping the message the flow-source case already reported); a write after the target's last performance ended, which no later performance of the target takes, is ErrStreamUnreceived when the enclosing performance completes (actionFrame.unreceived, cleared by takeDeliveries, checked in endPerformance and at root completion); a stream to a pin the ongoing target does not declare is ErrNodePin at the write. streamed/unreceived are carried through snapshots, held images and check state spelling.
  • Shared executor plumbing: only applyDataFlows gains a streamed parameter (three call sites: leaveExecutionNode, completeNode, the body-node path in action_statements.go). Nothing in state_executor.go or the inline do bodies changes.

Known limitation (recorded in the roadmap): an atomic body is performed whole in one step, so a source that writes its pin several times before any performance of the target is under way queues every value and each target performance takes one; a truly concurrent target would see the last.

Specification basis

SysML v2 (formal/2026-03-02) §7.16: "the input and output parameters are streaming unless designated as succession flows"; a streaming flow "can be ongoing while both the source and target action are being performed", a succession flow "cannot begin until the source completes". Library: Flows::Flow :> Message, FlowTransfer vs Flows::SuccessionFlow :> Flow, FlowTransferBefore. The Actions map's object-flow row in docs/project/spec-compliance.md is split into a streaming row and a succession row (both ✅), "Streaming pins" leaves "Major Features Not Implemented", and the roadmap's streaming-flows item is recorded as landed.

How it was verified

  • lower/action_flow_kind_test.go: the kind follows the spelling; only the succession kind adds an edge.
  • Conformance: action_flow_streaming_producer_consumer (producer loop writing 1, 2, 3 beside a consumer loop: total 6) + trace golden showing each write landing before the next read; action_flow_succession_producer_consumer (same nodes in sequence: consumer reads 3 three times, total 9) + trace golden; action_flow_streaming_before_target_begins; action_flow_streaming_in_loop_body; action_flow_streaming_declared_value (a declared out value = 5 streams as the producer begins).
  • robustness_streaming_flow_test.go TestRuntimeRobustnessStreamingFlow: source_never_writes (ErrFlowSource), target_completed_before_source_writes (ErrStreamUnreceived), target_pin_not_declared (ErrNodePin), later_performance_takes_one_of_two_late_values (the second late value is still reported after one performance takes the first).
  • modelform.TestGraphsActionCarriesTheFlowKind: the graphs:1 form's ObjectFlowForm.kind spells both kinds (added field, version unchanged).
  • go build ./..., go vet ./..., gofmt -l . empty, go test -count=1 ./... green with OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 and the corpora downloaded (no ratchet movement; training_examples_expected.txt untouched), TestPilotLibraryXMI with its XMI downloaded, python3 scripts/changelog.py check, scripts/check-doc-ids.py, doc-counts -check.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review September 20, 2026 17:48
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration
devin-ai-integration Bot changed the base branch from develop to feature/positional-invoke-arguments September 20, 2026 20:03
@devin-ai-integration
devin-ai-integration Bot added this pull request to stack #476 September 20, 2026 20:04
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@HuiJun
HuiJun force-pushed the feature/streaming-flows branch from dbaecc0 to 91c471b Compare September 21, 2026 01:30
Base automatically changed from feature/positional-invoke-arguments to develop September 21, 2026 02:13
devin-ai-integration Bot and others added 10 commits September 20, 2026 19:13
A flow between action parameters is streaming unless designated a
succession flow (SysML v2 7.16). The lowered ObjectFlow carries the kind
from the declaration; the runtime carries each write to the source pin to
the target's ongoing performances at once, queues it ahead of the next
when none is under way, and keeps completion-time transfer for the
succession flow spelling. A source that never writes, a write no later
performance of the target takes, and a stream to an undeclared pin are
typed errors.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
A declared out value streams as the performance begins, each value streamed
after a target's performances ended stays reported until a performance takes
it, and the graphs:1 form carries a flow's kind.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…cles

A node performing a typed action now forwards each output write of the
callee to its own pin as it is made, so streaming flows from the node
carry every value rather than only the last adopted at completion.
Streaming flows that lead a value back to the pin it was written to are
refused with ErrStreamCycle instead of recursing without end. The
canonical checker state spells every late streamed value with its pin,
queue position and source, so distinct states are no longer merged.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
… streaming flows

The checker's footprint of a node counts every target pin its writes stream to,
through nested bodies and transitively, so a streaming write and a read of the
target pin never commute under partial-order reduction. A performed action's
output callback is installed before its declared values are seeded, so a declared
output streams as the performance begins. The SMT encoding reads the flow kind:
a plain flow streams each write at the write, a queued value the same performance
replaces, and a value queued after its target's last performance fails the action
as it completes; a succession flow moves its value at completion as before.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…oined performances

Streaming writes queued ahead of a target's performance are staged per source
performance, so two sources writing in turn each replace only their own value;
held images carry that staging. A node joining the performance an object
already runs of its action listens to its outputs alongside any other node
performing it. The SMT encoding streams a write under the path it is written on
and carries staging through branches and loops.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
The node's listener is installed before the joined run and removed however the
run returns — completion, failure or pause — and re-installed when the paused
continuation resumes, so no listener outlives the step that uses it and a
snapshot restored between steps has none to lose.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…s pauses

The node's listener stays on a joined performance while the node waits on it,
so writes the clock drives in the meantime reach the node's pins; it is removed
when the join completes, fails or is abandoned. Snapshots capture and restore an
executor's listeners with the rest of its state.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…rformance

Two pins of one performance streaming into the same target pin replaced each
other's staged value, so a target begun afterwards took only the later pin's
write. Staged slots (and their held-image form, and the SMT queue slots) are
now keyed by source performance and source pin.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Flows naming one source pin by its inherited and its redefining name are the
same pin, so their writes share one staged slot; the SMT encoder keys its
queue slots by the resolved source variable the same way.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun force-pushed the feature/streaming-flows branch from 91c471b to 55f5aba Compare September 21, 2026 02:13
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 21, 2026 02:48
…puts by any name

A value a node takes from its pending queue as its performance begins now streams
on along the flows out of that pin. A performed action's declared out and inout
defaults reach the performing node as the performance begins, the root frame
recording each declared feature's direction; the node takes the action's writes by
whichever name it spells them, the name a pin redefines included.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ootprints

A flow naming a source pin by the name it redefines is followed when the write
names the redefining pin, so the reduction sees every downstream streaming write.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 21, 2026 03:05
A value taken as a performance begins now streams while the performance is
already among its node's ongoing ones, so a stream carried back to another
pin of the node reaches the performance that just began rather than waiting
for a later one, and a direct cycle is the typed ErrStreamCycle. Seeding
that fails unregisters the performance again.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
The encoding streams whatever value a pin starts with, a queued delivery as
well as a declared one, as the interpreter does; every node this stage
encodes begins and completes in one step, so the schedules it admits are
unchanged.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

A plain flow from one pin of a node to another reaches the performance that
is beginning or running, as the interpreter's does, rather than the pin's
queue for a later performance: a node's starting values stream after every
pin holds its own, and a stream whose target is the node itself settles on
the pin in the current environment.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

A stream that reaches a pin of the performance under way carries on over the
streaming flows sourced there, as the interpreter's write does, and the pins a
value is being carried from are tracked so a flow leading it back to one of
them is encoded as the interpreter's cycle error instead of recursing.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
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