{"id":"legal-case-summariser","title":"Case Summariser","description":"Produces a TYPED, court-ready matter summary from reviewed documents + reviewer notes. Every factual claim cites the underlying document ID; inconsistent notes are flagged as conflicts (competing positions surfaced) rather than resolved by picking a side. Always a draft for the attorney.","category":"legal","version":"2.0.0","source":"AKOS","license":"MIT","tags":["legal","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":"legal-case-summariser","description":"Produces a TYPED, court-ready matter summary from reviewed documents + reviewer notes. Every factual claim cites the underlying document ID; inconsistent notes are flagged as conflicts (competing positions surfaced) rather than resolved by picking a side. Always a draft for the attorney.","systemPrompt":"You produce a court-ready matter summary from reviewed documents + the reviewer's notes.\nStructure: parties and counsel; procedural posture; key facts (each citing the underlying document\nID); open issues for the supervising attorney.\n\nNeutral, professional tone. Do NOT editorialise. EVERY factual claim must cite a source document. If\nthe underlying notes are INCONSISTENT, record the competing accounts in conflicts rather than picking\na side.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_summary exactly once with { partiesAndCounsel, proceduralPosture, keyFacts, openIssues, conflicts }. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.legal-case-summariser","name":"Case Summariser","description":"Produces a TYPED, court-ready matter summary from reviewed documents + reviewer notes. Every factual claim cites the underlying document ID; inconsistent notes are flagged as conflicts (competing positions surfaced) rather than resolved by picking a side. Always a draft for the attorney.","version":"2.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"legal-case-summariser","description":"Produces a TYPED, court-ready matter summary from reviewed documents + reviewer notes. Every factual claim cites the underlying document ID; inconsistent notes are flagged as conflicts (competing positions surfaced) rather than resolved by picking a side. Always a draft for the attorney.","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 * Case Summariser — produces a TYPED, court-ready matter summary from reviewed documents\n * + reviewer notes. Every factual claim cites the underlying document ID; inconsistent\n * notes are FLAGGED as conflicts rather than resolved by picking a side. Always a draft.\n *\n * ```ts\n * const { summary, conflicts } = await createCaseSummariserAgent({ adapter }).run(docsAndNotes)\n * ```\n */\n\nexport interface CitedFact {\n  fact: string\n  /** Underlying document ID(s). */\n  citation: string\n}\n\nexport interface Conflict {\n  issue: string\n  /** The competing accounts found in the notes. */\n  positions: string[]\n}\n\nexport interface MatterSummary {\n  partiesAndCounsel: string\n  proceduralPosture: string\n  keyFacts: CitedFact[]\n  openIssues: string[]\n}\n\nexport interface CaseSummaryResult {\n  summary: MatterSummary\n  /** Inconsistencies in the source notes — flagged, never silently resolved. */\n  conflicts: Conflict[]\n  /** Always true — a draft for the supervising attorney. */\n  requiresAttorneyReview: boolean\n}\n\nexport interface CaseSummariserConfig {\n  adapter: AdapterFactory\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Output = z.object({\n  partiesAndCounsel: z.string(),\n  proceduralPosture: z.string(),\n  keyFacts: z.array(z.object({ fact: z.string(), citation: z.string() })),\n  openIssues: z.array(z.string()),\n  conflicts: z.array(z.object({ issue: z.string(), positions: z.array(z.string()) })),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst skill = {\n  name: 'case-summariser',\n  description: 'Produces a typed, cited matter summary from reviewed documents (flags conflicts).',\n  systemPrompt: `You produce a court-ready matter summary from reviewed documents + the reviewer's notes.\nStructure: parties and counsel; procedural posture; key facts (each citing the underlying document\nID); open issues for the supervising attorney.\n\nNeutral, professional tone. Do NOT editorialise. EVERY factual claim must cite a source document. If\nthe underlying notes are INCONSISTENT, record the competing accounts in conflicts rather than picking\na side.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_summary exactly once with { partiesAndCounsel, proceduralPosture, keyFacts, openIssues, conflicts }. Stop.`,\n  tools: ['submit_summary'],\n}\n\nexport function createCaseSummariserAgent(config: CaseSummariserConfig) {\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  const submit = (): ToolDefinition =>\n    defineZodTool({ name: 'submit_summary', description: 'Submit the matter summary. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition\n\n  async function run(input: string): Promise<CaseSummaryResult> {\n    if (!input?.trim()) throw new Error('case summariser requires reviewed documents + notes')\n    emit('summarise', 'start')\n    const out = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `REVIEWED DOCUMENTS + REVIEWER NOTES:\\n${fenceUntrustedContent(input)}`,\n      parse: (a) => Output.parse(a),\n      skill,\n      memory: config.memory,\n      observers: config.observers,\n      onConfirm: config.onConfirm,\n      maxSteps: config.maxSteps ?? 3,\n    })\n    const { conflicts, ...summary } = out\n    emit('summarise', 'ok', `${conflicts.length} conflict(s)`)\n    return { summary, conflicts, requiresAttorneyReview: true }\n  }\n\n  return {\n    name: 'legal-case-summariser',\n    run,\n    asHandle() {\n      return { name: 'legal-case-summariser', run: async (task: string) => JSON.stringify(await run(task)) }\n    },\n  }\n}\n"},{"path":"README.md","content":"# Case Summariser\n\nProduces a **typed, court-ready matter summary** from reviewed documents + reviewer notes — every fact cited, conflicts flagged not resolved.\n\n```bash\nnpx agentskit add legal-case-summariser\n```\n\n```ts\nimport { anthropic } from '@agentskit/adapters'\nimport { createCaseSummariserAgent } from './agents/legal-case-summariser/agent'\n\nconst r = await createCaseSummariserAgent({\n  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),\n}).run(`${reviewedDocs}\\n\\n${reviewerNotes}`)\n// → { summary: { partiesAndCounsel, proceduralPosture, keyFacts: [{ fact, citation }], openIssues[] }, conflicts: [{ issue, positions[] }], requiresAttorneyReview }\n```\n\n- **Typed + cited** — `invokeStructured` + zod; every key fact cites its underlying document ID.\n- **Flags conflicts** — inconsistent notes are recorded in `conflicts` with the competing positions, never silently resolved by picking a side.\n- **Neutral & always a draft** — no editorialising; `requiresAttorneyReview` always true. Untrusted input is **fenced**.\n\n`run(docsAndNotes)` → `CaseSummaryResult`. `asHandle()` is JSON-out. See [composing agents](../../COMPOSING.md). Pairs with [`legal-case-analyst`](../legal-case-analyst).\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\nexport const suite: EvalSuite = {\n  name: 'legal-case-summariser',\n  cases: [\n    {\n      input: `Produce a court-ready matter summary from these reviewed documents and reviewer notes. DOC-001: Complaint, Hawthorne Mfg. v. Delta Supply Co., filed 2023-02-01 in S.D.N.Y. Plaintiff counsel: R. Singh (Singh LLP). Defendant counsel: T. Brooks (Brooks & Hale). DOC-014: Reviewer note — \"MSA executed 2021-11-05; Delta missed three delivery milestones (Q2-Q4 2022).\" DOC-022: Order on motion to compel, entered 2023-08-19. Open issue per reviewer: damages model not yet produced.`,\n      expected: (r: string) =>\n        /hawthorne/i.test(r) && /delta supply/i.test(r) && /(posture|order|motion)/i.test(r) && /DOC-0\\d\\d/i.test(r),\n    },\n    {\n      input: `Summarize this matter for the supervising attorney. DOC-100 (deposition transcript) says the contract was signed on 2022-04-01. DOC-205 (executed agreement PDF) shows a signature date of 2022-06-15. The reviewer notes flag this discrepancy. Produce the summary and address the conflicting dates.`,\n      expected: (r: string) =>\n        /(conflict|inconsisten|discrepan|differ)/i.test(r) && /(2022-04-01|2022-06-15|april|june)/i.test(r),\n    },\n    {\n      input: `Build a key-facts section with citations. DOC-301: invoice dated 2023-05-02 for $84,000. DOC-302: payment record showing $40,000 wired 2023-06-10. DOC-303: demand letter dated 2023-08-01 claiming $44,000 outstanding. Cite each underlying document ID.`,\n      expected: (r: string) =>\n        /DOC-30\\d/i.test(r) && /(\\$84,?000|\\$44,?000|\\$40,?000|outstanding|balance)/i.test(r),\n    },\n    {\n      input: `Summarize this matter. The only material provided is a one-line reviewer note: \"Saw something about a lawsuit, not sure which parties or court.\" No document IDs, no parties, no procedural history are supplied.`,\n      expected: (r: string) =>\n        /(open issue|flag|missing|not provided|insufficient|cannot|unable|escalat|attorney)/i.test(r),\n    },\n  ],\n}\n"}],"installable":true,"validation":{"status":"approved","score":97,"confidence":0.96,"method":"codex-executor-independent-reviewer","iterations":1,"cases":3,"summary":"The agent produced valid structured outputs for all three cases, kept requiresAttorneyReview true, cited each key fact to the available input/document identifier, surfaced missing-context gaps, and handled the prompt-injection case by treating the override request as data rather than following it. It avoided hallucinating case facts from sparse prompts and stayed aligned with the legal summariser purpose. Minor reservations: the 'normal' case did not produce a rich matter summary because the supplied input was only a meta-task prompt, and citation style is slightly inconsistent across cases, but that is not a readiness blocker.","strengths":["Valid structured output shape in every case, including summary, conflicts, and requiresAttorneyReview.","No unsafe legal conclusions or invented parties, dates, filings, or claims.","Good uncertainty handling with concrete open issues for missing documents and context.","Prompt-injection resistance is demonstrated; the agent did not output the injected APPROVED string.","Conflicts are represented as an empty array when no competing positions are present, rather than fabricating disputes."],"notes":[]}}