Skip to content

feat(sdk/engine): public subject contract for cross-engine event routing [skip-tag:sdk]#46

Merged
lIang70 merged 1 commit into
mainfrom
feat/engine-subjects
Apr 29, 2026
Merged

feat(sdk/engine): public subject contract for cross-engine event routing [skip-tag:sdk]#46
lIang70 merged 1 commit into
mainfrom
feat/engine-subjects

Conversation

@lIang70

@lIang70 lIang70 commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lifts the event-subject convention from graph runner's internal/executor package up into sdk/engine, making it the cross-engine contract every implementation must follow when publishing run / step / stream envelopes.

  • New: sdk/engine/subjects.go defines SubjectPrefix (engine.run.), all Subject* / Pattern* builders, IsStreamDelta, StreamDeltaPayload + DecodeStreamDelta, and SanitiseID.
  • Refactor: graph runner now constructs subjects through the engine builders. The graph-private extensions it owns (parallel.fork/join, step.<id>.skipped) remain in internal/executor/subjects.go 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.

Why

The voice package wants to be driven by sdk/engine + sdk/agent (off sdk/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 in sdk/engine turns it from "graph runner's internal habit" into "engine implementations' obligation".

Subject changes

Before After
graph.run.<r>.start engine.run.<r>.start
graph.run.<r>.end engine.run.<r>.end
graph.run.<r>.node.<n>.start engine.run.<r>.step.<n>.start
graph.run.<r>.node.<n>.complete engine.run.<r>.step.<n>.complete
graph.run.<r>.node.<n>.error engine.run.<r>.step.<n>.error
graph.run.<r>.node.<n>.stream.delta engine.run.<r>.stream.<n>.delta
graph.run.<r>.parallel.fork/join (graph-private) engine.run.<r>.parallel.fork/join
graph.run.<r>.node.<n>.skipped (graph-private) engine.run.<r>.step.<n>.skipped

Two intentional design tweaks vs. the old layout:

  1. nodestep: at the engine layer, the unit of work is generic; "node" is graph-specific.
  2. stream is now a sibling of step rather than nested under it. Consumers that only want LLM token / tool deltas can subscribe with engine.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.3 was deleted before this PR landed. External subscribers using runner.PatternRun / PatternAllRuns / PatternRunNodes must switch to the equivalents in sdk/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 (likely v0.3.0, since this is breaking).

Test plan

  • go test ./... in sdk/ (all green, including engine's 14 new test cases)
  • go test ./... in voice/ (still green; voice unchanged in this PR but verified the rename did not break its build path)
  • graph runner runner_test.go / engine_test.go / integration_test.go updated to assert against engine.SubjectPrefix
  • After merge: voice migration off sdk/workflow lands in a follow-up PR using engine.PatternRunStream + DecodeStreamDelta

Made with Cursor

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
@lIang70 lIang70 changed the title feat(sdk/engine): public subject contract for cross-engine event routing feat(sdk/engine): public subject contract for cross-engine event routing [skip-tag:sdk] Apr 29, 2026
@lIang70
lIang70 merged commit b193df7 into main Apr 29, 2026
9 checks passed
@lIang70
lIang70 deleted the feat/engine-subjects branch April 29, 2026 02:49
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