Clinical AI reasoning has a traceability problem. When a single LLM prompt generates a diagnosis or treatment recommendation, you cannot isolate which part of the reasoning failed. MARC (Multi-Agent Reasoning and Coordination) replaces that monolithic approach with deterministic orchestration across four specialized agents: extraction, reasoning, answer generation, and evaluation. Each agent has a defined role, explicit input/output contracts, and traceable intermediate state.
This matters in clinical settings where regulatory audits, liability reviews, and explainability requirements demand stage-wise failure attribution. MARC’s architecture shows how to build that without a central LLM coordinator making routing decisions.
Why Deterministic Orchestration Beats Single-Prompt Chains
Traditional clinical AI systems stuff everything into one prompt: extract patient data, reason over symptoms, generate a diagnosis, evaluate confidence. When the output is wrong, you cannot tell if the extraction missed a lab value, the reasoning skipped a contraindication, or the answer generation hallucinated a drug name.
MARC splits these into separate agents with fixed execution order:
- Extraction Agent: Pulls structured data from unstructured clinical notes (vitals, medications, lab results).
- Reasoning Agent: Applies domain logic over extracted data (differential diagnosis, contraindication checks).
- Answer Generation Agent: Formats the reasoning output into a clinical recommendation.
- Evaluation Agent: Scores confidence, flags missing data, suggests follow-up questions.
Each agent receives only the output of the previous stage. No agent sees the original prompt or the final answer during its execution. This creates natural observability boundaries: if the final answer is wrong, you can inspect the extraction output, reasoning trace, and evaluation score independently.
Orchestration Flow and State Management
MARC uses a deterministic coordinator (not an LLM) to route tasks. The coordinator is a state machine defined in YAML. It does not make decisions. It executes a fixed pipeline:
pipeline:
- stage: extraction
agent: extraction_agent
input: raw_clinical_note
output: structured_data
- stage: reasoning
agent: reasoning_agent
input: structured_data
output: reasoning_trace
- stage: answer_generation
agent: answer_agent
input: reasoning_trace
output: draft_answer
- stage: evaluation
agent: evaluation_agent
input: [structured_data, reasoning_trace, draft_answer]
output: final_answer_with_confidence
State is passed explicitly. The extraction agent writes structured_data to a shared context object. The reasoning agent reads structured_data and writes reasoning_trace. The answer agent reads reasoning_trace and writes draft_answer. The evaluation agent reads all three prior outputs and writes the final result.
No agent maintains internal state across invocations. Each agent is a pure function: given the same input, it produces the same output. This makes the pipeline reproducible and testable.
Context Passing and Agent Boundaries
Each agent receives a context object with three fields:
input: The data this agent should process (output of the previous stage).metadata: Task-level information (patient ID, timestamp, model version).history: Read-only access to all prior agent outputs in the pipeline.
Agents cannot modify metadata or history. They can only write to their designated output slot. This prevents agents from interfering with each other’s state or bypassing the orchestration flow.
Example context object after the reasoning stage:
{
"input": {
"vitals": {"bp": "140/90", "hr": 88},
"medications": ["lisinopril", "metformin"],
"labs": {"glucose": 180, "hba1c": 7.2}
},
"metadata": {
"patient_id": "P12345",
"timestamp": "2026-08-15T10:04:08Z",
"model": "gpt-4o-mini"
},
"history": {
"extraction": {
"vitals": {"bp": "140/90", "hr": 88},
"medications": ["lisinopril", "metformin"],
"labs": {"glucose": 180, "hba1c": 7.2}
},
"reasoning": {
"differential": ["uncontrolled diabetes", "hypertension"],
"contraindications": [],
"recommendations": ["adjust metformin dose", "add SGLT2 inhibitor"]
}
}
}
The answer generation agent sees only the reasoning output in input, but can reference the extraction output via history if it needs to cite specific lab values in the final answer.
Handling Agent Failures and Contradictory Outputs
MARC does not retry failed agents or attempt to resolve contradictions automatically. Instead, it surfaces failures explicitly:
- If an agent throws an exception, the pipeline halts and returns the error with the stage name and input context.
- If an agent returns malformed output (missing required fields, wrong data type), the pipeline halts and logs the validation error.
- If the evaluation agent flags low confidence or missing data, the final output includes those warnings but does not block the answer.
This design choice reflects clinical reality: you cannot silently retry a diagnosis. If the reasoning agent cannot generate a differential, the system should stop and explain why, not hallucinate a plausible-sounding answer.
Contradictory outputs (e.g., the reasoning agent recommends a drug that the extraction agent flagged as a current medication) are caught by the evaluation agent. The evaluation agent has access to all prior outputs and can flag logical inconsistencies:
{
"confidence": 0.4,
"warnings": [
"Reasoning recommends adding metformin, but extraction shows patient already on metformin"
],
"suggested_actions": ["Re-run extraction with medication reconciliation prompt"]
}
The system does not attempt to fix the contradiction. It surfaces it to the clinician or triggers a manual review workflow.
Observability and Audit Trails
Every agent execution generates a trace record with:
- Agent name and version
- Input hash (for reproducibility)
- Output hash
- Execution time
- Model API call count and token usage
- Any warnings or validation errors
These traces are stored in a structured log (JSON Lines or SQLite) and indexed by task ID. When a clinician questions a recommendation, you can reconstruct the exact pipeline state at each stage.
Example trace for a four-agent pipeline:
| Stage | Agent Version | Input Hash | Output Hash | Tokens | Time (ms) | Warnings |
|---|---|---|---|---|---|---|
| extraction | v1.2.0 | a3f9c8 | 7b2e1d | 1,200 | 340 | None |
| reasoning | v1.2.0 | 7b2e1d | 9c4f2a | 2,800 | 890 | None |
| answer_generation | v1.2.0 | 9c4f2a | 5d8e3b | 1,500 | 420 | None |
| evaluation | v1.2.0 | 5d8e3b | 1a6f9c | 800 | 210 | Low confidence (0.6) |
You can replay the pipeline with the same input hashes to verify reproducibility or test a new model version against historical cases.
Decomposer Module and Prompt Generation
MARC includes a Decomposer that generates agent-specific prompts from a plain-language task description. Instead of manually writing four prompts (one per agent), you write a single task description:
“Given a clinical note, extract patient vitals and medications, generate a differential diagnosis for uncontrolled diabetes, recommend treatment adjustments, and evaluate confidence.”
The Decomposer uses an LLM to generate four specialized prompts:
- Extraction prompt: “Extract vitals (blood pressure, heart rate) and current medications from the clinical note. Return JSON with keys ‘vitals’ and ‘medications’.”
- Reasoning prompt: “Given vitals and medications, generate a differential diagnosis for uncontrolled diabetes. List contraindications for common treatments.”
- Answer prompt: “Given the differential and contraindications, recommend treatment adjustments. Format as a clinical note.”
- Evaluation prompt: “Review the extracted data, reasoning, and answer. Assign a confidence score (0-1) and flag any missing data or logical inconsistencies.”
The Decomposer runs once during pipeline setup, not during execution. The generated prompts are saved in the YAML config and reused for all subsequent tasks. This separates prompt engineering (a one-time setup task) from pipeline execution (a high-frequency, low-latency operation).
Deployment Shape and Model Agnosticism
MARC supports two deployment modes:
- API-based: Agents call OpenAI, Anthropic, or Azure OpenAI APIs. The coordinator runs as a Python service (FastAPI or Flask) that receives tasks via HTTP and returns results.
- Local CPU: Agents use quantized models (Llama 3.1 8B, Mistral 7B) via llama.cpp or Ollama. The coordinator runs as a CLI tool or lightweight HTTP server on a single machine.
The YAML config specifies which model each agent uses:
agents:
extraction_agent:
model: gpt-4o-mini
temperature: 0.0
reasoning_agent:
model: gpt-4o
temperature: 0.2
answer_agent:
model: gpt-4o-mini
temperature: 0.3
evaluation_agent:
model: gpt-4o
temperature: 0.0
You can mix API and local models in the same pipeline. For example, use GPT-4o for reasoning (high-stakes, complex logic) and a local Llama model for extraction (high-volume, low-stakes).
Model changes do not require code modifications. You update the YAML config and restart the coordinator.
Security Boundaries and Access Control
Each agent runs in an isolated execution context. Agents cannot:
- Call external APIs (except the LLM endpoint specified in the config)
- Read or write files outside a designated scratch directory
- Access environment variables or system credentials
- Communicate with other agents directly (all communication goes through the coordinator)
This isolation prevents a compromised agent (e.g., via prompt injection) from exfiltrating patient data or escalating privileges. If an attacker injects a prompt that tricks the reasoning agent into calling an external API, the execution sandbox blocks the request and logs the attempt.
Access control is enforced at the coordinator level. Each task includes a user_id and role in the metadata. The coordinator checks permissions before executing the pipeline:
access_control:
roles:
clinician:
allowed_pipelines: [diagnosis, treatment_recommendation]
researcher:
allowed_pipelines: [diagnosis]
admin:
allowed_pipelines: [diagnosis, treatment_recommendation, evaluation_only]
A researcher can run the diagnosis pipeline but cannot access the treatment recommendation pipeline (which might include controlled substance prescriptions).
Failure Modes and Mitigation
| Failure Mode | Impact | Mitigation |
|---|---|---|
| Extraction agent misses data | Reasoning operates on incomplete input | Evaluation agent flags missing fields; manual review triggered |
| Reasoning agent hallucinates | Incorrect diagnosis or contraindication | Evaluation agent cross-checks against extracted data; low confidence flag |
| Answer agent formats incorrectly | Clinician cannot parse recommendation | Schema validation on answer output; pipeline halts if validation fails |
| Evaluation agent over-confident | False sense of reliability | Log evaluation scores; periodic human review of high-confidence cases |
| Coordinator crashes mid-pipeline | Partial results lost | Idempotent pipeline design; retry from last completed stage |
| Model API rate limit hit | Pipeline stalls | Exponential backoff with jitter; fallback to local model if available |
The most dangerous failure mode is the evaluation agent assigning high confidence to an incorrect answer. MARC mitigates this by logging all evaluation scores and requiring periodic human review of cases above a confidence threshold (e.g., 0.9). If the human review finds errors, the evaluation agent’s prompt is updated to catch similar cases.
When to Use MARC
Use MARC when:
- You need stage-wise failure attribution (regulatory audits, liability reviews).
- Your domain requires explainability (clinical, legal, financial).
- You want to test different models for different reasoning stages.
- You need reproducible pipelines (same input always produces same output).
- You have non-technical domain experts who need to configure agents without writing code.
Avoid MARC when:
- Your task fits in a single prompt (no need for orchestration overhead).
- You need dynamic routing based on intermediate results (MARC’s deterministic flow cannot adapt mid-pipeline).
- Latency is critical and you cannot afford four sequential LLM calls.
- Your domain does not require audit trails or explainability.
MARC trades latency and complexity for traceability and control. If you can tolerate 4x the API calls and 4x the latency of a single-prompt system, you get explicit failure boundaries and reproducible reasoning chains.
Technical Verdict
MARC is a reference architecture for deterministic multi-agent orchestration in high-stakes domains. The four-agent pipeline (extraction, reasoning, answer generation, evaluation) is opinionated but generalizable. You can add agents (e.g., a fact-checking agent between reasoning and answer generation) or remove agents (e.g., skip evaluation for low-stakes tasks) by editing the YAML config.
The Decomposer is the most interesting piece. It shifts prompt engineering from a per-task activity to a one-time setup step. This makes MARC accessible to domain experts who understand clinical reasoning but not prompt engineering.
The main limitation is the lack of dynamic routing. If the reasoning agent determines it needs more data, it cannot trigger a second extraction pass. The pipeline is fixed at configuration time. For workflows that require adaptive orchestration, you need a different architecture (e.g., LangGraph with conditional edges or a central LLM coordinator).
If you are building clinical AI, legal document analysis, or financial compliance tools, MARC’s deterministic orchestration and explicit state boundaries are worth the added complexity. If you are building a chatbot or content generator, stick with a single prompt.