docs: rewrite 'Why Conductor?' with repeatable/deterministic/source-controlled framing - #185
Merged
Merged
Conversation
…ontrolled framing Replace the old 'single prompt can't do X' opener with a concise three-pillar description: repeatable execution, deterministic routing, and version-controlled YAML workflows. Surfaces the real differentiator (zero-token orchestration) and uses concrete use-case examples in the lead-in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merged
jrob5756
added a commit
that referenced
this pull request
May 14, 2026
- feat(engine): registry references in sub-workflow workflow: field (#188) - feat: Claude Code plugin marketplace (#186) - docs(skill): refresh conductor skill with latest features (#187) - docs: rewrite 'Why Conductor?' README section (#185) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jrob5756
pushed a commit
to lesandiz/conductor
that referenced
this pull request
Jun 2, 2026
…ity (microsoft#113) * feat: breadcrumb navigation and depth-isolated subworkflow rendering Adds full subworkflow awareness to the per-run web dashboard: State pollution fix: - Added wf_depth counter to workflow store — only depth-0 workflow_started initializes root context. Inner workflow events are routed to isolated SubworkflowContext objects. - Each subworkflow invocation gets its own nodes/routes/agents maps, keyed by (parentAgent, iteration). Repeated runs of the same subworkflow no longer share state. Subworkflow event handling: - Added TypeScript types for subworkflow_started, subworkflow_completed, subworkflow_failed events (mirrors engine emit). - Event handlers create/update child contexts and track the active context path for routing subsequent events. Breadcrumb navigation: - New BreadcrumbBar component shows the context stack above the graph (e.g., Root > twig-sdlc-planning > plan-issue). - Click any breadcrumb to navigate to that context level. - Double-click a workflow agent node in the graph to dive into its subworkflow context. - Graph rebuilds automatically when context changes. Context stack architecture: - SubworkflowContext[] tree structure mirrors workflow nesting. - activeContextPath tracks where live events are routed. - viewContextPath tracks what the user is viewing (independent). - getViewedContext() returns the correct nodes/routes for rendering. - All event handlers use activeTarget() helper to route to the correct context's nodes/groupProgress. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: subworkflow node visual, detail panel, and context-aware rendering Phase 4-5 of breadcrumb navigation feature: WorkflowNode component: - New node type for type:'workflow' agents with dashed border and Layers icon (visually distinct from regular agent nodes). - Shows child workflow name, elapsed time, and a chevron indicator when a SubworkflowContext exists. - Double-click to navigate into the subworkflow graph. SubworkflowDetail panel: - New detail component shown when a workflow agent is selected. - Lists all subworkflow runs for that agent with status, agent count, and cost summary. - Click any run to navigate into its context. Context-aware rendering: - All graph node components (AgentNode, ScriptNode, GateNode, GroupNode, AnimatedEdge) now read from getViewedContext().nodes instead of root state.nodes — ensures correct status display when viewing child contexts. - DetailPanel reads from viewed context for node lookup. - GroupDetail reads groupProgress from viewed context. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: replace getViewedContext() selectors with stable hooks getViewedContext() creates a new object on every call, causing infinite re-render loops (React error microsoft#185) when used inside Zustand selectors. New hooks in use-viewed-context.ts use useMemo with stable state references: - useViewedNodes() — nodes map for current context - useViewedGroupProgress() — group progress for current context - useViewedHighlightedEdges() — edge highlights - useViewedSubworkflowContexts() — child contexts - useViewedGraphData() — full graph data for WorkflowGraph All graph components and detail panels updated to use these hooks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: Stop button now reliably stops subworkflows When a subworkflow agent is running, the shared interrupt_event was being silently consumed by _check_interrupt() in web mode (line 857: 'if self._web_dashboard is not None: return None'). This meant Stop would pause the current agent but then resume and continue — the interrupt never propagated to the parent engine. Two fixes: 1. _check_interrupt(): when _subworkflow_depth > 0, raise InterruptError instead of silently consuming the interrupt. This unwinds the child engine back to the parent's _execute_subworkflow try/except, stopping the workflow. 2. _handle_web_pause(): in subworkflows, also watch interrupt_event alongside resume/kill/disconnect events. A second Stop click while an agent is paused now raises InterruptError immediately, without requiring Resume first. Root-level (depth 0) behavior is unchanged — Stop still pauses the current agent with Resume/Kill options in the dashboard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: guard against proactor accept-loop race on Windows (Python 3.14+) On Windows with Python 3.14+, the proactor event loop's accept callback can fire after Server.close() sets _sockets = None during shutdown, causing an AssertionError in base_events.py:_attach that crashes the workflow process. Fix: - Add _guarded_serve() wrapper that catches AssertionError when the uvicorn server is in shutdown state (should_exit = True) - Install a custom event-loop exception handler during server lifetime that suppresses the same race when it surfaces through callbacks - _is_proactor_shutdown_race() validates: AssertionError type, server shutdown state, and asyncio-originating traceback frames - Restore original exception handler in stop() The guard is narrowly scoped: only AssertionError during server shutdown is suppressed. All other exceptions delegate to the original handler. Tests: 9 new tests covering the race detection, exception handler delegation, guarded serve behavior, and edge cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): clear stale graph edges when navigating between workflow layers When navigating between subworkflow layers via breadcrumbs or double-click, old React Flow edges from the previous layer persisted as floating links disconnected from any visible nodes. Two fixes: - WorkflowGraph: explicitly clear nodes and edges when switching to an empty context (subworkflow data not yet populated) - graph-layout: filter edges against the actual node ID set to prevent orphan edges from routes referencing non-existent nodes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(web): add URL query param deep-linking for agent and subworkflow nodes Parse ?agent={name} and ?subworkflow={name} query params on initial load to auto-select and center the matching node in the workflow graph. This enables the meta-dashboard (conductor-dashboard) to generate clickable breadcrumb links that open the conductor UI focused on a specific node. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): support nested subworkflow paths and combined agent deep-links Update useDeepLink hook to: - Parse slash-separated subworkflow paths (e.g., ?subworkflow=planning/design) for navigating multiple levels deep into nested subworkflows - Support combined ?subworkflow=X&agent=Y to select an agent within a subworkflow context - Remove dependency on subworkflowContexts selector (array mutation doesn't trigger re-renders); rely on late-joiner replay instead Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add dashboard deep-link specification Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): rewrite deep-link hook for reliability + error feedback Rewrote useDeepLink to fix timing issues that prevented navigation: - Use zustand.subscribe() instead of useEffect + selector reactivity. The old approach relied on subworkflowContexts selector changes, but the store mutates the array in-place during processEvent, so the selector never detected changes. - Resolve the full subworkflow path in one shot via index walking instead of calling navigateIntoSubworkflow() in a loop. - Set viewContextPath directly via setState instead of relying on action functions that might see stale state. - Add error banner when deep-link target is invalid: shows the error message and a link back to the root dashboard. - Validate agent exists in the target context's agent list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(web): add ingress/egress nodes for sub-workflow views When viewing a sub-workflow graph, the start and end nodes are now replaced with distinct ingress/egress nodes that: - Use a dashed border with rounded-xl style to visually distinguish them from regular start/end nodes - Display 'From <parent agent>' on the ingress node - Display 'Return to <parent agent>' on the egress node - Navigate back to the parent workflow on double-click Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * build: rebuild frontend static assets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): route nested sub-workflow events by engine-supplied path The breadcrumb navigation feature could not reach gates inside nested sub-workflows: the dashboard's view path did not follow the active context as a parent agent spawned a child workflow, so a human_gate inside that child was unreachable from the breadcrumb tree. The root cause: the store inferred sub-workflow parentage from the shared activeContextPath, then mutated it on every subworkflow_started. That conflated the user's view cursor with the engine's execution cursor and made events impossible to route correctly under any concurrency. Engine: each WorkflowEngine now carries _dashboard_context_path, the slot-key path identifying its position in the recursive sub-workflow tree (root = []). _execute_subworkflow accepts a slot_key and threads [*parent_path, slot_key] into the spawned child engine. _emit auto- stamps subworkflow_path on every event a sub-engine produces. Sequential subworkflow_started/_completed/_failed include parent_path and slot_key. Frontend: subworkflow_started/completed/failed and workflow_completed/ failed handlers resolve the owning context strictly from engine- supplied parent_path / subworkflow_path via a new resolveSlotPath helper, instead of mutating a single shared activeContextPath. activeTarget consults subworkflow_path on event data when present so per-iteration agent_message/tool/turn events land in the right per- iteration context. viewContextPath sticks to the live edge when the user has not navigated away, so newly-spawned gates inside sub- workflows are reachable without manual breadcrumb clicks. Handlers fall back to legacy behavior when these new fields are absent. SubworkflowDetail navigates by ctx.slotKey (stable across iteration reorders) instead of (agent_name, iteration). Breadcrumbs render slot keys so concurrent iterations are distinguishable. Adds TestSubWorkflowDashboardPath covering parent_path/slot_key emission for sequential sub-workflows and the auto-stamped subworkflow_path on the child workflow_completed event. This is a self-contained slice of a broader fix that also addresses concurrent for_each-of-workflow stacking; the matching for_each-side emit changes are staged in microsoft#110 and become functional once both PRs land. Order of merge does not matter — handlers degrade gracefully when events lack the new fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(web): support for_each iteration deep-link notation Add flexible segment matching for subworkflow deep-links: 1. Exact slotKey match (e.g. plan_child[item-0]) 2. Positional index (e.g. plan_child#0, 0-based) 3. Bare agent name (when unambiguous) Ambiguous bare names (multiple for_each iterations) now produce an actionable error listing valid alternatives instead of silently failing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): remap group-child edges to parent node to prevent floating arrows Edges whose source or target was a child node inside a parallel group rendered at incorrect absolute positions (React Flow positions children relative to the parent group). This caused orphaned arrowheads in the top-left corner of the graph. Fix: remap any edge endpoint that references a group child to the parent group node instead, so dagre can properly route the edge and React Flow draws it at the correct position. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): auto-fit viewport on context switch Add FitViewOnContextSwitch component that triggers fitView when navigating between sub-workflow contexts, preventing stale viewport positioning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): reset viewContextPath for agent-only deep-links When ?agent= is provided without ?subworkflow=, explicitly pin viewContextPath to root ([]) before the agent lookup runs. This prevents the sticky-follow mechanism from advancing the view into a stale subworkflow/for_each iteration during WS replay, which caused 'Agent not found' errors for root-level agents. Also handles ?subworkflow=&agent=foo (empty subworkflow string) correctly — empty string is falsy so falls through to the agent-only reset path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): route child workflow_started by engine slot path Under concurrent for_each iterations, the global activeContextPath is advanced by each subworkflow_started event. When workflow_started for an earlier iteration's child engine arrived, it was resolved against the (now-stale) activeContextPath, so its agents/routes were written to the wrong sibling ctx — or got overwritten by a later sibling. That manifested as phantom routes (and missing routes) when the user navigated into a deeply-nested for_each iteration: routes from one iteration would surface in another, leaving edges drawn between agents that don't appear in the iteration's view. workflow_completed and workflow_failed already used the engine- supplied subworkflow_path slot key (commit f77b20b); workflow_started was the one handler missed. This change makes it consistent with its sibling handlers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): bail on slot-resolution miss in subworkflow_completed/failed Both handlers used esolved?.indexPath ?? [] as a fallback when resolveSlotPath returned null. That silently routed the event to the root context, with two distinct corruption modes: 1. argetNodes defaulted to state.nodes (root), so a same-named root agent had its status overwritten to 'completed' or 'failed' by an unrelated subworkflow event. 2. state.activeContextPath = parentIndexPath then unconditionally reset the active path to [], breaking subsequent sibling routing. Mirror the guard that subworkflow_started already uses (line 1365): when resolveSlotPath returns null, return early. A null result means the event arrived before its sibling subworkflow_started or the path is inconsistent — neither warrants polluting root state. Reported-by: jrob5756 (PR microsoft#113 review) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(engine): cover subworkflow stop/interrupt and dashboard event payloads Adds coverage for code paths that previously had none, all introduced by the breadcrumb-navigation PR: TestCheckInterruptSubworkflow - test_check_interrupt_raises_in_subworkflow Sub-engine in web mode propagates the interrupt as InterruptError so the child unwinds back to the parent. - test_check_interrupt_consumed_silently_at_root Regression guard for the root-depth web-mode silent-consume branch (the partial-output handler does the real pausing). TestHandleWebPauseSubworkflow - test_handle_web_pause_stop_event_in_subworkflow Pause-then-Stop in a sub-workflow exits via interrupt_event without requiring Resume first. - test_handle_web_pause_root_ignores_interrupt_event Documents the intentional root-vs-subworkflow asymmetry: at root, only Resume or Kill exit a pause. TestSubWorkflowDashboardPath (additions) - test_subworkflow_failed_event_carries_parent_path_and_slot_key The exception branch of _execute_subworkflow emits subworkflow_failed with parent_path and slot_key. Previously only the success-path emit was asserted. - test_nested_subworkflow_path_accumulates At depth >= 2 (parent -> mid -> leaf), each engine emits its own workflow_completed and the auto-stamped subworkflow_path chains correctly across nesting levels. - test_concurrent_for_each_subworkflow_emits_distinct_slot_keys Existing for_each test ran with max_concurrent=1; this variant uses max_concurrent=3 so iterations actually overlap, proving slot_key uniqueness is not an artifact of serial execution. Reported-by: jrob5756 (PR microsoft#113 review) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(engine): document root-vs-subworkflow pause/stop asymmetry Adds an inline comment in _handle_web_pause explaining: 1. Why root depth deliberately omits the interrupt_event subscription (pause exits only on Resume or Kill). 2. Why subworkflow depth subscribes to it (so Stop unwinds the child engine without requiring Resume first). 3. The tiny clear()/create_task() race window where a Stop click can be silently discarded, and the trade-off vs. carrying a stale Stop signal across pause cycles. No behavior change. Documents an intentional design choice flagged in PR microsoft#113 review. Reported-by: jrob5756 (PR microsoft#113 review) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * build: rebuild frontend static assets after fix-chain restoration Rebuilds the production bundle so it incorporates the subworkflow_completed/failed slot-resolution fix (f374a19) that was cherry-picked into this branch. Without this rebuild the committed `static/` bundle would not reflect the source change and the dashboard would still exhibit the bug at runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(web): transitive agent search for agent-only deep-links The previous fix (593f4e1) reset viewContextPath to root for agent-only deep-links to stop sticky-follow from stranding the user inside a stale for_each iteration during replay. That solved the false-negative case (agent at root) but introduced a false-negative for nested agents: if the agent only exists inside a sub-workflow / for_each iteration, the resolver would now error with 'Agent X not found in root workflow.' That broke external integrations (e.g. notification feeds) that surface ?agent=X without knowing the parent slot chain. Resolution algorithm now: - subworkflow=foo&agent=bar: navigate to foo, look for bar there; if bar isn't at foo but exists elsewhere, list discovered locations in the error so the next click is obvious. - agent=bar (no subworkflow): try root first; otherwise walk every sub-workflow context. On exactly one match: navigate there. On many matches (e.g. bar ran in every for_each iteration): pick running > deepest > newest, mirroring the engine's live-event routing precedence. - Zero matches anywhere: deterministic error, view pinned to root so sticky-follow still doesn't strand. Also reworks the wait condition: instead of firing on the first state change after agents.length > 0 (which races against WS replay of nested subworkflow_started events), the resolver debounces 200ms of state quiescence and only applies once the target is resolvable or the workflow has reached a terminal state. A 5s hard cap keeps live-workflow deep-links from hanging when the target never appears. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Daniel Green <dangreen@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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.
Replace the old 'single prompt can't do X' opener with a concise three-pillar description: repeatable execution, deterministic routing, and version-controlled YAML workflows. Surfaces the real differentiator (zero-token orchestration) and uses concrete use-case examples in the lead-in.