Skip to content

ADR 0004 — Multi-agent workflows: native engine first, LangGraph as a backend

Status

Accepted (2026-06).

Context

The platform needs n8n/Flowise-style multi-agent orchestration: users compose a DAG of agents ("AI employees") on a canvas, run it, and watch data flow along edges. The community standard for graph-based agent orchestration is LangGraph; LangChain is the broader ecosystem around it. Both resolve and install on our Python 3.14 toolchain (langgraph 1.2.x, langchain-core 1.4.x).

Two architectural options:

  1. Build the workflow engine on LangGraph exclusively.
  2. Build a small, deterministic native engine and also compile the same workflow spec to a LangGraph StateGraph as a selectable backend.

Decision

Option 2. aop.orchestrator.workflow defines the WorkflowSpec contract (nodes/edges/templates), validation (unique ids, known agents, acyclic), and a WorkflowEngine with two real backends:

  • native (default): level-parallel topological execution on asyncio. ~150 lines, zero dependencies, fail-fast semantics we fully control.
  • langgraph: the same spec compiled to a StateGraph (fan-in nodes use defer=True so joins run exactly once). Selected per run (engine field) or globally (AOP_WORKFLOW_ENGINE=langgraph).

aop.llm.langchain_bridge.build_chat_model() additionally exposes any AOP provider (Ollama, OpenRouter, fine-tuned registry models, …) as a langchain-core BaseChatModel, so LangChain/LangGraph tooling can drive our unified adapter stack directly.

Consequences

  • The product works fully without the optional orchestration extra; with it, users get LangGraph execution and LangChain interop with the same specs.
  • Both backends share node execution (template render → agent run → event publish), so behaviour and event streams are identical from the UI's view.
  • The deliberate cost: two execution paths to keep in parity. The shared _run_node keeps the divergence to graph traversal only.