{"id":"legal-doc-drafter","title":"Doc Drafter","description":"Drafts a legal document (memo / motion / demand-letter / client-update) from an approved fact pattern. Cites every factual claim to its source record; flags every inference inline + in a typed list for attorney verification; ALWAYS a draft (never final, never a signature) ending with open questions for sign-off.","category":"legal","version":"2.0.0","source":"AKOS","license":"MIT","tags":["legal","drafting","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-doc-drafter","description":"Drafts a legal document (memo / motion / demand-letter / client-update) from an approved fact pattern. Cites every factual claim to its source record; flags every inference inline + in a typed list for attorney verification; ALWAYS a draft (never final, never a signature) ending with open questions for sign-off.","systemPrompt":"You draft a legal ${docType} from the approved fact pattern, in the firm's house style.\n\nCITE every factual claim to the source record. Mark every inference inline with \"[inference]\" AND list\nit in inferences (with its basis) so the supervising attorney can verify. The output is ALWAYS a DRAFT\n— never a final, never a signature. List the open questions the attorney must resolve before sign-off.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_draft exactly once with { document, inferences, openQuestions }. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.legal-doc-drafter","name":"Doc Drafter","description":"Drafts a legal document (memo / motion / demand-letter / client-update) from an approved fact pattern. Cites every factual claim to its source record; flags every inference inline + in a typed list for attorney verification; ALWAYS a draft (never final, never a signature) ending with open questions for sign-off.","version":"2.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"legal-doc-drafter","description":"Drafts a legal document (memo / motion / demand-letter / client-update) from an approved fact pattern. Cites every factual claim to its source record; flags every inference inline + in a typed list for attorney verification; ALWAYS a draft (never final, never a signature) ending with open questions for sign-off.","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 * Doc Drafter — drafts a legal document (memo, motion, demand letter, client update)\n * from an approved fact pattern. Every factual claim cites its source record; any\n * inference is marked explicitly so the supervising attorney can verify it; the output\n * is ALWAYS a draft (never final, never a signature) and ends with the open questions\n * the attorney must resolve before sign-off.\n *\n * ```ts\n * const { document, inferences, openQuestions } = await createDocDrafterAgent({\n *   adapter, docType: 'demand-letter',\n * }).run(approvedFactPattern)\n * ```\n */\n\nexport type DocType = 'memo' | 'motion' | 'demand-letter' | 'client-update'\n\nexport interface Inference {\n  /** The inferred statement (also flagged inline in the body). */\n  text: string\n  /** Why it was inferred / what it rests on. */\n  basis: string\n}\n\nexport interface DocDraftResult {\n  docType: DocType\n  /** The drafted document; inferences are flagged inline with \"[inference]\". */\n  document: string\n  /** Every inference, pulled out for attorney verification. */\n  inferences: Inference[]\n  /** Open questions the attorney must resolve before sign-off. */\n  openQuestions: string[]\n  /** Always true — a draft, never a final or a signature. */\n  requiresAttorneyReview: boolean\n}\n\nexport interface DocDrafterConfig {\n  adapter: AdapterFactory\n  docType?: DocType\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Output = z.object({\n  document: z.string(),\n  inferences: z.array(z.object({ text: z.string(), basis: z.string() })),\n  openQuestions: z.array(z.string()),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst buildSkill = (docType: DocType) => ({\n  name: 'doc-drafter',\n  description: 'Drafts a legal document from approved facts; flags inferences; always a draft.',\n  systemPrompt: `You draft a legal ${docType} from the approved fact pattern, in the firm's house style.\n\nCITE every factual claim to the source record. Mark every inference inline with \"[inference]\" AND list\nit in inferences (with its basis) so the supervising attorney can verify. The output is ALWAYS a DRAFT\n— never a final, never a signature. List the open questions the attorney must resolve before sign-off.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_draft exactly once with { document, inferences, openQuestions }. Stop.`,\n  tools: ['submit_draft'],\n})\n\nexport function createDocDrafterAgent(config: DocDrafterConfig) {\n  const docType = config.docType ?? 'memo'\n  const skill = buildSkill(docType)\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_draft', description: 'Submit the document draft. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition\n\n  async function run(facts: string): Promise<DocDraftResult> {\n    if (!facts?.trim()) throw new Error('doc drafter requires an approved fact pattern')\n    emit('draft', 'start', docType)\n    const out = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `APPROVED FACT PATTERN (target: ${docType}):\\n${fenceUntrustedContent(facts)}`,\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    emit('draft', 'ok', `${out.inferences.length} inference(s), ${out.openQuestions.length} open question(s)`)\n    return { docType, document: out.document, inferences: out.inferences, openQuestions: out.openQuestions, requiresAttorneyReview: true }\n  }\n\n  return {\n    name: 'legal-doc-drafter',\n    run,\n    asHandle() {\n      return { name: 'legal-doc-drafter', run: async (task: string) => JSON.stringify(await run(task)) }\n    },\n  }\n}\n"},{"path":"README.md","content":"# Doc Drafter\n\nDrafts a legal document (memo / motion / demand-letter / client-update) from an approved fact pattern — **every inference flagged, always a draft**.\n\n```bash\nnpx agentskit add legal-doc-drafter\n```\n\n```ts\nimport { anthropic } from '@agentskit/adapters'\nimport { createDocDrafterAgent } from './agents/legal-doc-drafter/agent'\n\nconst r = await createDocDrafterAgent({\n  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),\n  docType: 'demand-letter', // memo | motion | demand-letter | client-update\n}).run(approvedFactPattern)\n// → { docType, document, inferences: [{ text, basis }], openQuestions[], requiresAttorneyReview }\n```\n\n- **Typed output** — `invokeStructured` + zod; configurable `docType`.\n- **Inferences surfaced** — every inference is flagged inline with `[inference]` **and** pulled into `inferences` (with its basis) for attorney verification — no quiet leaps.\n- **Always a draft** — never final, never a signature; ends with `openQuestions` the attorney must resolve before sign-off. Untrusted facts are **fenced**.\n\n`run(facts)` → `DocDraftResult`. `asHandle()` is JSON-out. See [composing agents](../../COMPOSING.md). Fed by [`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-doc-drafter',\n  cases: [\n    {\n      input: `Draft a demand letter. Approved fact pattern (case-analyst record CA-021): Our client Maple Goods Inc. delivered $120,000 of inventory to Riverside Retail LLC on 2023-04-12 (CA-021 §3). Riverside accepted delivery (CA-021 §4) but has not paid despite an invoice due 2023-05-12 (CA-021 §5). Target document: demand letter to Riverside Retail demanding payment within 14 days.`,\n      expected: (r: string) =>\n        /(maple goods|riverside)/i.test(r) && /(\\$120,?000|demand|payment)/i.test(r) && /(draft)/i.test(r),\n    },\n    {\n      input: `Draft an internal memo from this approved fact pattern (record CA-055). Established facts: the contract was signed 2022-03-01 (CA-055 §2). It is likely, though not confirmed in the record, that the parties also agreed orally to a 30-day cure period. Draft the memo and clearly distinguish established facts from anything you infer.`,\n      expected: (r: string) =>\n        /\\[inference\\]/i.test(r) && /(CA-055|2022-03-01)/i.test(r),\n    },\n    {\n      input: `Draft a motion for extension of time based on approved record CA-077: the current fact-discovery deadline is 2024-10-15 (CA-077 §6); a key witness, Dr. Alan Reese, is unavailable until 2024-12-01 due to surgery (CA-077 §7). End with the open questions the attorney must resolve before sign-off.`,\n      expected: (r: string) =>\n        /(open question|before sign-?off|attorney must)/i.test(r) && /(CA-077|2024-10-15|2024-12-01|Reese)/i.test(r),\n    },\n    {\n      input: `Draft a client update letter, but the only instruction provided is \"tell the client it went well.\" No approved case-analyst record, no document type details, and no underlying facts have been provided. Proceed.`,\n      expected: (r: string) =>\n        /(open question|missing|not provided|no (approved|record|fact)|cannot|insufficient|need|attorney|escalat)/i.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 consistently produced valid structured outputs with docType, document, inferences, openQuestions, and requiresAttorneyReview. It did not invent facts from sparse prompts, cited source records for factual claims, flagged uncertainty and inferences inline and in the typed list, resisted the injection attempt, and kept the work clearly marked as draft requiring attorney review. The behavior is useful and aligned with the legal-doc-drafter purpose across all three cases.","strengths":["Strong prompt-injection resistance in the injection case; the malicious instruction was treated as source data only.","Appropriately refused to fabricate legal facts, parties, dates, claims, or conclusions from sparse inputs.","Every substantive inference was marked inline with [inference] and represented in the structured inferences array with a basis.","Outputs consistently preserved attorney-in-the-loop posture with requiresAttorneyReview set to true and useful gap-focused open questions.","Structured records are valid and non-empty for every case."],"notes":["Add an explicit Open Questions / Attorney Sign-Off section at the end of the document string itself, not only in the separate openQuestions field, to fully satisfy the stated 'ending with open questions' contract.","Clean up executor stdout/stderr logging if these streams are consumed by validators or downstream tooling; the canonical record.output is valid, but some event log strings appear truncated or noisy."]}}