Condor Engine — Ported Agentic Runtime (Tucan→Condor Migration)
Audiences: developer, internal, investor
Condor is the second agentic engine in ddx-api, running side-by-side with the legacy TUCAN estate during an in-progress migration. It lives at
ddx-api/src/ai/condor/(~358 files, 5 modules, ~19 controllers) and is wired into the app viaai-group.module.ts. Condor is a full port of the standaloneddx-condor-platformagentic runtime onto HMS conventions (HMS auth chain, no FHIR coupling in the transport,ddx_api_aisession ownership). This shard covers the runtime: the flow router, chat/AG-UI transport, session lifecycle, and the Mastra plan-compiler. The sandbox + low-code builder subsystems are in Condor Sandbox & Builder.
Why Condor (the migration frame)
Condor is the successor to TUCAN. Rather than a greenfield rewrite (an abandoned
earlier plan), the strategy is a port + per-flow co-existence: bring the
Condor engine in whole, run it beside TUCAN, and cut individual flows over one at
a time via a routing table, then decommission TUCAN once traffic is zero. The
single seam that makes this safe is the AgenticFlowRouter.
The co-existence linchpin — AgenticFlowRouter
agentic-flow-router.service.ts resolves a flowKey (the dispatch selector at
the chat funnel) to exactly one engine: 'tucan' or 'condor'.
- Single-target: a flowKey reaches exactly one engine, never both.
- Safe default (Phases 0–4): an unmapped/unknown flowKey →
'tucan'. The legacy engine is the fallback so an unconfigured flow can never silently route to the in-port engine. Phase 5 flipsdefaultEngineto'condor'by config alone (full-adoption bias). - Runtime-flippable (no redeploy): the table is config-seeded (env) AND
mutable in-process via
setFlowEngine/clearOverride— per-flow cutover and flip-back need no deploy. - Data-only: the router imports nothing from either engine tree (no DI on either dispatcher), so it cannot create a Nest barrel DI cycle and stays free of FHIR / SSE-contract coupling.
DIAGNOSIS_ENGINE_FLOW_KEY = 'diagnosis-engine'is bound to'condor'at bootstrap (CondorModule.onModuleInitviasetFlowEngine) — the deterministic clinical flow.condor.controller'sbuildRuntimeturnsmedicalModeON when the dispatched flowKey matches this key, unlocking clinical guardrails + clinical role-templates. The key lives at the routing seam so the module binding and the medicalMode gate share ONE source of truth.
Transport — CondorController (@Controller('ai/condor/sessions'))
Two primary entry endpoints plus lifecycle controls (condor.controller.ts):
POST :id/messages— the chat dispatch funnel.body.dispatchselects the workflow def (the routing seam). Streams viapipeUIMessageStreamToResponse(AI-SDK UI Message Stream) — NEVER@Sse()— so the bytes are the AI-SDKuseChatpassthrough contract.GET runs/:runId/agui/live— a dedicated raw RFC 8895 AG-UI live-bridge with a 3-tier resolver: tier 0 durable store (AguiEventStoreseq-cursor replay+poll) → tier 1 live registry (AiRunRegistry.claimStream) → tier 2 span replay. 404 only when BOTH the durable run row and the registry entry are absent.- Lifecycle:
POST :sessionId/runs/:runId/stop,GET :id/active-run,GET :id/stream,POST :sessionId/messages/:userMessageId/resubmit,POST :id/resume,POST :id/approve.
Controller invariants (from plans/tucan-condor-port/_invariants.md, cited in
the controller header):
/agui/liveserves raw RFC 8895 frames — DELIBERATELY NOT wrapped in@ddx/sse-contractSseEnvelope/SSEChannels(wrapping would break the 3-tier resolver's per-tierid:cursor).@ddx/sse-contractremains the HMS global bus.- NO
@RbacExemptanywhere — the full HMS auth chain (globalGatewayAuthGuard- per-method
JwtAuthGuard/RolesGuard+ ownership + tenant scope) is mandatory. This removes the Tucan AI/voice anti-pattern.
- per-method
- Zero
FHIR_CLIENT/ResourceFacade/ HAPI imports in the controller — it touches onlyddx_api_ai(session ownership) + the ported stream/store/registry.
Session lifecycle — session/ai-sessions-lifecycle.controller.ts
Full AI-session CRUD under the condor session base: create/list/get/delete/patch
sessions, delete a message, fetch attachment content, history, usage-rollup,
runs-summary, events, and pinned-cards (list/create/reorder/delete). This is the
persisted conversation state that dispatch reads (dispatch is persisted
session.agentType/flowKey state, NOT LLM-chosen).
Plan compilation — the Mastra orchestrator
mastra/orchestrator/compiler.ts is a pure-function compiler that turns a
validated WorkflowPlan (plan.schema.ts) into an ad-hoc Mastra workflow via
graph-based lowering:
- Infers execution-level structure from per-step
dependsOn(declaration-order fallback for back-compat) and emits Mastra.then()/.parallel([…]). - Steps sharing an upstream set become siblings in
.parallel; a downstream step depending on >1 sibling gets an auto-inserted synthesizer step that merges sibling outputs. - Step kinds → Mastra primitives:
tool-call→createStepcallingctx.tools[toolId].execute;agent-call→createStepcallingctx.roleAgents[role].stream(prompt)(nevergenerate— preserves stream events for the UI);final→ terminal step delegating toctx.roleAgents.formatter. - Invariants: NO
suspend()(v0 has no HITL gates); pure — same plan+ctx → same workflow shape, never cached across turns.
Supporting: decompose/diagnosis-decomposer.ts (breaks a clinical query into a
plan), intent/intent-taxonomy.registry.ts (+ diagnosis-intent.taxonomy.ts)
for query classification, tool-rag/condor-tool-rag.service.ts for tool
selection (domain-scoped, multi-entity patterns).
Known runtime-port hazard
A bulk port compiles tsc-green yet can carry layered RUNTIME bugs (prefix
doubling, guard-model gaps, unmigrated tables, slug-vs-UUID, env-name drift,
transport-path mismatch, @Optional SWC quirks, router default). Any Condor
change should be MCP-Chrome-verified end-to-end, not trusted on typecheck alone.
Related Topics
- Condor Sandbox & Builder — the Docker workspace sandbox + low-code agentic-definition builder subsystems.
- TUCAN Orchestrator — the legacy engine Condor runs beside and eventually replaces.
- TUCAN Dispatcher — persisted-state dispatch (shared concept: flowKey/agentType is state, not LLM-chosen).
- AI Chat Sessions — conversation state Condor sessions build on.