Skip to main content
These patterns help your LLM agent produce reliable, efficient document edits. The first group applies to every integration; the rest is split by preset — core (recommended) and legacy work differently enough that their playbooks are separate.

Practices for every integration

Use the bundled system prompt — from the same preset as your tools

Each preset ships the prompt it was designed and evaluated with. Load both through the toolkit so they can never mismatch:
import { createAgentToolkit } from '@superdoc-dev/sdk';

const { tools, systemPrompt, dispatch } = await createAgentToolkit({
  provider: 'openai',
  preset: 'core',
});
Extend it with task-specific rules rather than replacing it:
const fullPrompt = `${systemPrompt}\n\n## Additional rules\n- Use tracked changes for all edits.\n- End with a one-sentence summary of what changed.`;

Feed errors back

Dispatch failures are written for the model to act on. Pass them back as tool results — most models self-correct on the next turn:
try {
  const result = await dispatch(doc, call.function.name, JSON.parse(call.function.arguments));
  messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) });
} catch (err: any) {
  // Return the error as a tool result: the model will see it and adjust
  messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify({ error: err.message }) });
}

Cache tools and prompts

Tools and the system prompt don’t change between requests. Build the toolkit once at startup and reuse it across conversations. On Anthropic, also pass cache: true so the tool array carries prompt-caching markers (see token budget).
let kit: Awaited<ReturnType<typeof createAgentToolkit>> | null = null;

async function ensureToolkit() {
  kit ??= await createAgentToolkit({ provider: 'openai', preset: 'core' });
  return kit;
}

Use tracked changes for review workflows

Set changeMode: "tracked" (or instruct it in your appended prompt rules): every AI edit lands as a tracked change users can accept or reject in SuperDoc or Microsoft Word. A “suggest vs. apply directly” toggle in your UI maps 1:1 to this flag.

Add examples for repeatable workflows

If the same kind of edit runs across many documents, include a concrete tool call example in your system prompt. Models that see a working example of the exact invocation produce correct calls more reliably than models that only see the schema.

Pin your model version

Use a specific model ID rather than an alias. Aliases can change behavior between releases and break working tool call patterns. The core surface is two tools — superdoc_inspect and superdoc_perform_action — with 40 named actions that resolve their own targets and return receipts. The playbook follows from that.

Inspect narrowly, then act

A typical edit is 2–3 calls: one narrow inspect, one action, and (only when the receipt says so) a follow-up. Steer the model away from full-document reads:
  • {countsOnly: true} for orientation; includeDomains to fetch only what the task needs.
  • On large documents, window with blockOffset/blockLimit — ordinals are absolute, so windows line up.

Trust receipts, not vibes

Every action returns real pre/post evidence. Teach your loop (and your users) to read it:
  • status: "ok" with verificationPassed: true — done; don’t re-inspect “to be sure.”
  • status: "partial" — some of the work landed; the receipt says which part. Re-inspect, then fix forward.
  • status: "failed" — nothing changed unless the receipt explicitly says otherwise. errors[].message usually contains the exact recovery step, and recovery/revertHint are machine-usable.

Batch with selectors, not with repeated calls

Actions absorb batching: add_comments takes selectors[] for many blocks in one call; replace_text takes edits[]; format_text takes targetTexts[]. One action call with a batch argument beats N calls every time — cheaper, atomic, one receipt.

Narrow the surface for your product

If your product should never delete tables or fill placeholders, exclude those actions — excludeActions removes them from the enum, the prompt, and dispatch in one move. A smaller surface is also cheaper per turn.

Prompt examples

Tested against the core action surface — use as inspiration or few-shot examples:
  • “Find the termination clause and rewrite it to require 30-day written notice. Use tracked changes.”
  • “Replace all references to ‘Contractor’ with ‘Service Provider’ as tracked changes.”
  • “Add a comment to every paragraph that mentions personally identifiable information: ‘Verify PII handling.’”
  • “Number the unnumbered obligation at the end of section 2 like its siblings.”
  • “Accept all formatting revisions but leave text edits pending review.”
  • “Move the PREAMBLE section after SCHEDULE A.”
  • “Add a 2×3 table under the second heading with headers Owner and Stage, then style it.”

Legacy preset

The legacy surface is low-level: the model searches for handles, then edits by address. If you’re on it, these patterns matter — and migrating to core removes most of them.

Read first, search, then edit

A typical edit takes 3-5 tool calls:
  1. superdoc_get_content: understand what’s in the document
  2. superdoc_search: find the exact location (returns stable handles/addresses)
  3. Edit tool (superdoc_edit, superdoc_format, etc.): apply the change using targets from search
Handles from search results point to the exact right location. If the model guesses a block address instead of searching, edits land in the wrong place. Search again after every mutation — refs expire when the revision bumps.

Prefer markdown insert for multi-block creation

When creating multiple headings and paragraphs, use superdoc_edit with type: "markdown" instead of one superdoc_create per block:
{
  "action": "insert",
  "type": "markdown",
  "value": "## Executive Summary\n\nThis agreement governs the terms of service.\n\n## Key Provisions\n\nThe following provisions apply to all parties."
}
After inserting, apply formatting in a single superdoc_mutations batch using format.apply steps — one step per block or range. This reduces a workflow that might otherwise take 40+ calls down to 4: read, search, insert, format.

Use focused tools; superdoc_mutations is an escape hatch

For straightforward edits, use the focused intent tools — they validate arguments and give clear errors. Reach for superdoc_mutations only when you need preview/apply semantics, an atomic multi-step batch, or a workflow that would otherwise require refreshing targets between steps (it resolves all targets before any step executes).

Choose formatting values from the document

Don’t hardcode formatting values — read them from existing content and match:
  • Body text: read fontFamily, fontSize, color from non-empty paragraphs; set bold: false for body.
  • Many DOCX documents report underline: true on all blocks due to style inheritance — a DOCX artifact, not intentional formatting. Don’t carry it forward.
  • Headings: read from existing heading blocks; confirm bold/centering against the document rather than assuming.

Prompt examples

  • “Format the entire document in Times New Roman, 12-point.”
  • “Make all Heading 2 paragraphs bold and set them to 14-point font.”
  • “Replace every occurrence of ‘FY2024’ with ‘FY2025’ throughout the document.”
  • “Insert CONFIDENTIAL: DO NOT DISTRIBUTE at the very top, bold, red, 14pt.”
  • “Convert the list of references at the end into a numbered list and restart numbering at 1.”