Workflow code export (LangGraph)¶
Status: Current | Last reviewed: 2026-10-08
TrueServe can export a workflow as a standalone, credential-free Python LangGraph package that executes the designed workflow outside the platform. async-agent already executes workflows as LangGraph StateGraphs at runtime (langgraph-construction.md); the export freezes that dynamic interpreter into generated code plus a vendored pure-Python kernel, dropping the DB/RabbitMQ/Redis coupling.
Entry point¶
GET /api/workflows/[workflowId]/export?format=langgraph returns application/zip, gated by ENABLE_CODE_EXPORT / NEXT_PUBLIC_ENABLE_CODE_EXPORT (the same consolidated flag as the Genkit/ADK agent exports). The id may be versioned (42:v3, 42:draft — resolved via resolve_workflow_version); a bare id uses the current config. Unsupported workflows return 422 {error, issues[]}; both entry points use a fetch-based handler so those issues surface in a toast.
Two UI entry points: the Workflows list's ⋯ actions menu (a flat item), and the workflow editor's Export button, which opens the shared components/ExportModal.tsx picker (portable + LangGraph cards with descriptions) and exports the version currently open in the editor (parsed.versionedId).
format=json and format=zip (portable container) keep their existing behavior. The route was refactored to the same DI pattern as the pipelines export route (WorkflowExportDependencies / handleWorkflowExportRequest).
Module: apps/ai-manager/lib/workflow-code-export/langgraph/ (index.ts analysis + emitters, kernel.ts generated, tests alongside).
Vendored kernel¶
The exported package embeds the pure-Python subset of the async-agent runtime under src/exported_workflow/kernel/ so rendering/eval semantics match the platform exactly:
| Kernel file | Vendored from | Edits |
|---|---|---|
template_renderer.py |
app/services/template_renderer.py |
loguru → stdlib logging; kernel-local import path |
workflow_expressions.py |
app/services/workflow_expressions.py |
none (stdlib + jsonata) |
output_bindings.py |
app/nodes/_output_bindings.py |
none |
state_fields.py |
app/nodes/_common.py |
resolve_state_field + merge_results only (boto3 presign dropped) |
router_eval.py |
app/nodes/router.py |
pure eval fns extracted (_truthy, _substitute_state_paths, _compare_expression) + evaluate_router_outputs |
kernel.ts is generated — regenerate with node scripts/sync-langgraph-kernel.mjs (from apps/ai-manager) after changing any of those async-agent sources. A drift test in index.test.ts re-derives the expected content from the repo sources and fails when the kernel is stale.
Node disposition (v1)¶
Supported (faithful handler modules): llm_agent (LangChain ChatOpenAI/ChatAnthropic/ChatGoogleGenerativeAI per provider; output_schema → with_structured_output; env-var keys), router (kernel eval, __default__ fallback identical to the runtime's _create_router_function), assignment (all five operators), passthrough, http_request (httpx; integration auth scrubbed to an env var), human_review (native interrupt()), wait (durations ≤5 min → asyncio.sleep, longer/event → interrupt()), parallel (generated asyncio.gather over baked branch handler sequences — matches runtime semantics; runtime branch nodes aren't top-level graph nodes so native LangGraph fan-out is wrong), foreach (inline body_graph → hoisted per-body-node modules run sequentially per iteration with the iterator scope; legacy body id-lists reference top-level modules).
Degraded (stub or warning): tool (registry lookup → NotImplementedError stub, tool_node_degraded), subworkflow inline (flagged for manual review), foreach bodies containing routers (sequential execution, foreach_body_router_degraded), llm_agent with workflow tools (llm_tools_not_exported).
Unsupported (stub raising NotImplementedError, topology preserved, listed in unsupported.json): code, notification, conversation_actions, salesforce, ocr_extract, transcribe, pii_redaction, reasoning_step/reasoning_chain, plan_extract, score_generation, subworkflow ref-mode. note nodes and body_ref edges are filtered out exactly as the runtime builder does.
Hard 422: execution_mode='batch' (depends on TrueServe batch pollers), or a workflow with zero exportable nodes.
Package shape¶
pyproject.toml # langgraph, jsonata-python, httpx, per-provider langchain-*
langgraph.json # LangGraph Studio (`langgraph dev`) support
.env.example # provider keys + per-node scrubbed auth env vars
run.py # CLI: --input/--trigger JSON → ainvoke; prints node_results + resume instructions
manifest.json # target, source, per-node disposition map
secret_report.json / unsupported.json / README.md
src/exported_workflow/
state.py # WorkflowState TypedDict (vendored minus tenant fields)
graph.py # build_graph(checkpointer=None) — START/END wiring, add_conditional_edges w/ route_map + __default__, MemorySaver default
kernel/ # vendored kernel (above)
nodes/<id>.py # one module per node instance, config baked as constants
Suspend / resume semantics¶
human_review and long wait nodes suspend via LangGraph interrupt(). run.py prints the interrupt payload, the thread id, and Command(resume=...) instructions. The default checkpointer is MemorySaver, so suspensions do not survive a process restart — the README instructs deployers to pass a persistent checkpointer (e.g. PostgresSaver) to build_graph(checkpointer=...) for durable suspend/resume.
Testing¶
lib/workflow-code-export/langgraph/index.test.ts covers package inventory, graph wiring (START/END, conditional edges + __default__), router semantics, foreach body-graph hoisting, parallel branch references, interrupt warnings, http integration scrubbing, note/body_ref filtering, batch/all-unsupported 422s, the kernel drift check, and a no-infra-imports kernel scan. route.test.ts covers the DI route (flag-off 404, zip streaming, 422 passthrough, versioned-id resolution, legacy json shape). The exported output was smoke-verified end-to-end against the async-agent venv's langgraph: build_graph() compiles and a router workflow executes both branches correctly.
See also¶
- Agent code export — the Strands/Genkit/ADK agent targets and the shared analysis core.
- LangGraph construction — how async-agent builds the same graph dynamically at runtime.
AI-assisted Decision nodes are rejected explicitly during standalone export, including nested nodes. The exported router currently supports Rules mode only; use workflow JSON export/import to preserve AI assessment configuration.