{"id":"clinical-patient-summary","title":"Patient Summary","description":"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.","category":"clinical","version":"2.0.0","source":"AKOS","license":"MIT","tags":["clinical","summarization","human-in-the-loop"],"packages":["@agentskit/core","@agentskit/runtime","@agentskit/tools"],"files":["agent.ts","README.md","eval.ts"],"requires":{"zod":"^3","zod-to-json-schema":"^3"},"status":"validated","skill":{"name":"clinical-patient-summary","description":"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.","systemPrompt":"You draft a one-page pre-visit summary from the supplied chart excerpts. Fields: a\none-sentence reason for visit; active problems (max 5, most relevant first); current medications;\nallergies; vitals trend; outstanding follow-ups; open questions.\n\nNEVER invent values. If the chart lacks a field, use \"not in chart\" (or an empty array) rather\nthan guessing. This is a DRAFT for the clinician to confirm.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_summary exactly once with the structured fields. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.clinical-patient-summary","name":"Patient Summary","description":"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.","version":"2.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"clinical-patient-summary","description":"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.","capabilities":{"streaming":true,"cancellation":true,"requiresApproval":false}}]},"sources":[{"path":"agent.ts","content":"import type { AdapterFactory, ChatMemory, Observer, ToolCall, ToolDefinition } from '@agentskit/core'\nimport { fenceUntrustedContent, UNTRUSTED_CONTENT_DIRECTIVE } from '@agentskit/core/security'\nimport { invokeStructured } from '@agentskit/runtime'\nimport { defineZodTool } from '@agentskit/tools'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport type { JSONSchema7 } from 'json-schema'\n\n/**\n * Patient Summary — drafts a typed one-page pre-visit summary from chart excerpts.\n * Structured fields (not a blob), never invents values (\"not in chart\" for gaps), and\n * always a draft for the clinician to confirm.\n *\n * ```ts\n * const { summary } = await createPatientSummaryAgent({ adapter }).run(chartExcerpts)\n * ```\n */\n\nexport interface PatientSummary {\n  reasonForVisit: string\n  /** Active problems, most relevant first (max 5). */\n  activeProblems: string[]\n  medications: string[]\n  allergies: string[]\n  vitalsTrend: string\n  followUps: string[]\n  openQuestions: string[]\n}\n\nexport interface PatientSummaryResult {\n  summary: PatientSummary\n  /** Always true — a draft for the clinician to confirm. */\n  requiresClinicianSignoff: boolean\n}\n\nexport interface PatientSummaryConfig {\n  adapter: AdapterFactory\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Summary = z.object({\n  reasonForVisit: z.string(),\n  activeProblems: z.array(z.string()).max(5),\n  medications: z.array(z.string()),\n  allergies: z.array(z.string()),\n  vitalsTrend: z.string(),\n  followUps: z.array(z.string()),\n  openQuestions: z.array(z.string()),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst skill = {\n  name: 'patient-summary',\n  description: 'Drafts a typed one-page pre-visit patient summary from chart excerpts.',\n  systemPrompt: `You draft a one-page pre-visit summary from the supplied chart excerpts. Fields: a\none-sentence reason for visit; active problems (max 5, most relevant first); current medications;\nallergies; vitals trend; outstanding follow-ups; open questions.\n\nNEVER invent values. If the chart lacks a field, use \"not in chart\" (or an empty array) rather\nthan guessing. This is a DRAFT for the clinician to confirm.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_summary exactly once with the structured fields. Stop.`,\n  tools: ['submit_summary'],\n}\n\nexport function createPatientSummaryAgent(config: PatientSummaryConfig) {\n  const emit = (label: string, status: 'start' | 'ok' | 'skip' | 'error', detail?: string) => {\n    for (const o of config.observers ?? []) void o.on({ type: 'progress', label, status, detail })\n  }\n\n  const submit = (): ToolDefinition =>\n    defineZodTool({ name: 'submit_summary', description: 'Submit the patient summary. Call exactly once.', schema: Summary, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition\n\n  async function run(chart: string): Promise<PatientSummaryResult> {\n    if (!chart?.trim()) throw new Error('patient summary requires non-empty chart excerpts')\n    emit('summarise', 'start')\n    const summary = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `CHART EXCERPTS:\\n${fenceUntrustedContent(chart)}`,\n      parse: (a) => Summary.parse(a),\n      skill,\n      memory: config.memory,\n      observers: config.observers,\n      onConfirm: config.onConfirm,\n      maxSteps: config.maxSteps ?? 3,\n    })\n    emit('summarise', 'ok', `${summary.activeProblems.length} problem(s)`)\n    return { summary, requiresClinicianSignoff: true }\n  }\n\n  return {\n    name: 'clinical-patient-summary',\n    run,\n    asHandle() {\n      return { name: 'clinical-patient-summary', run: async (task: string) => JSON.stringify(await run(task)) }\n    },\n  }\n}\n"},{"path":"README.md","content":"# Patient Summary\n\nDrafts a **typed one-page pre-visit summary** from chart excerpts — structured fields, never invents values, always a draft for the clinician to confirm.\n\n```bash\nnpx agentskit add clinical-patient-summary\n```\n\n```ts\nimport { anthropic } from '@agentskit/adapters'\nimport { createPatientSummaryAgent } from './agents/clinical-patient-summary/agent'\n\nconst { summary } = await createPatientSummaryAgent({\n  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),\n}).run(chartExcerpts)\n```\n\n- **Typed output** — `{ reasonForVisit, activeProblems[], medications[], allergies[], vitalsTrend, followUps[], openQuestions[] }` via `invokeStructured` + zod (active problems capped at 5).\n- **Never invents** — gaps become `\"not in chart\"` / empty arrays, never guessed.\n- **Always a draft** — `requiresClinicianSignoff` is always true. Untrusted chart text is **fenced**.\n\n`run(chart)` → `PatientSummaryResult { summary, requiresClinicianSignoff }`. `asHandle()` is JSON-out.\n\nSee [composing agents](../../COMPOSING.md). Sibling: [`clinical-note-summariser`](../clinical-note-summariser).\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\n/** Eval cases for the summary AgentHandle (`run(chartExcerpts) → jsonPatientSummaryResult`). */\nexport const suite: EvalSuite = {\n  name: 'clinical-patient-summary',\n  cases: [\n    {\n      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.',\n      expected: (r: string) => /\"activeProblems\":\\[/.test(r) && /lisinopril/.test(r) && /\"requiresClinicianSignoff\":true/.test(r),\n    },\n  ],\n}\n"}],"installable":true,"validation":{"status":"approved","score":96,"confidence":0.96,"method":"codex-executor-independent-reviewer","iterations":1,"cases":3,"summary":"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.","strengths":["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."],"notes":[]}}