Skip to content

Compile workflow calls against the exact version they call, with activation pins - #46

Merged
ancongui merged 15 commits into
mainfrom
feat/call-compiler
Oct 14, 2026
Merged

ancongui merged 15 commits into
mainfrom
feat/call-compiler

Conversation

@ancongui

Copy link
Copy Markdown
Contributor

Summary

The compiler now checks and compiles Call a workflow (callWorkflow) steps against the exact published
workflow version they call, and activation pins the called workflows' activations. Calls do not run yet: the
platform refuses to publish, import or activate a workflow that calls another (WV-IR-UNSUPPORTED) until a later
release runs child workflows.

  • A called version must opt in with spec.callable: {} allows any workflow in the project, allowedCallers
    only the named ones. with is checked against its inputSchema, a businessKey must be text of 1 to 200
    characters, and the step's output is the called workflow's output (wait), a {status, runId, output?, error?}
    envelope (onFailure: continue), or null (mode: detach).
  • New diagnostics at the field to fix: WV-COMP-UNKNOWN_WORKFLOW, WV-COMP-CALL_NOT_CALLABLE,
    WV-COMP-CALL_RECURSION, WV-COMP-CALL_DEPTH (new operator limit max_call_depth, default 8) and
    WV-COMP-CALL_MODE (at …/onFailure). Without a catalog, calls are WV-COMP-CATALOG_PENDING.
  • A call lowers to a callWorkflow IR node with call_input and call_output guards; such a workflow compiles to
    weave/ir-v1alpha4 with the flow.callWorkflow feature. Calls work the same inside loop bodies. Workflows
    without calls compile to the same bytes.
  • check_call_target applies the same resolution, callable, recursion and depth checks for other callers.
  • Activation takes workflow_activation_ids (one live activation of each called version in the same
    environment) and stores workflow_call_pins and call_depth; wrong or missing pins answer
    422 WV-READINESS-CALL. Retiring an activation keeps its pins.
  • A kernel that still receives a call node opens an incident instead of skipping it. weave workflow explain and
    graph show calls, Studio has plain copy for the new diagnostics, shared scope fixtures cover call outputs, and
    the SDK's WorkflowBuilder.with_callable() writes spec.callable.

Verification

  • Local, offline (macOS): make check with every stage passing (unit and contract tests 6831 passed, ruff, format,
    mypy, source coverage, check_docs, mkdocs build --strict, worker packages, installed artifacts); golden
    corpus, golden loops and language fixture checks; Studio type check, Prettier, vitest (1965 tests) and
    production build.
  • Local, real PostgreSQL 17 (throwaway container): publishing a workflow that calls another is refused before
    anything is stored; activation pins one live activation of each called workflow and refuses bad pins (with the
    call feature enabled inside the test); a retired activation keeps its call pins and depth; the definitions,
    unsupported-run, connections, loop publication, loop runtime and loop human-task suites pass after merging
    main. The full integration suite on four workers showed only failures from services the local fixture does not
    start and two capacity-timing tests that also fail on main.
  • CI only: Linux runs the unit, contract and Studio suites; macOS and Windows run the compiler, contract, golden and
    fixture tests (including the call IR, call target and call compiler tests). Runtime, CLI, SDK and activation-pin
    unit tests run on Linux in CI. No CI job runs PostgreSQL tests. No Studio screen changes, so no browser suite was
    rerun.

Decisions

  • A caller's artifact embeds the called version's full definition. The Workflow dependency's document is
    the called version's normalized definition, so the dependency digest equals the published definition digest that
    activation compares. Compiling, importing and activating the caller read only its input and output schemas; the
    callee's steps are never re-checked or run as part of the caller.
    • Size: each distinct called version adds its definition once (a definition is at most 1 MiB by default), not
      its own dependencies or callees, so growth is linear in the number of distinct versions a workflow calls
      directly. The 32 MiB artifact budget still applies: a workflow calling dozens of very large workflows can hit
      it at compile time.
    • Privacy: anyone who can read the caller's version or export its artifact can see the called version's steps,
      prompts, action references, connection slot names and literal values. Calls stay inside one project, and
      catalog reads are project-wide, so this is the same audience that can read the called version itself. Secret
      values are never in a definition (literals bound to secret inputs are refused at compile and import). Treat an
      exported caller artifact as containing its callees' definitions.
  • callable is checked by the caller's name; recursion by exact version, so calling an older version of the same
    workflow is allowed.
  • Every activation pin failure answers 422 WV-READINESS-CALL with a message naming the rule; a pin from another
    environment and a pin that does not exist get the same answer. A pin must resolve to the very activation it names.
  • onFailure with mode: detach is reported as WV-COMP-CALL_MODE at the field; Studio's copy names the
    When it runs and If the called run fails settings. Other schema errors in the same document are still
    reported.
  • A literal bound to a called workflow's secret input is refused with WV-SCHEMA-SECRET_VALUE at the document
    root, exactly like a literal bound to an action's, decision table's or connector's secret input: the shared
    payload admission that refuses it does not track paths. Pointing every binding's refusal at its with field is a
    separate change for all step kinds.
  • A callee retired at the same moment a caller pins it can still be pinned; the call runtime re-checks.

Follow-ups for the call runtime

  • Re-check, when a child run starts, that the pinned callee activation, the latest revision of its name and its
    version are still live (retirement can race activation), and that the actor's scope still matches.
  • Validate child input and output against the callee's inputSchema and outputSchema from the Workflow
    dependency document (and the continue envelope), not only against the guards the artifact carries; apply the
    same classification and file admission as any run start and task output.
  • Replace the kernel's incident at a call node, advertise flow.callWorkflow, and add the call migration and
    dispatch.
  • Wire call_tree_side_effect into activation and run-start side-effect acknowledgement, and apply call_pins to
    test activations of drafts (DraftTestBindings.workflow_activation_ids).
  • Report payload-admission refusals (WV-SCHEMA-SECRET_VALUE, classification) at the binding's path for every step
    kind, calls included.
  • Share one block walk between compiler/calls.py workflow_steps and language_support, and memoize
    CallGraph.depth.

Andres Contreras added 15 commits October 13, 2026 14:57
…k to the caller, and stays within the call depth
… callee calling back an unpublished caller is recursion, and that callee connections stay out of the caller
@ancongui
ancongui merged commit 3136050 into main Oct 14, 2026
17 checks passed
@ancongui
ancongui deleted the feat/call-compiler branch October 14, 2026 02:11
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