feat(sdk/engine): public subject contract for cross-engine event routing [skip-tag:sdk]#46
Merged
Merged
Conversation
Add subjects.go to sdk/engine, defining the event schema every concrete
engine implementation MUST publish: engine.run.<runID>.start, .end,
.step.<actorID>.{start,complete,error}, and .stream.<actorID>.delta.
The package now also owns the StreamDeltaPayload struct + DecodeStreamDelta
helper and SanitiseID, so consumers (voice, SSE bridges, dashboards,
kanban) can route + decode without importing any concrete engine.
Why: voice's planned migration off sdk/workflow needs a stable contract
to identify "this envelope is an LLM/tool delta" without hard-coding
graph runner's private subject layout. Lifting the convention into
sdk/engine turns it from "graph runner's internal habit" into
"engine implementations' obligation".
Refactor graph runner to use the new builders:
- engine.SubjectRunStart/End replace subjGraphStart/End
- engine.SubjectStepStart/Complete/Error replace subjNodeStart/.../Error
(note rename: "node" -> "step" at the engine layer)
- engine.SubjectStreamDelta replaces subjNodeStreamDelta; "stream" is
now a sibling of "step" rather than nested, so consumers can
subscribe with engine.PatternRunStream(runID) without also matching
every step lifecycle event.
- graph-private extensions (parallel.fork/join, step.skipped) keep
living in the executor but anchor on engine.SubjectPrefix so a single
engine.PatternRun(runID) subscription still captures them.
Drop runner.PatternRun / PatternAllRuns / PatternRunNodes re-exports;
subscribers should import sdk/engine for the canonical patterns.
BREAKING CHANGE: graph runner subject prefix changes from "graph.run.*"
to "engine.run.*"; node-level events change from ".node.<id>." to
".step.<id>." and stream deltas move from ".node.<id>.stream.delta" to
".stream.<id>.delta". External subscribers using runner.PatternRun /
PatternAllRuns / PatternRunNodes must switch to engine.PatternRun /
PatternAllRuns / PatternRunSteps. Released sdk versions are unaffected;
this lands before sdk/v0.2.3 is re-cut.
[skip-tag:sdk]
Made-with: Cursor
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.
Summary
Lifts the event-subject convention from graph runner's
internal/executorpackage up intosdk/engine, making it the cross-engine contract every implementation must follow when publishing run / step / stream envelopes.sdk/engine/subjects.godefinesSubjectPrefix(engine.run.), allSubject*/Pattern*builders,IsStreamDelta,StreamDeltaPayload+DecodeStreamDelta, andSanitiseID.parallel.fork/join,step.<id>.skipped) remain ininternal/executor/subjects.gobut anchor onengine.SubjectPrefixso a singleengine.PatternRun(runID)subscription still captures them.runner.PatternRun/PatternAllRuns/PatternRunNodesre-exports — subscribers should importsdk/enginefor the canonical patterns.Why
The voice package wants to be driven by
sdk/engine+sdk/agent(offsdk/workflow) so it can plug into any engine implementation. To do that, voice needs a stable subject contract to recognise "this envelope is an LLM token / tool delta" without hard-coding the graph runner's private layout. Owning the convention insdk/engineturns it from "graph runner's internal habit" into "engine implementations' obligation".Subject changes
graph.run.<r>.startengine.run.<r>.startgraph.run.<r>.endengine.run.<r>.endgraph.run.<r>.node.<n>.startengine.run.<r>.step.<n>.startgraph.run.<r>.node.<n>.completeengine.run.<r>.step.<n>.completegraph.run.<r>.node.<n>.errorengine.run.<r>.step.<n>.errorgraph.run.<r>.node.<n>.stream.deltaengine.run.<r>.stream.<n>.deltagraph.run.<r>.parallel.fork/join(graph-private)engine.run.<r>.parallel.fork/joingraph.run.<r>.node.<n>.skipped(graph-private)engine.run.<r>.step.<n>.skippedTwo intentional design tweaks vs. the old layout:
node→step: at the engine layer, the unit of work is generic; "node" is graph-specific.streamis now a sibling ofsteprather than nested under it. Consumers that only want LLM token / tool deltas can subscribe withengine.PatternRunStream(runID)without also matching every step lifecycle event — voice's TTS feeder is the headline use case.BREAKING CHANGE
Subject prefix and node→step rename are wire-incompatible with the previous shape. Released sdk versions are unaffected because
sdk/v0.2.3was deleted before this PR landed. External subscribers usingrunner.PatternRun/PatternAllRuns/PatternRunNodesmust switch to the equivalents insdk/engine.Auto-tag
Commit message carries
[skip-tag:sdk]so the auto-tag workflow skips this commit's sdk module bump. The next sdk release should be cut by hand at the appropriate semver level (likelyv0.3.0, since this is breaking).Test plan
go test ./...insdk/(all green, includingengine's 14 new test cases)go test ./...invoice/(still green; voice unchanged in this PR but verified the rename did not break its build path)runner_test.go/engine_test.go/integration_test.goupdated to assert againstengine.SubjectPrefixsdk/workflowlands in a follow-up PR usingengine.PatternRunStream+DecodeStreamDeltaMade with Cursor