Skip to content

Conversation

@betterclever
Copy link
Contributor

Summary

This PR implements the sub-workflow feature (core.workflow.call), enabling workflows to call other workflows as child processes and consume their outputs. It includes full parent/child navigation UI for seamless workflow debugging.

Features

1. Sub-workflow Component (core.workflow.call)

  • Dynamic input ports: Automatically syncs input ports from the child workflow's entrypoint
  • Result output: Returns child workflow outputs as a single result object containing all node outputs
  • Version strategy: Supports latest or pinned version selection
  • Timeout configuration: Configurable timeout for child workflow execution

2. Parent/Child Navigation

  • View Child Run button: Appears on core.workflow.call nodes after execution completes

    • Violet-styled button on the node itself (not in event inspector)
    • Navigates to the child workflow run
  • Floating breadcrumb: Shows on canvas when viewing a child run

    • Displays "Child of [Parent Workflow]" with clickable link
    • Shows which node spawned the child workflow
    • Positioned top-left of canvas (same location as "Show Components" in design mode)
  • Run redirect route: /runs/:runId automatically redirects to /workflows/:workflowId/runs/:runId

3. API Enhancements

  • parentRunId and parentNodeRef fields added to run responses
  • GET /api/v1/workflows/runs/:runId/children endpoint for listing child runs
  • Trace events include childRunId in metadata for workflow.call nodes

Testing

  • E2E test validates:
    • Child workflow receives inputs correctly
    • Child computes expected output (21 * 2 = 42)
    • Parent workflow consumes child's output
    • Trace events contain childRunId linkage

Run tests:

RUN_E2E=true bun test e2e-tests/subworkflow.test.ts

Files Changed

Backend

  • backend/src/workflows/workflows.service.ts - Added parentRunId/parentNodeRef to run summary

Worker

  • worker/src/components/core/workflow-call.ts - Core workflow.call component implementation

Frontend

  • frontend/src/components/workflow/WorkflowNode.tsx - View Child Run button on nodes
  • frontend/src/components/workflow/WorkflowBuilderShell.tsx - Execution overlay support
  • frontend/src/components/timeline/RunBreadcrumbs.tsx - New breadcrumb component
  • frontend/src/pages/RunRedirect.tsx - New redirect page
  • frontend/src/features/workflow-builder/WorkflowBuilder.tsx - Breadcrumb integration
  • frontend/src/store/runStore.ts - Parent run fields in store
  • frontend/src/services/api.ts - getChildRuns API method

Packages

  • packages/backend-client/src/api-client.ts - listWorkflowRunChildren method

…and a single `result` object output.

- New component `core.workflow.call` with dynamic input ports derived from selected child entrypoint `runtimeInputs`: `worker/src/components/core/workflow-call.ts:34`
- Temporal orchestration special-cases `core.workflow.call` to `prepareRunPayloadActivity` + `startChild(shipsecWorkflowRun)` + await result/timeout: `worker/src/temporal/workflows/index.ts:183`
- Parent/child linkage persisted on the child run (`parent_run_id`, `parent_node_ref`) via new migration `backend/drizzle/0018_add-subworkflow-linkage.sql:7`
- Internal run prep accepts linkage fields (`parentRunId`, `parentNodeRef`): `backend/src/workflows/dto/workflow-graph.dto.ts:109`
- New API endpoint to list children: `GET /api/v1/workflows/runs/:runId/children` at `backend/src/workflows/workflows.controller.ts:410` (service impl: `backend/src/workflows/workflows.service.ts:522`)
- Frontend workflow selector for `core.workflow.call.workflowId` that auto-syncs `childRuntimeInputs` and triggers port re-resolution: `frontend/src/components/workflow/ParameterField.tsx:94`
- Trace metadata extended with optional `childRunId/parentRunId/parentNodeRef/depth`: `packages/shared/src/execution.ts:121`
- Per your request, `openapi.json` patched directly and client regenerated: `openapi.json:505`, `openapi.json:5326`, `packages/backend-client/src/client.ts`

To apply DB changes: run `bun run --cwd backend migration:push` (or your normal Drizzle migration flow).

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
- Create child workflow that computes 21 * multiplier
- Create parent workflow that calls child and consumes its output
- Verify child output (result=42) flows correctly to parent
- Verify trace events contain childRunId linkage
- Uses proper edge-based input mappings (sourceHandle/targetHandle)

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
- Add parentRunId and parentNodeRef fields to run API response
- Add floating breadcrumb on canvas when viewing child runs
- Add 'View Child Run' button on workflow.call nodes
- Add /runs/:runId redirect route for navigation
- Add RunBreadcrumbs component with floating/inline variants
- Add getChildRuns API method for listing child runs
- Update runStore to include parent run fields

Navigation flow:
- Parent run: 'View Child Run' button appears on call-child node
- Child run: Floating breadcrumb links back to parent workflow

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +358 to +361
const timeoutMs = timeoutSeconds * 1000
const outcome = await Promise.race([
child.result().then((result) => ({ kind: 'result' as const, result })),
sleep(timeoutMs).then(() => ({ kind: 'timeout' as const })),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle child workflow failures in Promise.race

child.result() rejects when the child workflow throws (and shipsecWorkflowRun always throws on failure rather than returning { success: false }). In that case the Promise.race rejects before outcome is set, so the NODE_FAILED trace with childRunId is never recorded and this node is left with a last event of NODE_STARTED (UI shows it stuck “running” even though the parent run failed). Wrap child.result() in a .catch/try path that records a failure event and rethrows.

Useful? React with 👍 / 👎.

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.

2 participants