13-ai-condorwave: W6filled10 citations

Condor Tools Registry — the Descriptor-Driven Clinical Tool Catalog

Audiences: developer, internal, investor

Condor Tools Registry — the Descriptor-Driven Clinical Tool Catalog

tools-registry/ is the clinical surface the model plans over. It lives at ddx-api/src/ai/condor/tools-registry/ (194 files) and holds the descriptor-driven business tools — patient, appointment, medication, note, billing, plus the card/list read tools that render into the Condor UI. Every tool has a declared identity in descriptors/, is constructed through one factory (create-condor-tool.ts), and is exposed to a run through exactly one immutable snapshot resolved by one singleton service. The runtime that consumes it is documented in Condor Engine; the ratified subsystem contract is ddx-api/src/ai/condor/TOOLS.md.

Purpose

An agentic clinical assistant is only as safe as its tool surface. A tool here is not just a function the LLM may call — it is a declared capability carrying its control plane, its policy class, its side-effect class, its retry safety, its human-in-the-loop tier, and its owning team. Those declarations are what let the system answer, mechanically: may the planner select this? does it mutate? may it be retried? must a human approve it? which team is on call?

tools-registry/ is the home of that declaration layer, and the counterpart to the generic Mastra-native toolkit in mastra/tools/ (filesystem, exec, search, vision, PDF, session-entity primitives). The split between the two is a ratified, mechanically-guarded contract — see the two-registry contract.

The catalog seam — one singleton, one snapshot per run

condor-tool-catalog.service.ts is the single application-scoped (@Injectable() singleton) owner of the derived descriptor catalog. It exposes read accessors over the descriptor array and exactly one resolution entry point.

MemberRole
descriptorsthe full, unfiltered descriptor catalog (CONDOR_TOOL_DESCRIPTORS)
toolIdsevery declared tool id, derived from the descriptor array
descriptor(id)one descriptor by id, via a prebuilt DESCRIPTOR_BY_ID map
activeBusinessToolIds()the plan-eligible business-plane ids for the running profile + env
isPlanEligible(toolId)the per-id plan-eligibility predicate
resolveSnapshot(input)the ONE per-run candidate resolution (condor-tool-catalog.service.ts:126)

The architectural rule this service exists to enforce: the candidate set is resolved once and then passed as data. resolveSnapshot delegates to CondorToolRagService.resolveCandidateSet, registry-verifies and de-dupes the result, and returns an immutable ToolSelectionSnapshot (tool-selection-snapshot.ts:26) carrying toolIds, source, candidateSetId, ragDescriptions and activationReasons. Prompt assembly, plan validation, workflow compilation, observability and persistence all read that one value object — none of them re-resolve. The service is the singleton; the per-run state is a value object, never a per-request service (that shape is deliberate: a per-request Nest provider here is a barrel-DI-cycle hazard).

Two details in resolveSnapshot are load-bearing and easy to break:

  • Forced tools lead the ordered set. Policy additions (attachment-reader tools when files are present; the clinical intake-candidate floor on a multi-entity intake) are prepended ahead of the RAG hits, not appended (condor-tool-catalog.service.ts:149-167). The planner's tool-block degradation ladder sheds candidates from the tail (lowest RAG rank) first, so a tail-appended floor would be the first thing shed — re-introducing exactly the dropped-vitals defect the floor exists to prevent. Leading the set also gives those tools description and param-synopsis priority.
  • RAG provenance survives key normalization. A candidate id is remapped to its canonical registry key (visitRecordingCardvisit.recording.card, and underscore/kebab variants), and the activationReasons entry is remapped with it. Without that remap the tool stays surfaced but silently loses its rag-retrieved reason, disabling downstream intent/retrieval guards.

Boot is a hard gate, not a warning: the constructor resolves the active catalog profile once and calls assertNoDemoOnlyInProduction — under the clinical-production profile, a non-production descriptor being active is a boot failure, not a logged anomaly.

Rendering diagram…

The descriptor-driven tool model

descriptors/tool-descriptor.ts defines one ToolDescriptor shape whose fields are each an independent, queryable axis. Keeping them separate is the point — collapsing any two of them has, historically, produced a real defect.

AxisValuesWhat it governs
planebusiness · planner-control · agent-control · internalplan eligibility — only business descriptors may be planner workflow steps
profilesclinical-production · workspace · demo · testwhich environments the tool is active in
policyClassread · mutate · …approval authority — the single input the tool-policy engine consults
sideEffectnone · idempotent · mutation · external-send · execobservability + idempotency classification, finer-grained than policyClass
idempotencynot-needed · durable-requiredwhether the operation needs a durable idempotency key
retryClassnone (default) · transient-idempotenttransport retry opt-in
hitlTierhigh · lowthe second axis of the mutate approval gate
ownercondor-clinical · condor-docgen · condor-workspace · condor-demo · condor-coreaccountable team / on-call
metadataReffactory · rag-metadata · display-registryprovenance of the tool's physical RAG/display metadata

Three of these deserve emphasis:

hitlTier is compile-time mandatory for mutating tools. HITL_TIERS is ['high','low'] (tool-descriptor.ts:123): high always suspends for Approve/Reject — even in an autonomous operator chat, reserved for the highest-stakes clinical writes (prescription, medication, signed document, billing charge); low is autonomous-allow / supervised-suspend, where the human who typed the request is the approval, but an ambient or supervised dispatch still suspends. That requirement is enforced by the type system, not by review: MutateRequiresHitlTier (tool-descriptor.ts:229) is a conditional type distributed per-element by defineDescriptors, so a policyClass: 'mutate' row that omits hitlTier is a tsc error. A plain array-level satisfies cannot do this — the conditional would collapse to the non-mutate branch for the whole array — which is why the helper threads each row through its own generic.

retryClass and idempotency are deliberately different axes. idempotency: 'durable-required' marks a tool that needs a durable key because it mutates; keying retry off it would retry precisely the dangerous set. Retry safety is declared separately, and even then the declaration cannot win: resolveRetryPolicy returns none unconditionally for any mutating tool (tool-retry-policy.ts). The reasoning is a clinical-safety failure mode — a mutating call whose write succeeded but whose response timed out is indistinguishable at the client from one that never landed, so retrying it writes twice: a duplicate prescription, a duplicate billing entry. 28 tool files declare mutating: true.

owner is derived, not hand-typed. deriveToolOwner computes the owning team from each tool's plane + module, so ownership has one auditable derivation rather than a hundred hand-written strings that drift out of date.

A parallel derived view, CondorToolMetadataV2 via deriveToolMetadataV2, is a pure projection of those same descriptor fields — it introduces no second source of truth, and metadataRef is retained as provenance so the eventual physical relocation of the RAG-metadata blocks stays traceable.

The factory — one construction path, one policy authority

Every business tool is built through createCondorTool (create-condor-tool.ts:212). The factory is where declaration becomes behavior, and it composes two distinct layers around the raw tool body:

  • Inner layer — inside the run: callback handed to withToolLogging. This is the single choke point that owns the emit lifecycle (emitToolStarted/emitToolCompleted/emitToolFailed) and where the observer chain composes, so an observer sees exactly ONE invoke and ONE result per logical tool call.
  • Outer layer — around the function returned by wrapExecuteWithExecutionContract. This is where retry sits. The placement is the whole reason a retried call emits tool.invoked/tool.completed exactly once; retrying at the inner layer would emit once per attempt and corrupt every downstream consumer, including the health endpoint's failure counters.

The factory also resolves one approval authority. When a descriptor declares a policyClass, the tool-policy engine is the sole approval decision-maker and the per-tool requireApproval callback is bypassed (create-condor-tool.ts:232-248) — a stale callback can no longer force a suspend on a read tool or silently downgrade a mutation. The factory then stamps policyClass, policyDomain and hitlTier onto the constructed tool object (create-condor-tool.ts:498-531), falling back to descriptor coverage when the tool declares mutating: true without an explicit class. That stamping closes a real security hole: a tool whose policyClass never reached the tool object read as 'read' at the suspend gate, so 28 mutating tools were ungated.

Two guarantees the factory preserves, and which must never be collapsed into each other:

Failure shapeBehavior
Soft failure — the tool returns {ok:false}reported as ok:false, returned as-is, never converted into a throw. Model-repairable.
Hard failure — the tool throwsreported to onError and re-thrown unchanged, never swallowed. Hard-fails the run by design.

Observer failures are a third, separate class: every observer hook is individually try/caught, a throwing observer is swallowed, counted, and warned once — instrumentation may never reach the tool path.

Grouping and gating — what the planner is even allowed to see

Four independent filters narrow the declared catalog down to what a given run may plan over.

  1. Plane filter. Only business descriptors are plan-eligible. The planner-control tools (tool search, referent resolution) and the agent-control meta-tools are structurally excluded from business candidate disclosure — indexing them as business candidates was a named audit finding.
  2. Profile filter. resolveActiveCatalogProfile resolves the process's profile once at boot; activeDescriptors / activeBusinessToolIds / isPlanEligibleToolId (descriptors/catalog-profile.ts) apply it per id. A demo-only tool simply does not exist under clinical-production — and if one is active there, boot fails.
  3. RAG candidate resolution. Of the active set, only what the tool-RAG retrieves for this prompt reaches the planner. This is a genuine gate, not a ranking nicety: a tool absent from the candidate set can never be selected, no matter how well the plan is written. Hardening a tool's ragKeywords / ragDescription is therefore the fix when the right tool is never picked.
  4. Forced floors and policy additions. Where retrieval is not trustworthy enough (multi-entity clinical intake; a turn carrying attachments), an explicit floor forces ids into scope and, per the ordering rule above, places them where the degradation ladder cannot shed them.

How tools surface to the LLM

Tools are disclosed progressively, under a hard byte budget — the planner never sees the full business-tool schema set at once. (The live business-tool count is authoritative in ddx-api/scripts/audit/condor-tools-registry-direction-audit.sh, not a literal here — it drifts on every tool add/remove; as of this writing the guard baseline reads 76 while the tree holds 78, a known repo inconsistency flagged for the condor-tools-uplift owner, NOT a docs error.)

  • Candidate manifests. meta-tools.ts renders one compact line per candidate via buildCandidateManifest, bounded by MANIFEST_MAX_BYTES = 300 and MANIFEST_MIN_BYTES = 120 (meta-tools.ts:117), with truncateBytes as the single truncation routine the whole subsystem shares. The manifest's reported bytes includes any appended fragment, so budget accounting stays honest instead of under-reporting.
  • Control-plane meta-tools. search_tools and load_tool (META_TOOL_IDS, meta-tools.ts:43) let an agent-loop widen its own surface on demand. load_tool is hash-gated: it fails on a manifest that was never surfaced, so an agent cannot conjure a tool it was not shown. findMetaToolPlanViolations rejects a plan that tries to route through them — they are agent-control, not workflow steps.
  • Injectable fragments. Two optional declarative slots inject at two different phases: instructionFragment (behavioral — how to use the tool) is appended to the candidate manifest at planner phase and costs bytes for candidates only; stylingFragment (presentational — how the result should be presented) is appended at formatter phase and costs the planner nothing. Both are capped at 120 bytes, and a styling fragment carrying a raw colour literal throws rather than being quietly truncated into something that merely looks fine.
  • Card rendering + localization. The read/card tools emit UI-block card specs rather than prose. All rendered chrome (titles, kv keys, empty states, FHIR status labels, severity enums) routes through one localization seam split across card-i18n-base.ts, card-i18n-status.ts, card-i18n-extras.ts, card-i18n-hitl.ts and card-i18n.ts. Clinical data — drug names, ICD codes, patient names, dates — is never localized, and LLM-facing text (ragDescription, ragKeywords, output instructions) stays English.

The two-registry contract

ddx-api/src/ai/condor/TOOLS.md is the ratified contract (2026-07-25) and the authority for anything in this tree. Its core rule is a one-directional dependency:

tools-registry/ MAY import from mastra/tools/. mastra/tools/ MUST NOT import from tools-registry/.

mastra/tools/ sits below tools-registry/ and knows nothing about descriptors, planes, profiles or the business catalog. The _-prefixed modules under mastra/tools/ (_with-tool-logging.ts, _rag-metadata.ts) are shared infrastructure, not tools — and they are the sanctioned crossing point.

The rule is measured, not aspirational. condor-tools-registry-direction-audit.sh treats the forward count as a baseline (drift reported, new forward imports legal) and the reverse count as a guard (any hit fails). Its check (d) is a clinical-safety gate of its own: tool-retry-policy.ts keeps a local mirror of the mutating predicate (importing the canonical one would cycle, since the factory imports the retry module), and the audit asserts the predicate expression appears in both files — a one-sided edit would silently enable retry for a tool that actually mutates.

Where a new tool belongs follows from what it is, not who calls it: a clinical or HMS domain operation a clinician would recognise as a task goes in tools-registry/ with a descriptor; a generic agent capability with no HMS semantics goes in mastra/tools/ as a plain Mastra tool; and a new cross-folder seam belongs in mastra/tools/ with a _ prefix — authoring it under tools-registry/ and importing it upward is exactly the inversion the guard forbids.

Adding, removing, moving or renaming a file under tools-registry/tools/ changes the audited tool count and fails the audit by design. That is intentional friction: the clinical tool surface should not grow by accident.

Audience notes

Developer. Start at TOOLS.md — it is the contract, and §5–§8 cover the observer/retry seam, post-tool processors, injectable fragments, and the operator surface plus failure taxonomy. Then read descriptors/tool-descriptor.ts for the axes and create-condor-tool.ts for how a declaration becomes behavior. Two gate blind spots to know before you trust a green run: the build tsconfig excludes spec files (report both a default npx tsc --noEmit and the -p tsconfig.build.json run), and the test runner exits 0 on zero path matches (pass the fail-on-no-tests flag). Run ddx-api/scripts/audit/condor-tools-registry-direction-audit.sh after any edit in either folder. For anything that renders — a card, a new UI-block-emitting tool — type-check green is not sufficient evidence; the render path has to be verified in the browser, because arg-binding bugs and render-gate omissions are both invisible to tsc.

Internal. The registry is where clinical safety is declared rather than remembered. Three properties carry most of that weight: mutating tools can never be retried (a timed-out write that already landed must not be repeated); a mutating descriptor cannot exist without a declared HITL tier, because the compiler refuses it; and a non-production tool active under the clinical profile fails boot rather than logging a warning. Ownership is derived per tool, so on-call routing for a misbehaving capability is a lookup, not an investigation. The recurring lesson from this subsystem's defect history is that the dangerous failures are the silent ones — a policy class that never reached the tool object, a floor tool shed by the degradation ladder, an activation reason lost to key normalization. Each of those now has a mechanical guard, and that is the pattern to extend rather than to work around.

Investor. The clinical tool catalog is the productized surface of the platform: each tool is a discrete clinical capability — book an appointment, record a note, prescribe, list allergies, produce a document — that the assistant can select and execute against the live hospital system. Two things make that commercially defensible. First, the surface is declared and audited rather than ad hoc: every capability states whether it changes patient data, whether a human must approve it, and which team owns it, and violations of those declarations fail the build or fail boot rather than reaching a clinician. Second, the surface is extensible without re-architecture — adding a capability means adding a declaration and a tool, not rewiring the engine, and the deliberate friction on that count is what keeps a clinical catalog from growing carelessly.

  • Condor Engine — the runtime that resolves a snapshot and compiles a plan over these tools.
  • Condor Sandbox & Builder — the workspace sandbox the condor-workspace-owned tools operate in, plus the builder's operator console.
  • TUCAN RAG Tools — the legacy engine's tool-retrieval approach, the ancestor of Condor's candidate-set gate.
  • AI Medical Tools — the clinical tool surface from the product/capability side.