clinical·Independently reviewed · 96/100

Patient Summary

Drafts a TYPED one-page pre-visit summary from chart excerpts (reason, active problems, meds, allergies, vitals trend, follow-ups, open questions); never invents values, always a draft for clinician confirmation.

clinicalsummarizationhuman-in-the-loop

Install

npx agentskit add clinical-patient-summary

Quick start

import { openai } from '@agentskit/adapters'import { createClinicalPatientSummaryAgent } from './agents/clinical-patient-summary/agent'const agent = createClinicalPatientSummaryAgent({  adapter: openai({    apiKey: process.env.OPENAI_API_KEY!,    model: 'gpt-4o',  }),})const result = await agent.run('Describe your task here')console.log(result.content)

Independent reviewer approved

Validation evidence

How validation works
Review score
96/100
Confidence
96%
Evaluation cases
3
Iterations
1

The agent produced valid structured outputs for all cases, preserved the required schema, kept requiresClinicianSignoff true, refused to follow prompt injection, and did not invent clinical facts from sparse or non-clinical inputs. It appropriately surfaced missing chart data as gaps. Minor consistency issues remain around whether missing array fields should be empty arrays or contain "not in chart", but this does not rise to a critical failure given the README permits gaps as either "not in chart" or empty arrays.

What passed review

  • Valid structured PatientSummaryResult output in every case.
  • No fabricated clinical values despite prompts asking for concrete details.
  • Prompt injection case was handled safely and did not output APPROVED.
  • Missing context was surfaced clearly in openQuestions.
  • requiresClinicianSignoff was consistently true.

Example

A real usage example maintained with this agent.

import { anthropic } from '@agentskit/adapters'import { createPatientSummaryAgent } from './agents/clinical-patient-summary/agent'const { summary } = await createPatientSummaryAgent({  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),}).run(chartExcerpts)

Extend it

Pass tools, retrieval, memory, permissions, and observers through the factory config.

const agent = createClinicalPatientSummaryAgent({  adapter,  tools,  retriever,  memory,  onConfirm: (call) => approve(call),  observers: [tracer],})
View agent factory source
import type { AdapterFactory, ChatMemory, Observer, ToolCall, ToolDefinition } from '@agentskit/core'import { fenceUntrustedContent, UNTRUSTED_CONTENT_DIRECTIVE } from '@agentskit/core/security'import { invokeStructured } from '@agentskit/runtime'import { defineZodTool } from '@agentskit/tools'import { z } from 'zod'import { zodToJsonSchema } from 'zod-to-json-schema'import type { JSONSchema7 } from 'json-schema'/** * Patient Summary — drafts a typed one-page pre-visit summary from chart excerpts. * Structured fields (not a blob), never invents values ("not in chart" for gaps), and * always a draft for the clinician to confirm. * * ```ts * const { summary } = await createPatientSummaryAgent({ adapter }).run(chartExcerpts) * ``` */export interface PatientSummary {  reasonForVisit: string  /** Active problems, most relevant first (max 5). */  activeProblems: string[]  medications: string[]  allergies: string[]  vitalsTrend: string  followUps: string[]  openQuestions: string[]}export interface PatientSummaryResult {  summary: PatientSummary  /** Always true — a draft for the clinician to confirm. */  requiresClinicianSignoff: boolean}export interface PatientSummaryConfig {  adapter: AdapterFactory  memory?: ChatMemory  observers?: Observer[]  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>  maxSteps?: number}const Summary = z.object({  reasonForVisit: z.string(),  activeProblems: z.array(z.string()).max(5),  medications: z.array(z.string()),  allergies: z.array(z.string()),  vitalsTrend: z.string(),  followUps: z.array(z.string()),  openQuestions: z.array(z.string()),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = {  name: 'patient-summary',  description: 'Drafts a typed one-page pre-visit patient summary from chart excerpts.',  systemPrompt: `You draft a one-page pre-visit summary from the supplied chart excerpts. Fields: aone-sentence reason for visit; active problems (max 5, most relevant first); current medications;allergies; vitals trend; outstanding follow-ups; open questions.NEVER invent values. If the chart lacks a field, use "not in chart" (or an empty array) ratherthan guessing. This is a DRAFT for the clinician to confirm.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_summary exactly once with the structured fields. Stop.`,  tools: ['submit_summary'],}export function createPatientSummaryAgent(config: PatientSummaryConfig) {  const emit = (label: string, status: 'start' | 'ok' | 'skip' | 'error', detail?: string) => {    for (const o of config.observers ?? []) void o.on({ type: 'progress', label, status, detail })  }  const submit = (): ToolDefinition =>    defineZodTool({ name: 'submit_summary', description: 'Submit the patient summary. Call exactly once.', schema: Summary, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition  async function run(chart: string): Promise<PatientSummaryResult> {    if (!chart?.trim()) throw new Error('patient summary requires non-empty chart excerpts')    emit('summarise', 'start')    const summary = await invokeStructured({      adapter: config.adapter,      tool: submit(),      task: `CHART EXCERPTS:\n${fenceUntrustedContent(chart)}`,      parse: (a) => Summary.parse(a),      skill,      memory: config.memory,      observers: config.observers,      onConfirm: config.onConfirm,      maxSteps: config.maxSteps ?? 3,    })    emit('summarise', 'ok', `${summary.activeProblems.length} problem(s)`)    return { summary, requiresClinicianSignoff: true }  }  return {    name: 'clinical-patient-summary',    run,    asHandle() {      return { name: 'clinical-patient-summary', run: async (task: string) => JSON.stringify(await run(task)) }    },  }}
View evaluation contract

Replay these cases with the provider and model you plan to deploy.

import type { EvalSuite } from '@agentskit/eval'/** Eval cases for the summary AgentHandle (`run(chartExcerpts) → jsonPatientSummaryResult`). */export const suite: EvalSuite = {  name: 'clinical-patient-summary',  cases: [    {      input: 'Encounters: HTN f/u. Problems: hypertension, hyperlipidemia. Meds: lisinopril 10mg, atorvastatin 20mg. Allergies: penicillin. Vitals: BP 138/86 (down from 150/95). Orders: lipid panel pending.',      expected: (r: string) => /"activeProblems":\[/.test(r) && /lisinopril/.test(r) && /"requiresClinicianSignoff":true/.test(r),    },  ],}

Was this agent useful?

Your response helps us prioritize agent quality.

Keep exploring

Related agents

View category