When the Retriever Has to Decide: Reusable Multi-Agent Architecture, Configurable Gaps, and Deterministic Gating
By Dr. Amit Puri · October 2026
In continuation to: Information Retrieval Part I, Part II: When Similarity Isn't Enough!, and Recap & Closing.
Research write-up: When the Retriever Has to Decide: Ontology-Governed Deterministic Gating for Portable Multi-Agent Clinical Decision Support — architecture, the functional usage of the ontology (ontology-engineering lifecycle, construct-to-runtime contract, worked traces), evaluation method, results, code coverage and open items for the current state of this repository.
- An agent is a retriever that has to decide. Every one of the 7 classical/RAG retrieval gaps still applies. Agents introduce four new failure modes: guessing instead of asking (Gap 8), safety rules relegated to probabilistic prompts (Gap 9), un-evaluated execution trajectories (Gap 10), and tool output treated as unscoped, trusted context (Gap 11).
- An ontology's job in an agent is to define what a term means, and when to stop. In laboratory medicine, terms like
"Hb","Calcium","CSF panel", and"Troponin"each conceal multiple distinct assays with different clinical units, reference ranges, or owning laboratory departments. - Same clinical logic, three agent frameworks. The deterministic
SafetyGateEngine, Pydantic models, YAML config, and A2A contracts are framework-independent and centralized in the sharedontogatepackage (shared/). Three complete agent framework implementations demonstrate portability:
| Framework / Component | Folder | Model | Production Target | Status |
|---|---|---|---|---|
Shared Core (ontogate) |
shared/ |
Pure Python / Deterministic Gate / FastMCP | Framework-Agnostic Core (ontogate v1.0.0) |
✅ Complete |
| Google ADK ≥2.11.0 | google-adk-agents/ |
Gemini 3.8 Flash | Google Cloud Agent Platform / Cloud Run | ✅ Complete |
| AWS Strands Agents SDK + Bedrock AgentCore | strands-agents/ |
Anthropic Claude Opus 5.5 | AWS Bedrock AgentCore / ECS Fargate | ✅ Complete |
| Microsoft Agent Framework (MAF) / Azure AI Foundry | agent-framework/ |
gpt-6.1-sol via Azure AI Foundry / OpenAI |
Azure Container Apps + AI Foundry | ✅ Complete |
| Multi-Cloud Deployment Automation | deployment/ |
Multi-Cloud Agent Fleet | GCP, AWS & Azure (Bash & PowerShell) | ✅ Complete |
Tip
The objective is not to find a universally "perfect" agent architecture, but to pinpoint what failed, why it failed, and the smallest intervention that fixes it.
multiagent-retrieval-gaps/
│
├── shared/ # ── Shared Core: Framework-Agnostic Ontology & Gate ──
│ ├── pyproject.toml # ontogate v1.0.0 package specification (pip installable)
│ ├── pytest.ini # Test configuration (pythonpath = . ontogate)
│ ├── ontogate/ # Shared ontogate library (pure Python, zero cloud SDK deps)
│ │ ├── models.py # Pydantic schemas (TrustTier, ConceptDefinition, etc.)
│ │ ├── config.py # YAML pack loader & OntologyRegistry
│ │ ├── paths.py # Repository root & knowledge artifact discovery
│ │ ├── parsing.py # Ontology-aware clinician input parsing
│ │ ├── clarify.py # Deterministic clinical clarification generation
│ │ ├── resolver.py # LOINC concept resolution & semantic mapping
│ │ ├── units.py # Canonical UCUM unit checks & conversions
│ │ ├── concept_graph.py # Concept graph traversal, look-alikes & protocols
│ │ ├── okf_index.py # Progressive disclosure catalog index (OKF §7.1)
│ │ ├── okf_writer.py # Knowledge writeback & log.md maintenance
│ │ ├── attestation.py # Deterministic numeric range attestation (OKF §5.4)
│ │ ├── catalog.py # Model catalog loader (config/models.yaml)
│ │ ├── tools.py # Canonical clinical tool contract implementations
│ │ ├── mcp_server.py # FastMCP server (the 7 canonical clinical tools)
│ │ ├── validate.py # Ontology integrity & schema validation rules
│ │ ├── ports.py # Runtime ports (audit, telemetry, stores)
│ │ ├── release.py # Knowledge release build (`ontogate build`) & verification
│ │ ├── roles.py # Role specs (agents/<role>.yaml)
│ │ ├── skills.py # Skills render, lint, role checks, lock, archives, per-role resolution
│ │ ├── skill_refs.py # Skill references rendered from the ontology (templates/skills/)
│ │ ├── cloud_stores.py # Content-addressed skill stores: GCS, S3, Azure Blob, local
│ │ ├── rdf_export.py # SKOS/OWL/PROV export, SHACL validation, OWL 2 RL, bundle
│ │ ├── cli.py # `ontogate` command line (validate, skills, rdf, build)
│ │ └── detectors/ # 7 pluggable deterministic gap detectors + SafetyGateEngine
│ │ ├── engine.py # SafetyGateEngine (deterministic gate, fail-closed)
│ │ ├── ambiguity.py # AmbiguityDetector (Gap 8)
│ │ ├── unit_mismatch.py # UnitMismatchDetector (Gap 2)
│ │ ├── missing_qualifier.py # MissingQualifierDetector (Gap 5)
│ │ ├── missing_unit.py # MissingUnitDetector
│ │ ├── range_collision.py # RangeCollisionDetector (Look-Alikes)
│ │ ├── specimen_sequence.py # SpecimenSequenceDetector (Gap 11)
│ │ └── population_context.py # PopulationContextDetector
│ └── tests/ # gate, CQs, validator, skills, RDF/SHACL, stores, packaging
│
├── ontology/ # ── Clinical Knowledge Packs & Upper Ontology ─────────
│ ├── core/ # Upper ontology schema, meta-models & SHACL shapes
│ │ ├── core.yaml # Upper ontology (units, dimensions, relations, facets)
│ │ └── shapes.ttl # SHACL: intervals, look-alikes, designations, sequencing, provenance, roles/skills/tools
│ ├── domains/laboratory_medicine/ # Laboratory medicine primary domain pack
│ │ ├── domain.yaml # Domain specification, version & author metadata
│ │ ├── vocabulary.yaml # Synonyms & clinical aliases mapped to canonical IRIs
│ │ ├── analytes.yaml # Primary analytes (Hb, Calcium, Troponin, CSF)
│ │ ├── observables.yaml # Observable definitions, LOINC codes, UCUM units
│ │ ├── facets.yaml # Clinical qualifiers (total, ionized, arterial, venous)
│ │ ├── intervals.yaml # Reference & critical intervals across populations
│ │ ├── panels.yaml # Multi-analyte panels (CSF emergency workup, etc.)
│ │ ├── protocols.yaml # Clinical protocols & emergency escalation rules
│ │ └── designations.yaml # Display names & terminology bindings
│ └── domains/cardiology/ # Cardiology second-domain reuse proof (P13.8)
│ ├── domain.yaml # Cardiology domain specification
│ ├── vocabulary.yaml # Controlled departments & specimens
│ ├── analytes.yaml # Troponin I, Troponin T, BNP, hsCRP
│ ├── observables.yaml # LOINC concepts & confusable_with symmetric pairs
│ ├── facets.yaml # Population facet declarations
│ ├── intervals.yaml # Reference intervals (unvalidated demonstration)
│ ├── protocols.yaml # Grounded ESC & ACC/AHA clinical guidelines
│ └── designations.yaml # Look-alike designations (cTn ambiguity)
│
├── skills/ # ── Multi-Agent Runtime & Domain Skills (agentskills.io)
│ ├── runtime/ # 6 role-scoped clinical agent skills
│ │ ├── clarification-dialogue/ # SKILL.md for clarification coordinator role
│ │ ├── clinical-triage/ # SKILL.md for triage orchestrator role
│ │ ├── grounded-synthesis/ # SKILL.md for clinical synthesizer role
│ │ ├── lab-term-resolution/ # SKILL.md for ontology resolver role
│ │ ├── protocol-retrieval/ # SKILL.md for protocol retriever role
│ │ └── specimen-sequencing/ # SKILL.md for panel coordinator role
│ ├── domains/ # One orientation skill per domain pack
│ │ ├── domain-laboratory-medicine/ # references/ rendered from the laboratory medicine pack
│ │ └── domain-cardiology/ # references/ rendered from the cardiology pack
│ └── skills.lock.json # Content + archive SHA-256 per skill, and the bundle hash
│
├── agents/ # ── Declarative Role Specifications ───────────────────
│ ├── clarification_coordinator.yaml # Tools, skills, model tier & base prompt
│ ├── clinical_synthesizer.yaml # Synthesis role specification
│ ├── ontology_resolver.yaml # Term resolution role specification
│ ├── panel_coordinator.yaml # Panel sequencing role specification
│ ├── protocol_retriever.yaml # Protocol retrieval role specification
│ ├── safety_guard.yaml # Code node: uses_model false, no skills
│ └── triage_orchestrator.yaml # Triage coordination role specification
│
├── knowledge/ # ── Knowledge Base Audit & Change Tracking ────────────
│ └── log.md # Append-only TBox ontology mutation log (OKF §7.5)
│
├── config/ # Declarative runtime configuration
│ ├── models.yaml # Model catalog mapping framework roles to LLMs
│ └── scenarios/ # Clinical evaluation scenarios & overlays
│ ├── scenario_a_hemoglobin.yaml # Hb ambiguity & unit mismatch (Gap 2 & 8)
│ ├── scenario_b_csf_panel.yaml # CSF emergency panel tube ordering (Gap 11)
│ ├── scenario_c_calcium_collision.yaml # Calcium look-alike range collision
│ ├── scenario_d_troponin.yaml # Dynamic extension: Troponin I vs T (zero-code)
│ ├── scenario_e_glucose_specimen.yaml # Glucose specimen facet overlay
│ └── scenario_f_cardiology_troponin.yaml # Cardiology pack CQs (second-domain reuse)
│
├── deployment/ # ── Multi-Cloud Production Deployment Automation ─────
│ ├── README.md # Multi-cloud deployment guide (GCP, AWS, Azure)
│ ├── IMPLEMENTATION_PLAN.md # Architecture, security & provisioning specification
│ ├── teardown.py # Ledger-driven teardown: deletes what a deploy created (all clouds)
│ ├── gcloud-scripts/ # Google Cloud Agent Platform / Cloud Run
│ │ ├── config.env # Project ID, region (europe-west1), service config
│ │ ├── deploy.sh / deploy.ps1 # Release build + skill publish, Cloud Run, optional Agent Runtime
│ │ ├── agent_runtime.py # ADK AdkApp → Vertex AI Agent Runtime (--dry-run offline check)
│ │ ├── destroy.sh / destroy.ps1 # Clean resource teardown
│ │ └── test-service.sh / .ps1 # End-to-end integration & health verification
│ ├── aws-scripts/ # AWS Bedrock AgentCore / ECS Fargate
│ │ ├── config.env # AWS region (us-east-1), AgentCore config
│ │ ├── deploy.sh / deploy.ps1 # Automated Bedrock AgentCore & IAM provisioning
│ │ ├── destroy.sh / destroy.ps1 # Clean resource teardown
│ │ └── test-service.sh / .ps1 # End-to-end integration & health verification
│ └── az-scripts/ # Azure Container Apps + AI Foundry
│ │ ├── config.env # Azure region (eastus), AI Foundry project config
│ │ ├── deploy.sh / deploy.ps1 # Keyless Managed Identity + ACA deployment
│ │ ├── destroy.sh / destroy.ps1 # Clean resource teardown
│ │ └── test-service.sh / .ps1 # End-to-end integration & health verification
│
├── google-adk-agents/ # ── Framework 1: Google ADK 2.0 ──────────────────────
│ ├── README.md # Framework 1 documentation, OKF v0.2 & Harness details
│ ├── Dockerfile # Cloud Run / GKE production container (port 8000)
│ ├── pytest.ini # pythonpath = . src ../shared
│ ├── src/
│ │ ├── .env # GEMINI_API_KEY (gitignored)
│ │ ├── env.example # Template
│ │ ├── a2a/contracts.py # A2AMessage, AgentRole, A2AAction, A2ATaskState
│ │ ├── agents/ # 6 specialized ADK agent roles (OKF trust-aware synthesis)
│ │ ├── harness/ # Agent Harness — 4-step MCP continuous loop
│ │ │ ├── agent.py # Step 1: ClinicalADKHarness continuous reasoning loop
│ │ │ ├── mcp_server.py # Step 2: FastMCP server (delegates to ontogate.mcp_server)
│ │ │ ├── mcp_client.py # Step 2: Gemini function_call ↔ MCP bridge
│ │ │ ├── context.py # Step 3: Token budgeting & OKF progressive disclosure
│ │ │ ├── session.py # Step 3: Turn-by-turn conversation session state
│ │ │ ├── server.py # Step 4: FastAPI ASGI server (REST + healthz + MCP tools)
│ │ │ └── console.py # Step 4: Interactive CLI debugging console
│ │ ├── orchestration/a2a_orchestrator.py # ADK Workflow with OKF pre-scoping
│ │ ├── runner.py # Scenarios A–D runner (supports --harness flag)
│ │ ├── workflow.py # ADK workflow definition & safety guard node
│ │ ├── tools.py # Canonical tool bindings (delegates to ontogate.tools)
│ │ └── main.py # CLI entrypoint
│ ├── requirements.txt # google-adk, google-genai, mcp, fastapi, uvicorn
│ ├── load_env.sh
│ └── tests/ # gate, OKF, harness, role-skill & Agent Runtime packaging tests
│
├── strands-agents/ # ── Framework 2: AWS Strands Agents SDK ──────────────
│ ├── README.md # Framework 2 documentation & AgentCore memory details
│ ├── Dockerfile # ECS / App Runner production container (port 8000)
│ ├── pytest.ini # pythonpath = . src ../shared
│ ├── requirements.txt
│ ├── .env.example # Bedrock / AgentCore template
│ ├── src/
│ │ ├── .env # ANTHROPIC_API_KEY + ANTHROPIC_MODEL_ID (gitignored)
│ │ ├── env.example
│ │ ├── server.py # Production FastAPI ASGI server (REST + healthz + MCP tools)
│ │ ├── models/provider.py # AnthropicModel (claude-opus-5-5) or BedrockModel
│ │ ├── a2a/contracts.py # Shared A2A contracts (same schema as ADK)
│ │ ├── agents/ # 6 Strands agent roles
│ │ ├── memory/session.py # Bedrock AgentCore managed session / local fallback
│ │ ├── orchestration/ # Strands workflow coordinator
│ │ ├── tools/ # Strands @tool wrappers over ontogate.tools
│ │ ├── runner.py # Scenarios A–D runner
│ │ └── main.py # CLI entrypoint
│ └── tests/ # deterministic offline, invariant & role-skill tests
│
├── agent-framework/ # ── Framework 3: Microsoft Agent Framework (MAF) ────
│ ├── README.md # Framework 3 documentation & declarative workflow details
│ ├── Dockerfile # Azure Container Apps production container (port 8000)
│ ├── pytest.ini # pythonpath = . src ../shared
│ ├── requirements.txt
│ ├── load_env.sh
│ ├── declarative-agents/ # kind: Prompt YAML, GENERATED from agents/<role>.yaml
│ │ ├── triage_orchestrator.yaml # parse → resolve → gate
│ │ ├── ontology_resolver.yaml # LOINC concept resolution
│ │ ├── protocol_retriever.yaml # URI-bound protocol retrieval
│ │ ├── clinical_synthesizer.yaml # Grounded synthesis (only after PROCEED)
│ │ ├── clarification_coordinator.yaml # HITL clarification
│ │ └── panel_coordinator.yaml # Specimen sequencing (the safety guard is code, not a prompt agent)
│ ├── declarative-workflows/
│ │ └── clinical_decision_workflow.yaml # WorkflowFactory + InvokeAgent conditional routing
│ ├── src/
│ │ ├── .env # Azure / OpenAI credentials (gitignored)
│ │ ├── env.example
│ │ ├── a2a/contracts.py # Shared A2A contracts (parity with ADK & Strands)
│ │ ├── harness/ # MAF Agent Harness
│ │ │ ├── agent.py # ClinicalHarnessAgent + create_clinical_harness_agent()
│ │ │ ├── session.py # HarnessSession — per-turn history & state
│ │ │ ├── server.py # Production FastAPI ASGI server (REST + healthz + MCP tools)
│ │ │ ├── providers.py # ClinicalTodoProvider, ClinicalModeProvider, SafetyGateApprovalPolicy
│ │ │ └── console.py # Terminal UX — /todos /mode /scenario /status
│ │ ├── tools/ # Plain Python tool wrappers over ontogate.tools
│ │ ├── declarative.py # Role spec → declarative YAML generator (--check in CI)
│ │ ├── skills_loader.py # Role-scoped SkillsProvider (context provider)
│ │ ├── models/provider.py # Credential auto-detection (Foundry, Azure OpenAI, OpenAI)
│ │ ├── orchestration/workflow_runner.py # Live MAF workflow + offline fast path
│ │ ├── runner.py # Scenarios A–D runner
│ │ └── main.py # CLI entrypoint (--harness, --harness-scenario)
│ └── tests/ # safety invariants, harness, declarative drift & skills tests
│
├── conformance/ # Cross-cloud parity: every CQ through ADK, Strands & MAF wrappers
├── docs/ # Research paper (RESEARCH_PAPER.md), blueprints, OKF specs, memory models & decision models tracking
├── run-all-tests.sh # Integrity, skills, SHACL, generated-file drift, all suites, conformance
├── run-all.sh # Runs all 4 clinical scenarios across all 3 frameworks
└── .gitignore
Important
ontology/ and config/ provide the single source of truth for all clinical knowledge and scenarios. All three framework
implementations read from the same ontology/domains/ and config/scenarios/ YAML files through the shared
ontogate library (shared/) — no Python code changes are needed to add a new gap scenario.
| Gap | Level | Addressed In | Implementation |
|---|---|---|---|
| Gap 2: Unit Mismatch | Agent Loop | shared/ontogate/detectors/unit_mismatch.py |
Standalone detector that explicitly rejects units not valid for any matched concept (e.g. mg/dL for Hemoglobin). |
| Gap 5: Missing Qualifier | Agent Loop | shared/ontogate/detectors/missing_qualifier.py |
Flags numeric results in look-alike assay families (Calcium, Troponin) when no qualifier is supplied. |
| Gap 8: Ambiguity & Silent Guessing | Agent Loop | shared/ontogate/detectors/ambiguity.py |
Returns explicit AMBIGUOUS, UNIT_MISMATCH, or NOT_FOUND statuses instead of silent guesses. |
| Gap 9: Safety Rule in Prompt | Agent Loop | shared/ontogate/detectors/engine.py |
Deterministic routing SafetyGateEngine and safety_guard_node; fails closed on uncertainty. Human-in-the-loop pause. |
| Gap 10: Trajectory Never Evaluated | Agent Loop | shared/tests/, */tests/ |
Pure-code pytest suites and cross-cloud conformance verifying invariants without LLM calls. |
| Gap 11: Tool Output Trusted, Unscoped | Agent Loop | shared/ontogate/detectors/specimen_sequence.py |
Enforces CSF panel tube sequencing and department scope boundaries. |
| Range Collisions (Look-Alikes) | Clinical Safety | shared/ontogate/detectors/range_collision.py |
Evaluates numeric values against look-alike assay families to prevent lethal classification errors. |
| Dynamic Gap Extensibility | Framework | config/scenarios/ |
New gaps declared entirely in YAML — zero changes to Python code. |
The shared/ directory contains ontogate (ontogate v1.0.0), a pure-Python, zero-cloud-dependency package that implements the deterministic core of the entire multi-agent system. All three agent frameworks (Google ADK, AWS Strands, and Microsoft Agent Framework) import and delegate to ontogate.
flowchart LR
subgraph SharedCore["shared/ontogate (Pure Python — No LLM — No Cloud SDK)"]
direction TB
Models["Pydantic Models<br/>• ConceptDefinition<br/>• ReferenceInterval<br/>• AttestedComputation"]
Parser["ontogate.parsing<br/>• Ontology-Aware Parser"]
Resolver["ontogate.resolver<br/>• LOINC Resolution<br/>• UCUM Unit Normalization"]
Gate["SafetyGateEngine<br/>• 7 Pluggable Detectors<br/>• Fail-Closed Deterministic Code"]
Attest["ontogate.attestation<br/>• Attested Range Evaluation<br/>• [Attested ✓] Badge"]
Tools["ontogate.tools<br/>• Canonical Tool Logic"]
FastMCP["ontogate.mcp_server<br/>• Canonical FastMCP Server"]
end
SharedCore --> ADK["Google ADK 2.0<br/>(google-adk-agents/)"]
SharedCore --> Strands["AWS Strands SDK<br/>(strands-agents/)"]
SharedCore --> MAF["Microsoft Agent Framework<br/>(agent-framework/)"]
-
Deterministic Safety Gating (Gap 9):
SafetyGateEngineexecutes 7 pluggable gap detectors in pure Python with zero LLM calls and fail-closed routing (onlyRESOLVEDproceeds;AMBIGUOUS,UNIT_MISMATCH,RANGE_COLLISION,NOT_FOUNDall route toCLARIFY). -
Canonical Data Models & Protocols: Strict Pydantic v2 schemas (
ConceptDefinition,Observable,Facet,ReferenceInterval,ProtocolDefinition,AttestedComputation). -
Ontology Pack Loading & Dynamic Scenarios: Loads upper ontology (
ontology/core/core.yaml), domain packs (ontology/domains/laboratory_medicine/*.yaml), and runtime scenario overlays (config/scenarios/*.yaml). -
Attested Numeric Computation (OKF §5.4): Evaluates patient lab values against age/sex/qualifier reference intervals deterministically and stamps the
[Attested ✓]compliance badge. -
Canonical FastMCP Tool Server: Exposes the 7 canonical clinical decision-support tools via FastMCP:
parse_clinician_inputresolve_lab_termevaluate_safety_gatefetch_grounded_protocolattest_computationbuild_clarification_promptpanel_workup
classify_lookalikesis a read-only helper inontogate.tools(not an MCP tool). -
Cross-Framework Consumption:
- Local Dev / Testing: Each framework includes
shared/onPYTHONPATH(e.g.pytest.iniwithpythonpath = . src ../sharedand automatic discovery viaontogate.paths). - Production Containers: Dockerfiles install the package directly:
pip install "/tmp/shared[mcp]".
- Local Dev / Testing: Each framework includes
cd shared
python -m pytest tests/ -v
# 235 passed (pure Python, 0 LLM calls; the RDF/SHACL tests need `pip install -e ".[dev]"`)Everything an agent knows or is told to do ships as one knowledge release, built and verified offline before any deployment:
python -m ontogate validate # integrity rules over the pack and every scenario overlay
python -m ontogate skills build # render skill references from the ontology, lint, lock, archive
python -m ontogate rdf validate # SHACL over the ontology + the role/skill/tool meta-graph
python -m ontogate build # all of the above + OWL 2 RL → dist/release/manifest.json
python conformance/run.py # same decisions on ADK, Strands and MAF for every CQ| Layer | Source of truth | Checked by |
|---|---|---|
| Ontology | ontology/core, ontology/domains/<pack> |
integrity rules, SHACL (ontology/core/shapes.ttl), 35 competency questions |
| Formal view | generated: SKOS / SKOS-XL / OWL 2 / PROV-O (ontogate.rdf_export) |
SHACL; OWL 2 RL materialises symmetric and inverse relations |
| Role specs | agents/<role>.yaml (tools, skills, domain_skills, model tier, instruction) |
role ↔ skill ↔ tool consistency (lint + SHACL RoleSkillShape) |
| Runtime skills | skills/runtime/*, skills/domains/domain-<pack> |
agentskills.io format, clinical lint (no numbers, no routing, no scripts), lock drift |
| Skill references | rendered from the ontology (designations, facets, trust caveats, panels) | ontogate skills check fails if a reference differs from the ontology |
| Release | skills.lock.json (content + archive hashes, bundle hash), dist/release/manifest.json |
runtimes verify hashes at startup and fail closed |
Runtime skills per framework. Each role loads exactly its skills through the
framework's native loader, built from the same hash-verified directories:
ADK SkillToolset (script tool filtered out), Strands AgentSkills (local
Skill instances, strict=True) and MAF SkillsProvider (no script
extensions). safety_guard is a code node with no model and no skills. A role
with domain_skills: true loads the domain skill of whichever pack is served,
so the cardiology pack needed no role-spec change.
Skill stores. ontogate skills publish gs://… | s3://… | azblob://…
uploads content-addressed archives (<name>/<archive_sha256>.zip); setting
ONTOGATE_SKILL_STORE makes a runtime materialise and hash-verify them at
startup instead of using the copies baked into the image.
Conformance. conformance/run.py runs every competency question (both
packs) through each framework's own tool wrappers in a separate process and
fails on any difference from the canonical ontogate decision — status, route,
resolved URI, candidates, readings, attestation and panel order.
flowchart TD
Clinician([Clinician / Client Application]) -->|A2A Task Request| Triage[Triage Orchestrator Agent]
subgraph MultiAgent_Fleet["Google ADK Multi-Agent Fleet (A2A Protocol)"]
direction TB
Triage -->|A2A: PARSE_REQUEST| OntologyAgent[Ontology Resolver Agent]
OntologyAgent -->|Resolution State| SafetyGuard[Safety Guard Agent<br/><i>Deterministic Code Gate</i>]
subgraph Gap_Detectors["SafetyGateEngine — 6 Pluggable Detectors"]
AmbiguityDet["AmbiguityDetector (Gap 8)"]
UnitMismatchDet["UnitMismatchDetector (Gap 2)"]
MissingQualDet["MissingQualifierDetector (Gap 5)"]
MissingUnitDet["MissingUnitDetector"]
RangeCollisionDet["RangeCollisionDetector (Look-Alikes)"]
SpecimenSeqDet["SpecimenSequenceDetector (Gap 11)"]
end
SafetyGuard -.-> Gap_Detectors
SafetyGuard -- "status == RESOLVED (PROCEED)" --> ProtocolAgent[Protocol Retriever Agent<br/><i>Canonical URI Grounding</i>]
ProtocolAgent --> AttestGate[Synthesis Gate Node<br/><i>Attestation Verification & OKF Audit</i>]
AttestGate -- "Attested (PROCEED)" --> SynthesisAgent[Clinical Synthesizer Agent<br/><i>Single-Turn Gemini 3.8 Flash</i>]
AttestGate -- "Unattested / Out-of-Spec (CLARIFY)" --> ClarifyAgent
SafetyGuard -- "Uncertain / Collision (CLARIFY)" --> ClarifyAgent[Clarification Coordinator Agent<br/><i>Human-in-the-Loop HITL</i>]
end
SynthesisAgent --> Deliver([Deliver Grounded Clinical Interpretation])
ClarifyAgent --> RequestInput([adk_request_input: Prompt Clinician with Diagnosed Gap])
RequestInput -.->|Clinician Clarification| Triage
# 1. Install dependencies
cd google-adk-agents
pip install -r requirements.txt
# 2. Configure API key
cp src/env.example src/.env
# Edit src/.env: set GEMINI_API_KEY="..."
# 3. Run all scenarios (live Gemini 3.8 Flash, ADK workflow)
python src/main.py
# 4. Offline / stub mode (no API key needed)
python src/main.py --offline
# 5. Run all scenarios via Agent Harness (FastMCP + continuous loop)
python src/runner.py --harness
python src/runner.py --harness --offline
# 6. Interactive Agent Harness CLI console
python -m src.harness.console --offline
# clinician> Hb 13.5 | g/dL → [PROCEED] loinc:718-7 | Ref: 13.8–17.2 g/dL [Attested ✓]
# clinician> Calcium 4.8 mg/dL → [CLARIFY] RANGE_COLLISION: Total vs. Ionized
# 7. Start production REST server (FastAPI / ASGI)
uvicorn src.harness.server:app --host 0.0.0.0 --port 8000
# POST /api/v1/query GET /api/v1/mcp/tools GET /healthz GET /readyz
# 8. Run tests (193 deterministic safety invariants, OKF & harness tests)
python -m pytest tests/ -vTip
See google-adk-agents/README.md for detailed documentation of the Google ADK 2.0 multi-agent implementation and Open Knowledge Format (OKF v0.2) enhancements.
flowchart TD
Clinician([Clinician / Client Application]) -->|A2A Task Request| Triage[Triage Orchestrator Agent]
subgraph Strands_Fleet["AWS Strands Multi-Agent Fleet"]
direction TB
Triage -->|A2A: PARSE_REQUEST| OntologyAgent[Ontology Resolver Agent]
OntologyAgent --> SafetyGuard[Safety Guard Agent<br/><i>Deterministic Code Gate</i>]
subgraph Gap_Detectors["SafetyGateEngine — 5 Pluggable Detectors"]
AmbiguityDet["AmbiguityDetector (Gap 8)"]
UnitMismatchDet["UnitMismatchDetector (Gap 2)"]
MissingQualDet["MissingQualifierDetector (Gap 5)"]
RangeCollisionDet["RangeCollisionDetector (Look-Alikes)"]
SpecimenSeqDet["SpecimenSequenceDetector (Gap 11)"]
end
SafetyGuard -.-> Gap_Detectors
SafetyGuard -- "PROCEED" --> ProtocolAgent[Protocol Retriever Agent]
ProtocolAgent --> SynthesisAgent[Clinical Synthesizer Agent<br/><i>Claude Opus 5.5</i>]
SafetyGuard -- "CLARIFY" --> ClarifyAgent[Clarification Coordinator Agent<br/><i>HITL RequestInput</i>]
subgraph AgentCore["Amazon Bedrock AgentCore"]
MemMgr["Session / Memory Manager<br/><i>AgentCoreMemorySessionManager</i>"]
end
SynthesisAgent <-.-> MemMgr
Triage <-.-> MemMgr
end
SynthesisAgent --> Deliver([Deliver Grounded Clinical Interpretation])
ClarifyAgent --> RequestInput([Prompt Clinician with Diagnosed Gap])
| Mode | Model ID | Trigger |
|---|---|---|
| Local / Direct Anthropic | claude-opus-5-5 |
ANTHROPIC_API_KEY set in src/.env |
| AWS Bedrock | global.anthropic.claude-opus-5-5 |
AWS_REGION + credentials, ANTHROPIC_API_KEY unset |
| Offline / Mock | MockBedrockModel |
STRANDS_OFFLINE_MODE=true or no credentials |
cd strands-agents
# 1. Install dependencies
pip install -r requirements.txt
# 2. Verify src/.env (key is shared from google-adk-agents):
# ANTHROPIC_API_KEY="sk-ant-..."
# ANTHROPIC_MODEL_ID="claude-opus-5-5"
cat src/.env
# 3. Run all scenarios (live Claude Opus 5.5 via direct Anthropic API)
python src/main.py
# 4. Offline mode (no API key needed, deterministic mock)
python src/main.py --offline
# 5. Run tests (54 deterministic offline invariants, ~7.8 s)
python -m pytest tests/ -v
# 6. Start production REST server (FastAPI / ASGI on port 8000)
uvicorn src.server:app --host 0.0.0.0 --port 8000
# POST /api/v1/query GET /api/v1/mcp/tools GET /healthz GET /readyzTip
See strands-agents/README.md for detailed documentation of the AWS Strands Agents SDK implementation, supervisor orchestration, and Amazon Bedrock AgentCore memory management.
export AWS_REGION=us-east-1
export AWS_PROFILE=your-profile
export BEDROCK_MODEL_ID=global.anthropic.claude-opus-5-5
unset ANTHROPIC_API_KEY # let Bedrock auto-discovery take over
python src/main.py# Add to strands-agents/src/.env:
# AGENTCORE_MEMORY_ID=mem-clinical-decision-support
# AGENTCORE_SESSION_ID=session-default
# AGENTCORE_ACTOR_ID=clinician-001
# STRANDS_OFFLINE_MODE=false
python src/main.pyNote
ANTHROPIC_API_KEY in src/.env takes precedence over AWS auto-discovery.
Unset it to switch to Bedrock. The Strands SDK logs a reminder when both are present.
flowchart TD
Clinician([Clinician / Client Application]) -->|A2A Task Request| Workflow[clinical_decision_workflow.yaml<br/><i>WorkflowFactory / InvokeAgent</i>]
subgraph MAF_Fleet["Microsoft Agent Framework (MAF) Declarative Fleet"]
direction TB
Workflow -->|InvokeAgent| Triage["triage_orchestrator.yaml<br/><i>kind: Prompt (All 5 Tools Bound)</i>"]
Triage -->|parse_clinician_input| ParseTool["parse_tool.py (Plain Python)"]
Triage -->|resolve_ontology| OntologyTool["ontology_tool.py (LOINC Resolution)"]
Triage -->|run_safety_gate| SafetyGateTool["safety_gate_tool.py<br/><i>Deterministic Code Gate (No LLM)</i>"]
subgraph Gap_Detectors["SafetyGateEngine — 5 Pluggable Detectors"]
AmbiguityDet["AmbiguityDetector (Gap 8)"]
UnitMismatchDet["UnitMismatchDetector (Gap 2)"]
MissingQualDet["MissingQualifierDetector (Gap 5)"]
RangeCollisionDet["RangeCollisionDetector (Look-Alikes)"]
SpecimenSeqDet["SpecimenSequenceDetector (Gap 11)"]
end
SafetyGateTool -.-> Gap_Detectors
Triage -- "route == PROCEED (RESOLVED)" --> ProtocolAgent["protocol_retriever.yaml<br/><i>fetch_protocol()</i>"]
ProtocolAgent --> SynthesisAgent["clinical_synthesizer.yaml<br/><i>Grounded LLM Synthesis (gpt-6.1-sol)</i>"]
Triage -- "route == CLARIFY (Uncertain / Collision)" --> ClarifyAgent["clarification_coordinator.yaml<br/><i>HITL Pause / RequestInput</i>"]
end
SynthesisAgent --> Deliver([Deliver Grounded Clinical Interpretation])
ClarifyAgent --> RequestInput([Prompt Clinician with Diagnosed Gap])
RequestInput -.->|Clinician Clarification| Workflow
- Declarative Agents (
kind: PromptYAML): 6 declarative agents defined entirely indeclarative-agents/*.yaml, separating prompt instructions and tool declarations from execution logic. - Declarative Workflows (
WorkflowFactory): Structured indeclarative-workflows/clinical_decision_workflow.yamlusing MAF conditional routing (InvokeAgent+If/Then/Else). - Plain Python Tools (Zero Decorator Overhead): Plain Python functions bound via YAML
bindings:without mandatory@toolor@kernel_functiondecorators. - Fail-Closed Code Safety Gate:
SafetyGateEngineruns pure deterministic Python before any synthesis model invocation (closing Gap 9). - Namespace-Safe Core Shim: Pre-registers shared ADK core modules via
importlib.utilinsys.modules, achieving zero-duplication code reuse across frameworks without namespace collisions.
| Priority | Mode | Environment Variable | Target Environment |
|---|---|---|---|
| 1 | Azure AI Foundry | AZURE_AI_FOUNDRY_PROJECT_ENDPOINT |
Production cloud deployment (gpt-6.1-sol) |
| 2 | Azure OpenAI Direct | AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_DEPLOYMENT |
Direct Azure tenant resource |
| 3 | OpenAI Agents SDK | OPENAI_API_KEY |
Local development & rapid prototyping |
| 4 | Deterministic Offline | MAF_OFFLINE_MODE=true |
Fast invariant CI/CD (0 API calls, ~1 s) |
# 1. Install dependencies
cd agent-framework
pip install -r requirements.txt
# 2. Configure credentials (or use included local key in src/.env)
cp src/env.example src/.env
# 3. Run all scenarios (live gpt-6.1-sol)
python src/main.py
# 4. Offline mode (no API key needed, deterministic mock)
python src/main.py --offline
# 5. Run a single scenario via WorkflowFactory runner
python src/main.py --scenario A # Hb 13.5 — ambiguity, unit mismatch, resolution
python src/main.py --scenario B # CSF emergency panel — tube ordering (Gap 11)
python src/main.py --scenario C # Calcium 4.8 — look-alike collision
python src/main.py --scenario D # Troponin I vs T — dynamic extension
# 6. Launch the interactive MAF Agent Harness console
python src/main.py --harness
# clinician> Hb 13.5 | g/dL → [PROCEED] loinc:718-7 | Ref: 13.8–17.2 g/dL
# clinician> /todos → 4-step diagnostic workflow checklist
# clinician> /mode plan → switch to PLAN mode (analysis only, no protocol fetch)
# clinician> /scenario A → run Scenario A through the harness
# clinician> /status → session ID, mode, registered tools
# 7. Run a scenario non-interactively through the Harness
python src/main.py --harness-scenario A
python src/main.py --harness-scenario all
# 8. Start production REST server (FastAPI / ASGI on port 8000)
uvicorn src.harness.server:app --host 0.0.0.0 --port 8000
# POST /api/v1/query GET /api/v1/mcp/tools GET /healthz GET /readyz
# 9. Run test suite (44 deterministic safety invariants, ~1.2 s)
python -m pytest tests/ -vAll four scenarios are declared as declarative YAML under config/scenarios/ — shared across all frameworks.
- Problem:
"Hb 13.5"without unit → CBC Hemoglobin (13.5 g/dL= borderline normal) or Glycated Hemoglobin (13.5%= severe diabetic emergency). - Resolution:
AmbiguityDetector→RequestInputasking for unit.g/dL→ LOINC718-7;mg/dL→UNIT_MISMATCH.
- Problem: Single Lumbar Puncture must be partitioned across 3 departments without cross-contamination.
- Resolution:
SpecimenSequenceDetectorpre-scopes by tube: Tube 1 (Biochem), Tube 2 (Micro), Tube 3 (Haematology).
- Problem: Total Calcium at 4.8 =
CRITICAL_LOW(emergency IV calcium); Ionized Calcium at 4.8 =NORMAL. - Resolution:
RangeCollisionDetector→RANGE_COLLISION→ forced clarification before medication.
- Problem:
"Troponin 15"— cTnI inng/mL(critical > 0.40) vs hs-cTnT inng/L(critical > 52). Wrong unit = false alarm catheterization or missed MI. - Resolution: Declared entirely in
scenario_d_troponin.yaml— zero Python code. Full multi-agent A2A fleet.
Pure-code deterministic pytest suites verify safety invariants without LLM calls:
# Shared Core (ontogate) — 235 tests (gate, CQs, validator, skills, roles, RDF/SHACL, stores, AgentCards, A2A)
cd shared && python -m pytest tests/ -v
# Google ADK ≥2.11.0 — 206 tests
cd google-adk-agents && python -m pytest tests/ -v
# AWS Strands SDK — 77 tests
cd strands-agents && python -m pytest tests/ -v
# Microsoft Agent Framework (MAF) — 50 tests
cd agent-framework && python -m pytest tests/ -v
# Cross-cloud conformance — 35 CQs × 3 frameworks, AgentCards, live runner per server
python -m pytest conformance
# Deployment teardown and answer graders
python -m pytest deployment/tests
python -m pytest evalsFrom the repository root, run the entire multi-cloud agent fleet and test suites using the top-level bash scripts:
# 1. Integrity, skills, SHACL, generated-file drift, every suite and cross-cloud conformance
sh run-all-tests.sh
# 2. Run end-to-end multi-agent clinical scenarios A–D across all 3 frameworks
sh run-all.sh| Framework | Orchestration & Synthesis Mode | Scenario Highlights & Behavioral Verification |
|---|---|---|
| Google ADK 2.0 | Live Gemini 3.8 Flash (gemini-3.8-flash) |
• Scenario A: Turn 1 (Hb 13.5 no unit) pauses deterministically with status=AMBIGUOUS (route=CLARIFY). Turn 2 (Hb 13.5 | g/dL) resolves to loinc:718-7, fetching reference range 13.8–17.2 g/dL with [Attested ✓] badge. Turn 3 (Hb 13.5 | mg/dL) triggers status=UNIT_MISMATCH (route=CLARIFY).• Scenario B: CSF Emergency Panel strictly enforces tube sequence: Tube 1 (Biochemistry), Tube 2 (Microbiology), Tube 3 (Hematology). • Scenario C: Calcium 4.8 mg/dL triggers RANGE_COLLISION (CLARIFY); qualifying as total yields CRITICAL_LOW, whereas ionized yields NORMAL.• Scenario D: Troponin I vs T dynamic YAML extension resolves Troponin 15 | ng/L to loinc:6598-7 (hs-cTnT) with panic limits (> 52 ng/L). |
| AWS Strands SDK + Bedrock AgentCore | Anthropic Claude on AWS Bedrock (claude-opus-5-5) |
• Full A2A supervisor orchestration with Bedrock AgentCore memory session manager. • Resilient fallback to deterministic offline pipeline if cloud API quotas or rate limits are reached. • Identical scenario outcomes: AMBIGUOUS on Turn 1, RESOLVED on Turn 2, and UNIT_MISMATCH on Turn 3. |
| Microsoft Agent Framework (MAF) | Live gpt-6.1-sol via OpenAI Agents SDK / Azure AI Foundry |
• Declarative kind: Prompt agents and WorkflowFactory conditional routing.• Identical clinical decisions: intercepts look-alike collisions (Calcium 4.8) and rejects incompatible units ( mg/dL for Troponin). |
All three implementations (ADK, Strands, and MAF) exchange the same structured A2AMessage envelopes:
class A2AMessage(BaseModel):
message_id: str
trace_id: str
sender: AgentRole # triage_orchestrator, ontology_resolver_agent, …
recipient: AgentRole
action: A2AAction # PARSE_REQUEST, RESOLVE_CONCEPT, VALIDATE_SAFETY, …
timestamp: float
payload: Dict[str, Any]
status: Optional[ResolutionStatus] = NoneThe proposed production architecture integrates the Offline Clinical Knowledge Ingestion & Governance Plane (docs/healthcare_multi_agent_architecture.md), the Online Serving Plane with Deterministic Safety Gating, and the 14-Part Memory Substrate (docs/ai-agent-memory-architecture.md).
flowchart TB
subgraph CLIENT["Clinician & Client Layer"]
Clinician["Clinician / EHR Client"]
Gateway["API Gateway / AuthN & AuthZ<br/><i>Social Memory (Roles & ACLs)</i>"]
Clinician <-->|Query / Clarification| Gateway
end
subgraph INGESTION["1. Ingestion Plane (Offline & Human-Governed)"]
direction TB
Sources["Clinical Guidelines, Lab SOPs & Reference Protocols"] --> Parser["Layout-Aware Parsing Service<br/><i>(Textract / Unstructured + Table QA)</i>"]
Parser --> Extractor["Extraction Agent<br/><i>(Schema-Constrained Assertion Proposals)</i>"]
Extractor --> Normalizer["Normalization via Terminology Service<br/><i>(SNOMED CT, LOINC, UCUM, RxNorm)</i>"]
Normalizer --> Curator["Curation Queue<br/><i>(Human Review & Conflict Resolution)</i>"]
Curator --> StagingGraph[("Staging Graph")]
StagingGraph --> EvalGate{"Evaluation Gate<br/><i>(Gold-Set Invariants & Safety Regression)</i>"}
EvalGate -->|Pass & Promote| ReleaseMgr["Release Manager<br/><i>(Stamp Knowledge Release ID)</i>"]
end
subgraph SERVING["2. Serving Plane (Online Multi-Agent Fleet)"]
direction TB
Gateway -->|A2A Task Request| Triage["Triage Orchestrator Agent<br/><i>Working Memory (Context Window & A2A Scratchpad)</i>"]
Triage -->|A2A: PARSE_REQUEST| Ontology["Ontology Resolver Agent<br/><i>Semantic Memory (Controlled Vocabularies)</i>"]
Ontology -->|Resolution State| SafetyGuard["Safety Guard Agent<br/><i>Deterministic Code Gate</i>"]
subgraph GATE_ENGINE["SafetyGateEngine (Procedural Memory: Pluggable Code Invariants)"]
AmbiguityDet["AmbiguityDetector (Gap 8)"]
UnitMismatchDet["UnitMismatchDetector (Gap 2)"]
MissingQualDet["MissingQualifierDetector (Gap 5)"]
RangeCollisionDet["RangeCollisionDetector (Look-Alikes)"]
SpecimenSeqDet["SpecimenSequenceDetector (Gap 11)"]
end
SafetyGuard -.-> GATE_ENGINE
SafetyGuard -->|UNRESOLVED / COLLISION| Clarify["Clarification Coordinator Agent<br/><i>Prospective Memory (Pending HITL Trigger)</i>"]
Clarify -->|RequestInput: Prompt Diagnosed Gap| Clinician
SafetyGuard -->|RESOLVED / PROCEED| Protocol["Protocol Retriever Agent<br/><i>External & Graph Retrieval Grounding</i>"]
Protocol -->|A2A: Grounded Protocol + Spans| Synthesizer["Clinical Synthesizer Agent<br/><i>Parametric Memory (LLM Language Judgement)</i>"]
Synthesizer -->|Draft Answer with Span Citations| Verifier["Multi-Layer Verification Pipeline<br/><i>(Citation Integrity, Entailment Agent, Applicability)</i>"]
Verifier --> Rules["Clinical Rules Service<br/><i>(Deterministic Drug/Dose/Panic Limit Checks)</i>"]
Rules --> Delivery["Deliver Grounded Clinical Interpretation<br/><i>(Stamped with Knowledge Release ID)</i>"]
end
subgraph MEMORY_SUBSTRATE["3. 14-Part Memory & Knowledge Substrate"]
direction TB
subgraph GRAPH_LAYER["Associative, Relational & Spatial Memory"]
PropertyGraph[("Property Graph DB: Neptune / Neo4j<br/>• Concepts, Recommendations, Qualifiers<br/>• Associative: Look-Alike Range Collision Families<br/>• Spatial Memory: Lab Dept Scopes & CSF Tube Order")]
end
subgraph SEARCH_LAYER["External & Retrieval Memory"]
VectorSearch[("OpenSearch: BM25 + Dense Vectors<br/>• Chunk Spans, Document Text & Embeddings")]
end
subgraph RELATIONAL_LAYER["Temporal, Provenance, Meta & Collective Memory"]
RelationalDB[("PostgreSQL: Provenance, Metadata & Governance<br/>• Temporal: Bitemporal Dates & Supersession Edges<br/>• Meta-Memory: Confidence Scores, Provenance & Gap Flags<br/>• Collective: Domain YAML, Release Manifests & Institutional SOPs")]
end
subgraph AUDIT_LAYER["Episodic & Audit Memory"]
AuditLog[("Immutable Append-Only Audit Log<br/>• Episodic Memory: User, Query, Trajectories, Spans & Hashes")]
end
end
%% Ingestion Plane updates Memory Substrate
ReleaseMgr ==>|Publish Graph Model| PropertyGraph
ReleaseMgr ==>|Index Chunks & Vectors| VectorSearch
ReleaseMgr ==>|Record Release & Provenance| RelationalDB
%% Serving Plane interacts with Memory Substrate
Protocol <-->|Traverse Relationships & Topologies| PropertyGraph
Protocol <-->|Hybrid Search Retrieval| VectorSearch
Ontology <-->|Lookup Terminology & Rules| RelationalDB
Rules <-->|Verify Against Provenance & Ranges| RelationalDB
Verifier -->|Append Execution Trajectory| AuditLog
Delivery --> Clinician
Grounding the fourteen memory types from docs/ai-agent-memory-architecture.md in this decision-support system:
| # | Memory Type | Implementation in Decision Support System | Failure Mode Mitigated |
|---|---|---|---|
| 1 | Working (Context) | Single-turn context window + typed A2AMessage payload scratchpads across the fleet. |
Context rot; loss of active lab session constraints. |
| 2 | Semantic | Controlled clinical vocabularies, LOINC mappings, UCUM units, and approved concept definitions (concepts.yaml). |
Semantic drift; inventing non-existent clinical codes. |
| 3 | Episodic | Session event logs, execution trajectories, and historical query resolutions. | Repeating past diagnostic failures or re-asking answered questions. |
| 4 | Procedural | Pluggable SafetyGateEngine detectors, A2A action contracts, skill recipes, and protocol resolution algorithms. |
Blind replay of outdated runbooks or inconsistent gate execution. |
| 5 | External (Retrieval) | OpenSearch hybrid search (BM25 + dense vectors) over document chunks and evidence spans. | Hallucinating clinical literature not present in guidelines. |
| 6 | Parametric | Frozen weights of base foundation models (Gemini 3.8 Flash, Claude Opus 5.5, gpt-6.1-sol via Azure AI Foundry / OpenAI). |
Answering changing clinical thresholds from stale training data. |
| 7 | Prospective | Pending clarification triggers in ClarificationCoordinatorAgent awaiting clinician input (RequestInput), async lab result hooks. |
Unresolved clinical ambiguities left hanging or forgotten. |
| 8 | Spatial | Physical CSF lumbar puncture tube sequencing (Tubes 1–3) and owning laboratory department boundaries (Biochemistry, Microbiology, Haematology). | Specimen cross-contamination; dispatching tests to the wrong lab department. |
| 9 | Temporal | Bitemporal records (valid_from/valid_to vs. recorded_at), explicit [:SUPERSEDES] graph edges, and guideline expiration dates. |
Recommending obsolete or superseded clinical guidelines. |
| 10 | Associative / Relational | Property graph (Neptune/Neo4j) linking Concepts, Recommendations, Qualifiers, Evidence Chunks, and Look-Alike Collision Families (Calcium, Troponin). | Treating numeric values in isolation without cross-assay collision awareness. |
| 11 | Social | Clinician role, department credentials, patient-caregiver relationship, and granular Document ACLs. | Leaking restricted clinical trial data or bypassing role authorization. |
| 12 | Meta-Memory | Confidence scores, span citation verification, gap detection status (AMBIGUOUS, RANGE_COLLISION), and retrievability gating. |
Unearned certainty; guessing when information is incomplete (Gap 8). |
| 13 | Collective / Organizational | Governed config/domains/ YAML knowledge base, institutional SOPs, panic limit definitions, and immutable KnowledgeReleaseID. |
Inconsistent recommendations across clinical teams or facilities. |
| 14 | Sensory (Perceptual) | Short-lived raw multimodal buffers for lab instrument outputs, scanned PDF requisitions, and table OCR. | Prematurely discarding subtle assay flags or raw instrument metadata. |
-
Ingestion Plane (Offline & Human-Governed):
- Guidelines, SOPs, and manufacturer assay package inserts are processed through a layout-aware parsing pipeline.
- An isolated, schema-constrained Extraction Agent proposes candidate assertions linked to exact text spans.
- Assertions are normalized via the Terminology Service (SNOMED CT, LOINC, UCUM, RxNorm).
- Clinical governance reviews high-risk claims and conflicts in the Curation Queue.
- An Evaluation Gate runs gold-set regression invariants; on pass, a
KnowledgeReleaseIDis stamped and published across the graph, search index, and relational store.
-
Serving Plane (Online Multi-Agent Fleet):
- Queries enter through the API Gateway where identity, role, and ACLs are established (Social Memory).
- The Triage Orchestrator parses the clinical request and coordinates the fleet via typed
A2AMessagecontracts. - The Ontology Resolver Agent expands synonyms and maps clinical terms to canonical concepts.
- The Safety Guard Agent runs the deterministic
SafetyGateEnginein code (closing Gap 9). If any gap is detected (e.g. unit mismatch, look-alike collision, tube order violation), the flow halts immediately and routes to the Clarification Coordinator Agent (closing Gap 8). - Once validated, the Protocol Retriever Agent pulls grounded recommendations and evidence chunks from the knowledge substrate.
- The Clinical Synthesizer Agent drafts the clinical interpretation with span-level citations.
- The Verification Pipeline & Clinical Rules Service deterministically verifies citation integrity, entailment against source spans, population applicability, currency, and dose/panic thresholds.
- The verified output is stamped with the
KnowledgeReleaseIDand delivered to the clinician, with a complete trajectory recorded in the immutable Audit Log.
| Layer | Google ADK | AWS Strands | Microsoft MAF / Azure AI Foundry |
|---|---|---|---|
| Shared Core Library | ontogate (shared/) |
ontogate (shared/) |
ontogate (shared/) |
| Agent framework | Google ADK ≥2.11.0 | AWS Strands SDK + Bedrock AgentCore | Microsoft Agent Framework (MAF) Python SDK |
| Model | Gemini 3.8 Flash | Claude Opus 5.5 | gpt-6.1-sol via Azure AI Foundry / OpenAI |
| Session memory | ADK InMemorySessionService + HarnessSession |
Bedrock AgentCore managed sessions | MAF HarnessSession / Local session manager |
| Agent Harness | ClinicalADKHarness (FastMCP, ASGI server, REST API) |
FastAPI ASGI server (src/server.py, REST API) |
ClinicalHarnessAgent + ASGI server (src/harness/server.py) |
| MCP Integration | FastMCP server — the 7 canonical clinical tools | FastMCP tools & REST bridge via src/server.py |
FastMCP tools & REST bridge via src/harness/server.py |
| Production Target | Google Cloud Agent Platform / Cloud Run | AWS Bedrock AgentCore / ECS Fargate | Azure Container Apps + AI Foundry |
| Container Port | Port 8000 (/healthz, /readyz, /api/v1/query) |
Port 8000 (/healthz, /readyz, /api/v1/query) |
Port 8000 (/healthz, /readyz, /api/v1/query) |
| Safety gate | SafetyGateEngine (shared deterministic code via ontogate) |
SafetyGateEngine (shared deterministic code via ontogate) |
SafetyGateEngine (shared deterministic code via ontogate) |
| Runtime skills | SkillToolset per role (no script tool) |
AgentSkills plugin per role (strict=True) |
SkillsProvider per role (no scripts) |
| Tests | 206 offline pytest tests (OKF + Gate + Harness + Skills + Role agents) |
77 offline pytest tests (Invariants + Extensible + Roles + Skills + AgentCore contract) |
50 offline pytest tests (Invariants + Pipeline + Harness + Declarative drift) |
Note: the shared core adds 235 tests (
shared/tests/); deployment teardown (29), cross-cloud conformance (12) and answer graders (11) bring the repository to 620 offline tests.
| Shared Infrastructure | Production Choice |
|---|---|
| Graph DB | Neptune (openCypher) or Neo4j (Cypher) |
| Search | OpenSearch (BM25 + dense vectors) |
| Relational | PostgreSQL — provenance, ACLs, releases |
| Terminology | Terminology server over LOINC, SNOMED CT, RxNorm, UCUM |
| Clinical rules | Licensed drug knowledge base behind a service |
| Guardrails | Bedrock Guardrails / Azure Content Safety / equivalent |
| Observability | Langfuse or similar, PHI-redacted |
Warning
Safety rules must live in deterministic code, not in prompts. This is Gap 9.
The SafetyGateEngine runs before any LLM synthesis — regardless of which agent framework is used.
As analyzed in docs/Decision-Models-Trend-Evidence-and-Outlook.md, typed decision models have emerged since late 2026 as a distinct product category and the control layer of multi-agent systems. Rather than generating unconstrained natural language tokens, these single-pass models score fixed option sets and output calibrated probabilities, categorical choices, or scalar ratings over predefined schemas (e.g. predicates, multi-class selections, or rubric scores).
In this repository's clinical decision-support architecture, decision models represent the middle tier of a 3-tier control cascade:
- Tier 1 — Deterministic Code Gate (
SafetyGateEngine): Pure-Python deterministic invariants enforcing clinical safety guardrails (Gap 9: zero LLM calls, fail-closed routing). - Tier 2 — Typed Decision Models (Control & Gating Layer): Sub-100 ms routing, skill dispatch, intervention filtering, and triage classification (e.g., evaluating
SKILL.mdrules without re-prompting lengthy instructions). - Tier 3 — Frontier Generative Models (Gemini 3.8 Flash, Claude Opus 5.5, GPT-6.1 Sol): Open-ended clinical synthesis, complex reasoning, and multimodal trajectory generation.
| Model / API | Provider / Lab | Release | Architecture & Backbone | Delivery / Catalog | Pricing (per 1M input tokens) | Latency (p50) | Accuracy (Benchmark) | Calibration Score | Robustness (Flip Rate) | Agent & Workflow Integration |
|---|---|---|---|---|---|---|---|---|---|---|
| Microsoft-Decision-1 | Microsoft (Office of the CTO) | Oct 9, 2026 | Post-trained Qwen3.5-9B (rebase: MAI / OpenAI) | Azure AI Foundry & OpenRouter | $0.042 (Output free) | 85 ms (p95: 125 ms) | 83.5% (#1 across 36 blind benchmarks) | 92.2 (#3 behind Jev & Quyet) | 1.3% perturbation flip (0% on reordering / paraphrase) | Skill-based decisions, agent control flow (continue/stop/retry/handoff), UI action selection |
| OpenAI Decisions API | OpenAI | Oct 6, 2026 | gpt-6-luna (Public Beta) |
OpenAI API endpoint | $0.10 (Output free) | ~300 ms (up to 10× faster than Responses API) | 79.4% (JevBench #7) | 89.9 | High order-sensitivity (30% answer flips across 5 reorderings on 77-option task) | Predicate, choice & score endpoints; text and image input |
| Cloudflare Clef / Clef-flash | Cloudflare | Oct 1, 2026 | 27B params, 64K context, vision (Apache 2.0) | Open weights & Workers AI | Open weights / Workers AI | 38.8 ms (Clef-flash median) | Competitive with Jev baseline | High | Jev-compatible API; customer RL fine-tuning on proprietary decisions | Edge classification, low-latency triage distillation |
| AWS Strands Decider 2B | AWS Strands Labs | Oct 1, 2026 | Qwen3.5-2B + ~1M pointer head (LM head removed) | Open weights, code & scripts (no hosted Bedrock endpoint yet) | Free / self-hosted compute (~11h on 1× RTX 3090) | ~115 ms (RTX 3090) / ~153 ms (M3 Mac) / ~1.6 s (Xeon CPU) | JevBench 0.762 (54.8% on 23-benchmark subset) | Short classification only | First-option bias & order-sensitivity | Gating Strands agent actions via intervention hooks (proceed / deny / confirm / feedback) |
| TypeSafe Jev (1.13.0) | TypeSafe AI | Sep 15, 2026 | Dedicated "System One" model (RLCD trained) | TypeSafe API, Vercel AI Gateway | $0.042 (Output free) | 240 ms | 82.3% (#2 across 36 blind benchmarks) | 93.7 (#1 calibration score) | ≤4% flip rate on option reordering | General-purpose typed decisions, LangChain tool call screening middleware |
| Perplexity pplx-decider | Perplexity | Sep 29, 2026 | 27B params (Apache 2.0) | Open weights / Perplexity infra | Open weight | Sub-100 ms | Competitive open 27B | Standard | Open-weight entrant | Search query classification, candidate reranking |
| OpenJev / DiffusionGemma | Google Ecosystem | Oct 2026 | DiffusionGemma (up to 8 images per request) | Open weights / Google ADK Codelab | Compute-dependent | Parallel ADK step | High multimodal | Backbone-dependent | Multimodal image evaluation | Visual & parallel decision nodes inside Google ADK agent workflows |
| Haiku 5.5 Emulation Wrapper | Community / Anthropic | Oct 2026 | Claude Haiku 5.5 (prompt-engineered wrapper) | Anthropic API | Standard Haiku pricing | ~400–600 ms | Matched Jev/OpenAI on 4 of 5 datasets (BoolQ: 87.2% vs Jev 91.0%) | Moderate | 22% answer flips on option reordering | Low-cost fallback for structured decision triage |
| Lab / Platform | Status | Strategic Footprint & Notable Detail |
|---|---|---|
| Microsoft | Shipping (GA) | Shipped Microsoft-Decision-1 on Azure AI Foundry and OpenRouter. Leads in benchmark accuracy (83.5%), latency (85 ms p50), and perturbation robustness (1.3% flip rate, 0% on reordering/paraphrasing). Internal Xbox Research validation showed 14–18× speedups and 200× cost reduction vs GPT-6 Sol. Formally targets skill-based decisions and agent control flow. |
| OpenAI | Public Beta | Public beta Decisions API on gpt-6-luna. Offers predicate, choice, and score outputs with text and vision input. However, independent tests reveal significant order-sensitivity (30% decision-flip rate across option reorderings). |
| Google DeepMind | Ecosystem Integration | No dedicated first-party API endpoint announced yet. Active ecosystem presence via OpenJev (built on DiffusionGemma, accepting up to 8 images per request) and official Google codelabs demonstrating Jev and DiffusionGemma as parallel decision models inside Google ADK workflows. |
| AWS | Open Source Release | Strands Labs released Strands Decider 2B (Qwen3.5-2B + 1M pointer head) with public weights, code, and training recipe. Integrated into the open-source Strands Agents intervention system, but lacks a managed Amazon Bedrock catalog endpoint as of early October 2026. |
| Cloudflare | Generally Available | Released open-weight Clef (27B, Apache 2.0) and Clef-flash (38.8 ms median latency) with Jev-compatible API and customer RL fine-tuning on proprietary decision datasets. |
| Nvidia | Framework Support | NeMo Agents pull request introduced native decision-model abstractions, allowing agents to execute without a conversational LLM and fall back gracefully when unconfigured. |
| Anthropic | Community Emulation | No dedicated model or API announced. Third-party emulation wraps Haiku 5.5; matches Jev on several benchmarks but exhibits 22% order-sensitivity. |
| Meta Superintelligence Labs | Absent / Open Weights | No decision model announced (nearest small model is Muse Spark). Open causal weights can be converted to decision models without training via Nokia AnyJev. |
- Skill Routing as Typed Classification:
Rather than asking a heavyweight reasoning model to parse lengthy prompts or multi-thousand token
SKILL.mdfiles on every turn, a lightweight decision model evaluates structured option sets (e.g. matching clinician requests toskills/runtime/*) in under 100 ms at a fraction of the cost ($0.042/1M tokens). - Deterministic Rules Must Remain in Code (Gap 9 Invariant):
Decision models cannot emit an invalid schema option, but valid is not necessarily correct. Critical safety rules (such as look-alike range collisions, tube sequencing, and units) must remain in deterministic Python (
SafetyGateEngine), not delegated even to trained decision models. - Guard Against Order Sensitivity & Correlated Escalation: Empirical audits show that some decision models suffer from first-option bias or answer flipping under option reordering (up to 30%). Furthermore, studies reveal that LLM escalation judges repeat decision-model errors in 96% of cases on hard calls; escalation paths must therefore utilize genuinely independent verification rather than mirroring reasoning paths.
Note
For the complete research briefing, calibration evidence, benchmark tables, and governance outlook, see docs/Decision-Models-Trend-Evidence-and-Outlook.md.
All three framework implementations are containerized and cloud-ready for high-availability clinical decision support across Google Cloud, Amazon Web Services, and Microsoft Azure.
flowchart TD
Clinician([Clinician / EHR / LIS Client])
subgraph GCP["Google Cloud Agent Platform (europe-west1)"]
GCRun["Vertex AI Agent Platform / Cloud Run\nclinical-adk-agent:8000"]
GAR["Artifact Registry\nclinical-agents"]
GSM["Secret Manager\nclinical-gemini-api-key"]
VAE["Vertex AI Agent Runtime\nADK AdkApp (--use-agent-runtime)"]
GCRun --- GAR
GCRun --- GSM
GCRun --- VAE
end
subgraph AWS["Amazon Web Services (us-east-1)"]
AgentCore["Bedrock AgentCore Runtime\nclinical-strands-agent:8000"]
ECR["Amazon ECR\nstrands-clinical-agents"]
Mem["AgentCore Memory\nclinical-agent-memory"]
IAM_AC["IAM Role\nBedrockAgentCoreExecutionRole"]
AgentCore --- ECR
AgentCore --- Mem
AgentCore --- IAM_AC
end
subgraph Azure["Microsoft Azure (eastus)"]
ACA["Azure Container Apps\nmaf-clinical-agents:8000"]
ACR["Azure Container Registry\nacrclinagents*"]
Foundry["Azure AI Foundry Project\nais-clinical-agents/aiproj-clinical-agents (gpt-6.1-sol)"]
MI["Managed Identity\nCognitive Services OpenAI User"]
ACA --- ACR
ACA --- Foundry
ACA --- MI
end
Clinician -->|HTTPS REST / MCP| GCRun
Clinician -->|HTTPS REST / MCP| AgentCore
Clinician -->|HTTPS REST / MCP| ACA
| Cloud Provider | Framework Deployed | Target Compute / Agent Service | Foundation Model Backend | Skill Store | Authentication & Secrets | Port | Health Checks |
|---|---|---|---|---|---|---|---|
| Google Cloud | google-adk-agents/ (Google ADK 2.0) |
Google Cloud Agent Platform (Agent Runtime / Cloud Run) | Gemini 3.8 Flash (gemini-3.8-flash) |
Cloud Storage, content-addressed (gs://<project>-skills/skills) |
Secret Manager (GEMINI_API_KEY) + ADC (roles/aiplatform.user) |
8000 | /healthz, /readyz |
| AWS | strands-agents/ (AWS Strands SDK) |
Amazon Bedrock AgentCore Runtime (Memory; App Runner opt-in fallback) | Claude Opus 5.5 (global.anthropic.claude-opus-5-5) |
S3, content-addressed (s3://<project>-<env>-skills/skills) |
IAM Execution Role (BedrockAgentCoreExecutionRole) |
8000 | /healthz, /readyz |
| Microsoft Azure | agent-framework/ (Microsoft Agent Framework) |
Azure Container Apps (ACA / Foundry Agent Service) | Azure AI Foundry / Azure OpenAI (gpt-6.1-sol) |
Blob Storage, content-addressed (azblob://<account>/skills/skills) |
System-Assigned Managed Identity (Cognitive Services OpenAI User) |
8000 | /healthz, /readyz |
Automated deployment, testing, and teardown scripts are provided in both Bash (.sh) and PowerShell (.ps1) under deployment/:
# ── Google Cloud (Cloud Run / Vertex AI Agent Platform) ────────────
cd deployment/gcloud-scripts
./deploy.sh # Or in PowerShell: .\deploy.ps1
./test-service.sh # End-to-end integration and health validation
./destroy.sh # Clean teardown of cloud resources
# ── Amazon Web Services (Bedrock AgentCore / ECS Fargate) ──────────
cd deployment/aws-scripts
./deploy.sh # Or in PowerShell: .\deploy.ps1
./test-service.sh # Verify Bedrock AgentCore & memory endpoints
./destroy.sh # Clean teardown of cloud resources
# ── Microsoft Azure (Container Apps + Azure AI Foundry) ────────────
cd deployment/az-scripts
./deploy.sh # Or in PowerShell: .\deploy.ps1
./test-service.sh # Verify Container App & Managed Identity auth
./destroy.sh # Clean teardown of cloud resourcesTip
See deployment/README.md for the complete multi-cloud deployment guide, environment configuration variables, and IAM/RBAC specifications.
- Identity: A governed definition of what terms mean (LOINC, UCUM, SNOMED).
- Epistemic Discipline: Ambiguity as a first-class output, not a silent guess.
- Deterministic Safety: Clinical guardrails extracted from probabilistic prompts and enforced in code.
- Agent-to-Agent Division of Labor: Specialized agent roles via typed A2A contracts.
When the retriever has to decide, the safest action it can take is knowing when to stop and ask.
| Gate | Check | Blocks Release If |
|---|---|---|
| Trajectory (Gap 10) | Deterministic pytest — no LLM calls |
Any safety invariant regression |
| Retrieval | Gold queries with known correct spans | Recall regression |
| Verification | Seeded false claims, wrong-population cases | Any unblocked unsafe claim |
| Safety | Red-team: prompt injection, ACL bypass, PHI leakage | Any critical failure |
| Regression | Model, prompt, or ontology change | Any metric drop vs. last release |
- PHI: One BAA-covered inference path; PHI-safe telemetry (redact/hash prompts; enforce retention limits).
- Authorisation: ACLs on
DocumentVersionenforced in graph queries and citations. UI RBAC is insufficient. - Audit: Append-only log:
user,query,knowledge_release_id,retrieved_spans,output_hash. - Regulatory: Write an intended-use statement before build. FDA CDS criteria (January 2026 guidance). Consult regional equivalents (EU MDR etc.) with counsel.
- Evidence is the source of truth; the graph is a derived index.
- Recommendations are first-class, qualified nodes — not bare triples.
- Agents only where judgement is needed. Parsing, lookup, ACL checks, and index writes are services.
- Humans gate the knowledge base. Staging → evaluated release → promote.
- Verify against source, not against the LLM's own output.
- Never store a fact without its provenance.
- Prefer supersession over overwriting. Supersession is an explicit graph edge.
- Scope everything. Enforce with ACLs, not UI conventions.
- Make forgetting explicit. Define retention and deletion rules up front.
- Evaluate memory directly — test recall, staleness, conflict resolution, cross-session continuity.
Further Reading
- Unified Knowledge Graph RAG on AWS: GraphRAG and LightRAG on one stack
- Python toolkit for building graph-enhanced GenAI applications
- Neo4j GraphRAG
- A modular graph-based Retrieval-Augmented Generation (RAG) system
arXiv Papers