Multi-Task (DAG) Routing

Most assistants answer a complex request with a single AI call. Synaplan does something different: a small planner model turns the request into a DAG — a directed acyclic graph of tasks — and executes the steps in order, streaming a live task card for each one. Ask for several things at once and you get several real outputs back, on whatever channel the message arrived on.

This is the deep dive. For where it sits in the wider system (the Docker service map, SSE vs WebSocket streaming, the realtime layer) see Architecture & Realtime.


Why a plan instead of one call

A single prompt often hides several distinct jobs. "Summarise this PDF, translate the summary to German, and read it aloud" is three capabilities, not one. A flat chat completion has to fake that in one pass; a plan does each step with the right tool and lets you watch it happen.

Single AI call Multi-task DAG plan
One model, one answer The right capability per step (RAG, search, media, document, calendar, …)
Files? Usually one, or none Multiple generated files from one request
Opaque — you wait, then a wall of text Transparent — a live card per step shows pending → running → done
Hard to extend A typed capability registry you can grow

Simple requests still take the classic fast path — one classifier decision, one handler, no planner overhead. The planner only engages for requests that the AI sorter sees as genuinely multi-step, and only when an administrator has enabled it.


A worked example

Prompt: "Can you create a short paragraph about DAG routing in AI models and create a reminder calendar entry for tomorrow at 10am?"

The planner emits a two-node plan. The chat UI shows a Task plan · 2/2 card while it runs, then leaves both results in place:

  • Answer — a streamed text paragraph (chat capability).
  • Calendar invite — a downloadable .ics meeting file for tomorrow 10:00, with the date resolved to an absolute time (calendar_event capability).
                  ┌──────────────────────────────┐
  user request ──▶│  TaskPlanner (planner model  │
                  │  → validated JSON DAG)        │
                  └───────────────┬──────────────┘
                                  ▼
     ┌──────────────── DAG, executed in topological order ──────────────┐
     │   n1: chat ───────────────┐                                       │
     │                           ▼                                       │
     │   n2: calendar_event ──▶ compose_reply  (terminal reply node)     │
     └───────────────────────────────────────────────────────────────────┘
                  │   live SSE cards: plan · task_update · task_chunk · task_file
                  ▼
          Answer text   +   meeting_YYYYMMDD_HHMMSS.ics

The relative date ("tomorrow at 10am") is resolved by injecting the current time into the planner prompt, so the .ics lands on the right day in the right timezone — no manual date math.


Capabilities

Each DAG node runs exactly one capability. The planner may only emit capabilities from this fixed, validated set — there is no arbitrary code execution.

Capability Does
extract_text Read text from an uploaded attachment (Tika / OCR / Whisper)
chat A normal text answer
summarize Summarise input text
translate Translate input text
rag_query Semantic search over your knowledge base
web_search Live web search with a generated query
file_analysis Vision / OCR / document question-answering
image_generation Generate an image (the /pic path)
video_generation Generate a video (the /vid path)
text2sound Text-to-speech audio
document_generation Build a CSV / XLSX / DOCX / PPTX file
calendar_event Build an .ics calendar invite
email_me Email the result to the account owner

A hidden compose_reply node always terminates the graph — it assembles the final text plus any attachments into the single reply you see.

Steps pass data along the edges with a small reference grammar ($message.text, $n1.text, $n1.file, …), so one node can consume what an earlier node produced.


How a plan executes

  1. Plan — the planner model returns JSON; Synaplan validates it (known capabilities only, valid dependencies, no cycles, a sane node cap, a valid reply node). Invalid output safely falls back to a single chat answer.
  2. Execute — nodes run in topological (dependency-first) order. With parallel mode enabled, independent media nodes (image/video/audio) are offloaded to concurrent subprocesses while text nodes stream inline.
  3. Isolate failures — if a node fails, only the steps that depended on it are skipped; the rest of the plan still delivers. If everything fails, the turn falls back to the classic single-handler path.
  4. Assemble & deliver — results (including multiple files) are delivered on the originating channel: chat, widget, WhatsApp, email, or webhook. Web chat additionally persists the card states, so the task plan is still there after a reload.

Live progress events (SSE)

Task plans stream over the same Server-Sent Events channel as normal answers (/api/v1/messages/stream). Alongside the answer tokens you get plan events:

Event (status) Fires when Key fields
plan A multi-node plan starts the node list (node_id, capability, kind), reply_node
task_update A node changes state node_id, state (pendingrunningdone / failed / skipped)
task_chunk A text node streams a token node_id, chunk
task_file A node produced a file node_id, type, url
task_progress A long media render advances node_id, percent, provider status

See Code Examples for the SSE client pattern.


Enabling & configuring

Multi-task routing is controlled per user under Settings → Routing in the app. Existing installs keep the classic single-handler fast path until it is switched on, and a shadow mode can plan without executing so operators can review what the planner would do before turning it loose.

Behind the scenes these map to the MULTITASK configuration group (ROUTING_ENABLED, SHADOW_MODE, PARALLEL_ENABLED, MAX_PARALLEL, NODE_TIMEOUT) plus the classifier's fast-path flag. Developer notes live in docs/DEVELOPMENT.md in the main repository.


Extending the graph

Capabilities are a typed registry, not a hardcoded prompt: a Capability value plus a tagged task-runner service. Today's set are thin adapters over capabilities Synaplan already runs in production, which keeps the graph safe and predictable.

On the roadmap: open DAG endpoints. We're working toward DAG nodes that hand off to n8n and other open-source services — so the planner can orchestrate the self-hosted stack you already operate. The AI does the planning; your tools do the work, on your infrastructure. This is a particularly good fit for self-hosting teams who want an AI front door to their existing automations rather than a closed black box.


See also