Skip to main content

Aggregator Agent + Workflow Registry

Brain has three entry points for invoking a workflow: chat (the default), slash commands (the bypass), and approval clicks (from the Inbox). All three eventually run the same workflows. What changes is how the workflow is selected. The aggregator agent is the routing layer for the chat entry point. The workflow registry is the list of things workflows can be routed to. Together they make Brain composable: adding a new workflow is a single-file change, and the chat surface picks it up automatically.

The three entry points

Each entry point has its own purpose:
  • Chat is the default. Mai types “draft a quote for Jaspal J26WGW153” and the aggregator routes it. The fastest path for ad-hoc work.
  • Slash commands are the power-user bypass. Mai knows exactly what she wants, types /generate-bom, skips the aggregator entirely. Lower latency, lower cost, deterministic.
  • Approval clicks are the Inbox-driven path. Brain produced a draft; Mai clicks “Approve and create” and the workflow advances. No language interpretation needed.
All three paths converge on the same workflow registry. The same /generate-bom workflow runs whether it was invoked by chat, slash, or click. There is exactly one implementation of each workflow.

The aggregator agent

The aggregator is a Tier 3 component (see Implementation Tiers) living inside the Brain framework, not a separate service. Its job is small and well-bounded:
  1. Classify intent. Given a user message, identify which workflow (if any) the user wants to invoke.
  2. Extract parameters. Pull out the relevant entities — style references, client names, dates — and map them onto the workflow’s parameter schema.
  3. Ask for clarification when ambiguous. “Draft a quote for who?” — multi-choice questions when intent is unclear.
  4. Refuse out-of-scope requests. Per Principles #2, the aggregator only routes to registered workflows. Anything else returns the polite redirect message.
  5. Route to a workflow. Invoke the matched workflow with extracted parameters and return its result.
The aggregator does not:
  • Execute workflow logic itself (workflows do that)
  • Hold persistent business state (workflows write to the DB)
  • Make commercial or approval decisions (every workflow has its own approval gates)
This keeps it stateless, debuggable, and replaceable. The aggregator’s prompt and model can change independently of any workflow.

The workflow registry

Every workflow in Brain registers itself with a manifest. Concretely:
When Brain boots, all @register_workflow decorators populate a registry. The aggregator reads this registry to build its routing prompt — it sees every workflow’s name, description, and examples and uses them to classify incoming messages. Adding a new workflow is a single-file change. A new .py file under backend/app/vinmake/workflows/ with @register_workflow is all it takes. The aggregator picks it up automatically. No aggregator changes, no UI changes, no contract changes (unless the workflow calls a new external service). This is what makes Brain composable. The first vertical is RFQ → quotation. The second might be supplier communications. The third might be production tracking. Each one adds workflows to the registry; nothing else changes.

How this runs on deer-flow

deer-flow’s harness provides four things that make this pattern fit naturally:
  1. LangGraph runtime. The aggregator is a small LangGraph state machine — classify → route → invoke → return. Each registered workflow is a tool the aggregator can call. State transitions are observable in LangGraph Studio.
  2. Multi-thread + conversation state. deer-flow already manages per-user, per-thread state. Conversation context for the aggregator’s clarification questions lives in the same store deer-flow uses for chat threads.
  3. Skills + tools system. deer-flow’s skill registration mechanism IS the workflow registry. We register workflows as skills with manifests; deer-flow tracks them, makes them available to the aggregator’s tool surface, and exposes them via slash commands automatically.
  4. Tracing. Every aggregator routing decision and every workflow invocation produces a trace through Langfuse (or LangSmith during early development). When a routing is wrong, the trace shows exactly what the aggregator saw, what it decided, and why.
The point isn’t that this pattern is unique to deer-flow — you could build it on Claude Agent SDK or LangGraph alone. It’s that deer-flow already provides every primitive needed. Brain’s aggregator is “wire up deer-flow’s existing pieces in this specific shape,” not “build a new orchestration layer from scratch.”

Conversation state for multi-turn

The aggregator handles multi-turn conversations. Example:
Mai: “draft a BOM” Brain: “Which style would you like a BOM for?” Mai: “Jaspal J26WGW153” Brain: [routes to /generate-bom with style_ref=J26WGW153]
Conversation state lives in Brain’s Supabase, scoped to (user_id, thread_id), with a 24-hour TTL. The aggregator reads the previous turn when classifying the current message — so “Jaspal J26WGW153” is correctly interpreted in the context of the just-asked clarification. State is small: the last few user messages plus the aggregator’s open clarification, if any. It doesn’t hold business records (drafts, tasks, approvals) — those live in their own tables with their own lifecycles.

Routing accuracy is a measured metric

The aggregator’s classification accuracy is a top-level quality metric. We measure it the same way we measure BOM agent quality:
  • Routing eval set. ~50 user-message-to-intended-workflow pairs, labeled by Mai and Zean. Examples like “draft a quote for Jaspal” → generate-quotation, “what’s happening with J26WGW153” → ask-pipeline-status, “create a new client from this contract” → create-client.
  • Target: ≥95% routing accuracy on the eval set after Phase 2. Lower thresholds for ambiguous edge cases that route to the clarification flow.
  • CI integration. Every change to the aggregator’s prompt or to a workflow’s manifest triggers the routing eval. Regressions block the merge.
See Observability for the full eval discipline.

When routing is uncertain

When the aggregator’s confidence is below a threshold, it doesn’t guess — it asks. Two patterns:
  • Multi-choice clarification (preferred for v0): “Did you mean to draft a quote, or look up an existing one?”
  • Free-form clarification (v1): “I’m not sure what you mean by ‘check’ here — could you describe what you’d like to do?”
Multi-choice is preferred because it’s measurable (the user picks an option that maps to a workflow), debuggable (the eval set can include ambiguous messages and verify the right options are offered), and faster for users (one click vs. typing). The threshold tunes over time. Too high and the aggregator asks too often; too low and it guesses wrong. Both failure modes show up in the correction log.

Permission gates live at workflow level, not routing

The aggregator routes to whatever workflow matches the user’s message. The workflow itself then checks permissions. If the user doesn’t have permission (e.g., Mai tries to create a new client and her account lacks the right), the workflow:
  1. Prepares the draft as if it would proceed
  2. Routes the approval to a manager via the Inbox
  3. Responds to Mai: “Draft prepared. Sent to Zean for approval.”
This keeps the aggregator stateless and role-agnostic. It also matches the principles — permission gates happen before action, but action prep is allowed.

What this means for new workflows

Once the aggregator and registry pattern exist, adding a new workflow looks like this:
  1. Write a new file in backend/app/vinmake/workflows/<workflow_name>.py
  2. Define the workflow class with @register_workflow, parameters Pydantic schema, examples, and the run() method
  3. Add a workflow page to this documentation under workflows/
  4. Add 3-5 routing eval examples to the routing eval set
  5. Add the workflow’s own eval set if it produces AI-generated output (BOMs, drafts, structured queries)
No aggregator changes. No UI changes. No contract changes unless the workflow needs a new external service. This is the composability story for Brain: each new workflow is a single-file addition that the rest of the system absorbs automatically. First vertical (RFQ→quotation) ships in Phase 1-3. Second vertical (supplier comms, production tracking, etc.) is mostly new workflow files; the platform doesn’t change.
  • Implementation Tiers — why the aggregator is Tier 3 and workflows are Tier 1/2/3 mixed
  • Runtime — deer-flow’s primitives that the aggregator and registry use
  • UI Structure — the chat / slash / approval entry points described from the UI side
  • Principles — why scope is enforced at the aggregator, not at chat input
  • Observability — how routing accuracy is measured