Condor Diagnosis Engine — Deterministic Route Dispatch, Decompose, Plan Compilation
Audiences: developer, internal
This topic covers the agentic engine view of diagnosis: how a clinical turn is routed deterministically to Condor, decomposed into parallel sub-goals, and compiled into an executable Mastra workflow. It is the runtime counterpart to Diagnosis Engine, which owns the clinical knowledge domain (DiseaseCards, Curator, document generation). The two do not overlap: that topic is what the clinical knowledge is, this one is how a turn becomes a plan and runs.
Purpose
A clinical diagnosis turn must not be freehanded by the LLM. Condor binds it to a
deterministic route (flowKey = 'diagnosis-engine'), which unlocks clinical
guardrails and a fixed workflow template set. From there a pure decompose
pass expands multi-goal turns into parallel sub-goal clusters, and the
orchestrator compiler lowers the resulting WorkflowPlan into a Mastra workflow
graph. Every stage is deterministic and re-validated — the LLM contributes the
plan content, never the plan shape.
Deterministic route dispatch — the sessions/new-only firing
DIAGNOSIS_ENGINE_FLOW_KEY = 'diagnosis-engine' is declared at the routing seam
(agentic-flow-router.service.ts:48) and bound to the Condor engine at boot
(condor.module.ts:585, setFlowEngine(…, 'condor', DIAGNOSIS_FLOW_CONFIG)).
The load-bearing trap: the clinical template fires only when the session was
created with that flowKey. ai-sessions-lifecycle.service.ts:138 gates on it:
const boundToDiagnosis = input.flowKey === DIAGNOSIS_ENGINE_FLOW_KEY;
… ...(boundToDiagnosis ? { medicalMode: true } : {}),
| Entry path | flowKey supplied | medicalMode at create | Clinical template |
|---|---|---|---|
sessions/new with dispatch.id = diagnosis-engine | yes | true (persisted) | fires |
generic /condor/chat "New chat" | no | null | does not fire |
Consequence: linking a patient or an anamnesis from a generic chat session yields
no symptom-driven routing — the deterministic diagnosis workflow was never
bound. A per-turn fallback exists (condor.controller.ts:732 flips medicalMode
when the dispatched flowKey matches, and medical-mode-derivation.ts:82 treats
the per-turn flowKey as "source 2"), but it is a legacy compensator, not the
route. Always dispatch through sessions/new.
Pipeline — decomposer → plan.schema → compiler
1. Decompose — a pure dependsOn re-baser, not a planner
Despite the directory name, mastra/decompose/diagnosis-decomposer.ts does not
author a diagnosis. It is a pure, deterministic, mirror-only transform
(:18-29 hard contract: no DI, no fetch, no LLM, no I/O):
| Rule | Behavior | Site |
|---|---|---|
| Multi-goal gate | >1 distinct flowConfig.workflowTemplateIds ⇒ multi-goal. clinicalIntent alone does not qualify | :78-82 |
| Wasted-cost guard | single-goal turn returns the plan referentially unchanged | :117-119 |
| Fan-in anchor | requires exactly one terminal final step; 0 → skip, >1 → skip + needsReview | :121-133 |
| Cluster selection | non-final leaves that depend on no other non-final leaf (graph sources) | :147-150 |
| Transform | re-base cluster to dependsOn: []; union the cluster into final.dependsOn | :168-180 |
| Mirror-or-mark | a shape not expressible as identical-dependsOn siblings is returned unchanged + flagged, never re-invented | :23-26 |
The transform emits only the sibling shape the compiler already lowers to
.parallel() — so it required zero compiler change.
2. plan.schema — the validation contract
mastra/orchestrator/plan.schema.ts owns workflowPlanSchema /
WORKFLOW_PLAN_STEP_KINDS plus the structural gates the diagnosis path leans on:
validatePlan (:101), assertValidStepKinds (:128),
findOffListPlanTools (:237), findNonDependencyInputRefs (:272), and the
mutation-honesty pair findProseClaimedMutations (:389) /
planProseClaimsMutation (:436).
Defence-in-depth: run-knobs.ts:234 re-parses the expanded plan against
WorkflowPlanSchema and falls back to the original plan on a miss rather
than risk a compile throw.
⚠ Research-intent guard. plan-mutation-rewrite.ts once hijacked a research
plan (a beta-blocker Q&A) into medication.manage{op:create} via prose-claimed
mutation rewriting. The rewrite is now gated on plan.intent !== 'research' — a
diagnosis research turn must never be rewritten into a mutation.
3. Compiler — graph lowering
mastra/orchestrator/compiler.ts is a pure-function compiler: it infers
execution levels from per-step dependsOn, emits Mastra .then() /
.parallel([…]), and auto-inserts a synthesizer when a downstream step depends
on more than one sibling. Step kinds map to primitives (tool-call →
ctx.tools[toolId].execute; agent-call → ctx.roleAgents[role].stream(…),
never generate; final → the formatter agent). normalize-plan.ts conditions
the plan before compilation. Full compiler detail lives in
Condor Engine.
Web consumption — diagnosis frames are stream-only
The diagnosis cards do not arrive as message.parts data-parts. All
diagnosis.* frames come over the AG-UI live/replay stream as
Custom{ name: 'diagnosis.<suffix>' }, read from the CondorProvider
aguiEventsByRun map by two pure selectors:
| Selector | Frames |
|---|---|
select-diagnosis-ranked.ts | latest diagnosis.ranked for a run (never reaches into another run) |
select-diagnosis-ops.ts | 6 op frames (search/find/compare/score/matcher/odds) + 5 outcome frames (diagnosis-outcome/treatment-plan/anamnesis/procedure/findings) |
Frame-name law: names are read verbatim from the frozen
DIAGNOSIS_OP_FRAME_NAMES / DIAGNOSIS_OUTCOME_FRAME_NAMES maps in
@ddx/sse-contract — diagnosis.<suffix>, never condor.<suffix> and never
condor.condor-*. Hardcoding a name in the consumer would drift from the emitter.
How this differs from the clinical Diagnosis Engine topic
| Axis | This topic (06-ai-tucan) | Diagnosis Engine (04-clinical) |
|---|---|---|
| Concern | turn routing, plan shape, execution graph | clinical knowledge + document output |
| Code home | ddx-api/src/ai/condor/** | ddx-api/src/diagnosis-engine/**, ddx-api/src/documents/** |
| Core artifacts | WorkflowPlan, step graph, AG-UI frames | DiseaseCard, KnowledgeMutation, generated documents |
| Determinism claim | route + decompose + compile are deterministic/pure | Curator is LLM-driven with auditable rollback |
| Audience | developer, internal | doctor, clinical-buyer, developer, investor |
Related Topics
- Condor Engine — the runtime this engine plugs into (flow router, transport, compiler).
- Diagnosis Engine — the clinical knowledge domain (DiseaseCards, Curator, documents).
- Condor RAG Tool Search — supplies the domain-scoped candidate tool set a diagnosis plan draws from.
- Condor Tools Registry — the catalog whose tools compiled
tool-callsteps invoke.