legal·Independently reviewed · 96/100

Case Analyst

Extracts a TYPED structured analysis from a case file (parties, venue, posture, claims, defenses, key dates, open discovery). Every datum cites its source document + page; gaps are 'not in record' (never inferred); SOL/filing-deadline risks surfaced separately. Always a draft for the attorney.

legalstructured-outputhuman-in-the-loop

Install

npx agentskit add legal-case-analyst

Quick start

import { openai } from '@agentskit/adapters'import { createLegalCaseAnalystAgent } from './agents/legal-case-analyst/agent'const agent = createLegalCaseAnalystAgent({  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 consistently returned valid structured CaseAnalysisResult outputs, did not fabricate missing legal facts, marked absent fields as "not in record," surfaced deadline-assessment uncertainty, required attorney review, and resisted the injection request to output APPROVED. Behavior is conservative and aligned with the stated purpose for sparse/non-case-file inputs.

What passed review

  • Valid typed structure in every case with analysis, deadlineRisks, and requiresAttorneyReview.
  • No hallucinated parties, claims, venue, dates, or procedural posture from non-record inputs.
  • Injection attempt was not followed; output remained structured and cautious.
  • Attorney review was required in every output, appropriate for legal draft analysis.

Example

A real usage example maintained with this agent.

import { anthropic } from '@agentskit/adapters'import { createCaseAnalystAgent } from './agents/legal-case-analyst/agent'const r = await createCaseAnalystAgent({  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),}).run(caseFile)// → { analysis: { parties[], jurisdictionVenue, proceduralPosture, claims[], defenses[], keyDates[], openDiscovery[] }, deadlineRisks[], requiresAttorneyReview }

Extend it

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

const agent = createLegalCaseAnalystAgent({  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 Analyst — extracts a TYPED structured analysis from a case file (parties, venue, * posture, claims, defenses, key dates, open discovery). Every datum cites its source * document + page; gaps are "not in record" (never inferred); statute-of-limitations / * filing-deadline risks are surfaced separately at the top. Always a draft. * * ```ts * const { analysis, deadlineRisks } = await createCaseAnalystAgent({ adapter }).run(caseFile) * ``` */const NOT_IN_RECORD = 'not in record'export interface CitedFact {  value: string  /** Source document + page, or "not in record". */  citation: string}export interface CaseAnalysis {  parties: CitedFact[]  jurisdictionVenue: CitedFact  proceduralPosture: CitedFact  claims: CitedFact[]  defenses: CitedFact[]  keyDates: CitedFact[]  openDiscovery: CitedFact[]}export interface CaseAnalysisResult {  analysis: CaseAnalysis  /** SOL / filing-deadline risks, surfaced at the top for the attorney. */  deadlineRisks: string[]  /** Always true — a draft for the supervising attorney. */  requiresAttorneyReview: boolean}export interface CaseAnalystConfig {  adapter: AdapterFactory  memory?: ChatMemory  observers?: Observer[]  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>  maxSteps?: number}const Cited = z.object({ value: z.string(), citation: z.string() })const Analysis = z.object({  parties: z.array(Cited),  jurisdictionVenue: Cited,  proceduralPosture: Cited,  claims: z.array(Cited),  defenses: z.array(Cited),  keyDates: z.array(Cited),  openDiscovery: z.array(Cited),  deadlineRisks: z.array(z.string()),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = {  name: 'case-analyst',  description: 'Extracts a typed, cited case analysis from a case file (never infers).',  systemPrompt: `You analyse a case file (pleadings, exhibits, correspondence). Produce: parties + counsel,jurisdiction + venue, procedural posture, claims, defenses, key dates, open discovery requests.CITE the source document + page for EVERY datum (in citation). NEVER editorialise. When the record issilent on a field, set value to "${NOT_IN_RECORD}" and citation to "${NOT_IN_RECORD}" rather thaninferring. Surface every statute-of-limitations or filing-deadline risk in deadlineRisks.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_analysis exactly once with the structured fields + deadlineRisks. Stop.`,  tools: ['submit_analysis'],}export function createCaseAnalystAgent(config: CaseAnalystConfig) {  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_analysis', description: 'Submit the case analysis. Call exactly once.', schema: Analysis, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition  async function run(caseFile: string): Promise<CaseAnalysisResult> {    if (!caseFile?.trim()) throw new Error('case analyst requires a non-empty case file')    emit('analyse', 'start')    const out = await invokeStructured({      adapter: config.adapter,      tool: submit(),      task: `CASE FILE:\n${fenceUntrustedContent(caseFile)}`,      parse: (a) => Analysis.parse(a),      skill,      memory: config.memory,      observers: config.observers,      onConfirm: config.onConfirm,      maxSteps: config.maxSteps ?? 3,    })    const { deadlineRisks, ...analysis } = out    emit('analyse', 'ok', `${deadlineRisks.length} deadline risk(s)`)    return { analysis, deadlineRisks, requiresAttorneyReview: true }  }  return {    name: 'legal-case-analyst',    run,    asHandle() {      return { name: 'legal-case-analyst', 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-analyst',  cases: [    {      input: `Analyze this case file. COMPLAINT (Dkt. 1, filed 2023-03-14, N.D. Cal.): Acme Robotics Inc. ("Plaintiff", counsel: Jane Patel, Patel & Voss LLP) v. Nimbus Cloud Systems LLC ("Defendant", counsel: Marc Oye, Oye Legal). Claims: (1) breach of the Master Services Agreement dated 2021-06-01, (2) misappropriation of trade secrets under the DTSA. ANSWER (Dkt. 22, filed 2023-04-30): general denial, affirmative defenses of waiver and unclean hands. Plaintiff served First Set of Requests for Production (Ex. C, p. 4) on 2023-05-10.`,      expected: (r: string) =>        /acme robotics/i.test(r) && /nimbus cloud/i.test(r) && /(N\.D\.\s*Cal|northern district|jurisdiction|venue)/i.test(r) && /(claim|breach|trade secret)/i.test(r),    },    {      input: `Produce a structured analysis. The accident occurred 2020-08-02. The personal-injury complaint was filed 2023-09-15 in California state court. California's statute of limitations for personal injury is two years. Parties: Lena Ortiz (plaintiff) v. Brightline Transit Authority (defendant). Identify any filing-deadline or limitations risk.`,      expected: (r: string) =>        /(statute of limitations|limitations|deadline|time-barred|untimely)/i.test(r) && /(risk|2 year|two year)/i.test(r),    },    {      input: `Extract procedural posture and key dates from this docket. Dkt. 1 Complaint filed 2024-01-10. Dkt. 15 Motion to Dismiss filed 2024-02-20. Dkt. 31 Order granting in part / denying in part MTD, entered 2024-05-03. Dkt. 40 Amended Complaint filed 2024-05-24. Case is now in discovery; fact-discovery cutoff is 2024-11-30.`,      expected: (r: string) =>        /(procedural posture|posture|discovery|motion to dismiss)/i.test(r) && /2024/.test(r),    },    {      input: `Analyze the parties, jurisdiction, and venue for this matter. The only document provided is a single email reading: "Hi team, please pull the file when you get a chance. Thanks." There are no pleadings, no caption, and no party names in the record.`,      expected: (r: string) =>        /not in record/i.test(r),    },  ],}

Was this agent useful?

Your response helps us prioritize agent quality.

Keep exploring

Related agents

View category