feat(runtime): execute a plain flow as a streaming transfer - #465
Open
devin-ai-integration[bot] wants to merge 16 commits into
Open
devin-ai-integration[bot] wants to merge 16 commits into
devin-ai-integration[bot] wants to merge 16 commits into
Conversation
Contributor
Author
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
devin-ai-integration
Bot
force-pushed
the
feature/streaming-flows
branch
from
September 20, 2026 19:57
bfe6b6f to
8a0365f
Compare
devin-ai-integration
Bot
changed the base branch from
develop
to
feature/positional-invoke-arguments
September 20, 2026 20:03
devin-ai-integration
Bot
force-pushed
the
feature/streaming-flows
branch
from
September 20, 2026 20:07
90a6336 to
f647f53
Compare
HuiJun
force-pushed
the
feature/streaming-flows
branch
from
September 21, 2026 01:30
dbaecc0 to
91c471b
Compare
Base automatically changed from
feature/positional-invoke-arguments
to
develop
September 21, 2026 02: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
force-pushed
the
feature/streaming-flows
branch
from
September 21, 2026 02:13
91c471b to
55f5aba
Compare
…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>
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>
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A
flowbetween two action parameters ran as a succession flow:applyDataFlowsmoved 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) butlower.ObjectFlowcarried 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, orFlowSuccession) is set bylowerFlowfrom the declaration;succeedFlowreads the kind rather than the declaration to add the succession edge asuccession flowalso states.ast.ObjectFlowEdge(never a succession) lowers as streaming. The runtime never looks atDecl.runtime/action_frame.go):setFrameFeature→streamFrom→streamFlowcarries 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 throughdeliverFlowand 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.streamedrecords the pins streamed soapplyDataFlowsdoes not carry the last value again at completion.applyDataFlows→deliverFlowat completion, and the target begins after the source.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, isErrStreamUnreceivedwhen the enclosing performance completes (actionFrame.unreceived, cleared bytakeDeliveries, checked inendPerformanceand at root completion); a stream to a pin the ongoing target does not declare isErrNodePinat the write.streamed/unreceivedare carried through snapshots, held images andcheckstate spelling.applyDataFlowsgains astreamedparameter (three call sites:leaveExecutionNode,completeNode, the body-node path inaction_statements.go). Nothing instate_executor.goor 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", asuccession flow"cannot begin until the source completes". Library:Flows::Flow :> Message, FlowTransfervsFlows::SuccessionFlow :> Flow, FlowTransferBefore. The Actions map's object-flow row indocs/project/spec-compliance.mdis 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.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 declaredout value = 5streams as the producer begins).robustness_streaming_flow_test.goTestRuntimeRobustnessStreamingFlow: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: thegraphs:1form'sObjectFlowForm.kindspells both kinds (added field, version unchanged).go build ./...,go vet ./...,gofmt -l .empty,go test -count=1 ./...green withOPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1and the corpora downloaded (no ratchet movement;training_examples_expected.txtuntouched),TestPilotLibraryXMIwith its XMI downloaded,python3 scripts/changelog.py check,scripts/check-doc-ids.py,doc-counts -check.Checklist
make testandmake lintpass locallychanges/unreleased/<slug>.<section>.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (compliance rows need nothing: the census is counted at docs build)F4,K5) in the body, docs, or changelog