legal·Independently reviewed · 97/100

Case Summariser

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.

legalsummarizationhuman-in-the-loop

Install

npx agentskit add legal-case-summariser

Quick start

import { openai } from '@agentskit/adapters'import { createLegalCaseSummariserAgent } from './agents/legal-case-summariser/agent'const agent = createLegalCaseSummariserAgent({  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
97/100
Confidence
96%
Evaluation cases
3
Iterations
1

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.

What passed review

  • 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.

Example

A real usage example maintained with this agent.

import { anthropic } from '@agentskit/adapters'import { createCaseSummariserAgent } from './agents/legal-case-summariser/agent'const r = await createCaseSummariserAgent({  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),}).run(`${reviewedDocs}\n\n${reviewerNotes}`)// → { summary: { partiesAndCounsel, proceduralPosture, keyFacts: [{ fact, citation }], openIssues[] }, conflicts: [{ issue, positions[] }], requiresAttorneyReview }

Extend it

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

const agent = createLegalCaseSummariserAgent({  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'/** * Case Summariser — 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 rather than resolved by picking a side. Always a draft. * * ```ts * const { summary, conflicts } = await createCaseSummariserAgent({ adapter }).run(docsAndNotes) * ``` */export interface CitedFact {  fact: string  /** Underlying document ID(s). */  citation: string}export interface Conflict {  issue: string  /** The competing accounts found in the notes. */  positions: string[]}export interface MatterSummary {  partiesAndCounsel: string  proceduralPosture: string  keyFacts: CitedFact[]  openIssues: string[]}export interface CaseSummaryResult {  summary: MatterSummary  /** Inconsistencies in the source notes — flagged, never silently resolved. */  conflicts: Conflict[]  /** Always true — a draft for the supervising attorney. */  requiresAttorneyReview: boolean}export interface CaseSummariserConfig {  adapter: AdapterFactory  memory?: ChatMemory  observers?: Observer[]  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>  maxSteps?: number}const Output = z.object({  partiesAndCounsel: z.string(),  proceduralPosture: z.string(),  keyFacts: z.array(z.object({ fact: z.string(), citation: z.string() })),  openIssues: z.array(z.string()),  conflicts: z.array(z.object({ issue: z.string(), positions: z.array(z.string()) })),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = {  name: 'case-summariser',  description: 'Produces a typed, cited matter summary from reviewed documents (flags conflicts).',  systemPrompt: `You produce a court-ready matter summary from reviewed documents + the reviewer's notes.Structure: parties and counsel; procedural posture; key facts (each citing the underlying documentID); open issues for the supervising attorney.Neutral, professional tone. Do NOT editorialise. EVERY factual claim must cite a source document. Ifthe underlying notes are INCONSISTENT, record the competing accounts in conflicts rather than pickinga side.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_summary exactly once with { partiesAndCounsel, proceduralPosture, keyFacts, openIssues, conflicts }. Stop.`,  tools: ['submit_summary'],}export function createCaseSummariserAgent(config: CaseSummariserConfig) {  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 matter summary. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition  async function run(input: string): Promise<CaseSummaryResult> {    if (!input?.trim()) throw new Error('case summariser requires reviewed documents + notes')    emit('summarise', 'start')    const out = await invokeStructured({      adapter: config.adapter,      tool: submit(),      task: `REVIEWED DOCUMENTS + REVIEWER NOTES:\n${fenceUntrustedContent(input)}`,      parse: (a) => Output.parse(a),      skill,      memory: config.memory,      observers: config.observers,      onConfirm: config.onConfirm,      maxSteps: config.maxSteps ?? 3,    })    const { conflicts, ...summary } = out    emit('summarise', 'ok', `${conflicts.length} conflict(s)`)    return { summary, conflicts, requiresAttorneyReview: true }  }  return {    name: 'legal-case-summariser',    run,    asHandle() {      return { name: 'legal-case-summariser', 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'export const suite: EvalSuite = {  name: 'legal-case-summariser',  cases: [    {      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.`,      expected: (r: string) =>        /hawthorne/i.test(r) && /delta supply/i.test(r) && /(posture|order|motion)/i.test(r) && /DOC-0\d\d/i.test(r),    },    {      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.`,      expected: (r: string) =>        /(conflict|inconsisten|discrepan|differ)/i.test(r) && /(2022-04-01|2022-06-15|april|june)/i.test(r),    },    {      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.`,      expected: (r: string) =>        /DOC-30\d/i.test(r) && /(\$84,?000|\$44,?000|\$40,?000|outstanding|balance)/i.test(r),    },    {      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.`,      expected: (r: string) =>        /(open issue|flag|missing|not provided|insufficient|cannot|unable|escalat|attorney)/i.test(r),    },  ],}

Was this agent useful?

Your response helps us prioritize agent quality.

Keep exploring

Related agents

View category