Observe director targets by their built ids and forward machine wiring through the timeout wrappers - #65
Merged
Conversation
Directors (event dispatch, the plain-run Otherwise chain, choice/switch) hold target NodeIds captured at authoring time, before Build() applies display names. The run loops routed on those raw ids, so observers saw a nameless id for the node a dispatch or a plain start jumps to - the first node of every entry chain emitted no name-keyed step events, and a single-step chain emitted none - while ordinary edge destinations (renamed at Build()) observed correctly. The four run loops now canonicalize a director-selected id against the graph before routing, so OnTransition/OnStateEntered/OnStateExited carry the built id exactly as they do for edge hops. Identity is the index, so routing is unchanged; unknown indexes still fail with the existing node-not-found error.
AsyncTimeoutState and TimeoutState hid the state they wrap from all three machine wiring channels: the blackboard stamping walk, agent stamping, and the log-report tables the machines resolve at construction. A blackboard-using state inside a timeout silently lost its board context, its log reports went nowhere, and a graph whose only agent acceptor sat inside a timeout made SetAgent throw "no nodes implement IAgentSettable". Both wrappers now forward the stamped blackboard context to the wrapped state (IBlackboardSettable, forward-only - the wrapper holds no board state), and a new ILogicWrapper seam exposes the wrapped instance for the wiring that cannot be forwarded interface-to-interface: agent stamping (generic in the agent type) reaches through decorator layers in the graph walk, and all four machines' report tables resolve the wrapped state's own reporter so log wiring and slot clearing behave exactly as for a bare state. Timeout semantics are untouched; the public API baselines record the new surface.
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.
Two wiring gaps, both found by a downstream consumer that observes runs and wraps steps in timeouts.
What was broken
1. Observers saw a nameless id for the node a dispatch or plain start jumps to. Directors — the event-entry dispatcher (
StartWithEvents), the plain-runOtherwiseroute,ChoiceState/SwitchState, and any user director — hold targetNodeIds captured at authoring time, beforeBuild()applies display names. The run loops routed on those raw ids, soOnTransition/OnStateEntered/OnStateExitedfor the first node of every entry chain carried an emptyName(a single-step chain never surfaced a named event at all), while every ordinary edge hop reported the built, named id. Any observer that keys onNodeId.Name— progress publishers, trace tooling — silently dropped the first authored step of every chain.2. The timeout wrappers swallowed machine wiring.
AsyncTimeoutState/TimeoutStateimplemented only the logic interface, so the blackboard stamping walk, agent stamping, and the machines' log-report resolution all stopped at the wrapper: a blackboard-using state inside a timeout lost its board context, itsLogcalls went nowhere, and a graph whose only agent acceptor sat inside a timeout madeSetAgentthrow "no nodes implement IAgentSettable".What changed
IBlackboardSettable, forward-only — the wrapper holds no board state, so nothing is duplicated).ILogicWrapperseam (WrappedLogic) exposes the wrapped instance for the wiring that cannot be forwarded interface-to-interface: agent stamping is generic in the agent type, and the sync report slot lives on theStatebase class. The graph's agent walk and all four machines' report tables resolve through decorator layers, so the wrapped state's own slots are wired and cleared exactly as if it were bare. A decorated composite still surfaces its children to the agent walk.ILogicWrapper, the wrappers' interface lists).Other wrappers checked
SyncLogicAdapterwas already unwrapped at theLogicNodelevel;AllState/AsyncAllStatehold delegates that receive the routed context as a parameter; the machine-wrapping composites already implementIBlackboardSettable/ISubGraphProvider. The two timeout states were the only decorators with the hole.Resume semantics
Untouched.
Resume/ResumeDeepalready restore the current node from the graph's own node table (built ids), and restore deliberately replays no observer events — both fixes only affect the live routing/wiring paths.Tests
DirectorTargetObservationTests(9): entered/exited/transition ids carry the built name for an event dispatch target (full-run and stepped), the plain-startOtherwisetarget, a single-step entry chain, and choice branch targets — sync and async; plus a single-node-graph pin. All fail before the fix.TimeoutWiringForwardingTests(7): board, log report, and agent reach a state inside the async and sync timeout wrappers, including the sync-state-under-async-machine bridge case. All fail before the fix.Downstream note
The Aitomata consumer (project reference against this checkout) was smoke-checked with its full acceptance harness after both fixes: everything green — 222 backend tests plus the frontend phase, no test there asserted the old missing-first-step behavior. Its run-progress publisher now correctly emits step-started/step-finished for the first authored step of every chain, and its
StepTimeoutStateforwarding-subclass workaround (which forwardsIBlackboardSettable/ILogReporteritself) keeps working unchanged and can be retired at the consumer's leisure.