# leviathan-page-capabilities

Give the Leviathan widget live tools to read, edit, save and drive a page's own data — the save/edit pattern every Leumas surface repeats.


# Leviathan page capabilities — the save/edit pattern

Every Leumas product mounts the Leviathan widget (`@leumas/leviathan`) inside a
`CapabilityProvider` (`@leumas/capability-sdk`). A page can register **live
tools** with that provider; the widget sends them as function-call tools on
every chat turn, executes returned `clientToolCalls` **in the page** via the
registered handlers, and feeds the results back (up to 4 tool rounds per turn).
The [fast] badge on the widget shows the live count. Navigate away → the scope
unregisters → the tools vanish.

That loop is what turns Leviathan from "RAG over my data" into an **operator of
the page**: it reads real state, mutates it through the page's own save path,
and the user watches the UI update mid-conversation.

## The one hook

```jsx
import { useRegisterCapability } from '@leumas/capability-sdk';

useRegisterCapability([
  {
    name: 'add_statements',                       // snake_case verb
    description: 'What it does + WHEN to use it', // the model reads this
    parameters: { type: 'object', properties: { texts: { type: 'array', items: { type: 'string' } } }, required: ['texts'] },
    call: async ({ texts }) => {                  // may be async; return JSON-able
      await addStatements(texts, { source: 'ai' });
      return { ok: true, added: texts.length };
    },
  },
], {
  scopeId: 'graphs:editor',        // 'domain:page' convention — stable, explicit
  enabled: Boolean(doc),           // gate until data is loaded
  pageTitle: `Reasoning graph: ${doc?.name}`,
  pageInstructions: 'Context + ground rules the bot gets while THIS page is open.',
});
```

Fresh array literals per render are fine — registration is keyed on the tools'
*shape* (names/descriptions/parameters), and `call` always sees the latest
closures. Unregister-on-unmount is automatic.

## The save/edit rules (what makes the pattern solid)

1. **Tools call the page's EXISTING mutators** — the exact functions the
   buttons call (optimistic update → API verbs → refresh). Never write a
   parallel persistence path for the AI; then AI edits inherit every invariant,
   race guard and stats refresh for free, and are indistinguishable from hand
   edits.
2. **Read tools first, write tools second.** Ship a `get_*_overview` that
   returns the page's real state compactly, and tell the bot (in
   `pageInstructions`) to call it before editing. Grounded reads are what make
   the edits sensible.
3. **Return small JSON, always `{ ok, ... }` on writes** — errors as
   `{ ok: false, error }`, never throws the model can't read. Cap list sizes.
4. **"Show" tools drive the UI** (select, focus, navigate) so the user *sees*
   what the bot means. Selection/spotlight setters are capabilities too.
5. **Mark AI writes** where the data model supports it (e.g. statements carry
   `source: 'ai'`) so provenance survives.
6. **One scope per page**, `domain:page` id, `enabled:` gated on data readiness.
   Keep 5–12 tools; more dilutes tool choice.
7. **Extract to a `use<Domain>Capabilities.js` hook** beside the page — the
   page stays lean and the capability surface is reviewable in one file.

## Reference implementations

- **Canonical toy:** `products/leumas-studio/src/admin/demos/DemoContacts.jsx`
  (add/find/tag contacts — the minimal shape).
- **Real save/edit surface:** `products/leumas-studio/src/admin/graphs/useGraphCapabilities.js`
  (reasoning graphs: overview/list/inspect reads + add/edit/delete/rename/mute
  writes + select/focus canvas drivers), registered in `GraphEditor.jsx`; the
  gallery registers a lighter `list/open/create` set in `GraphsHome.jsx`.
- SDK internals: `shared/packages/capability-sdk/src/{CapabilityProvider.jsx,useRegisterCapability.js}`;
  widget tool loop: `shared/packages/leviathan/src/Widget.jsx`.

## Checklist for a new surface

1. Identify the page's mutators + state (the ones the UI already uses).
2. Write `use<Domain>Capabilities.js`: overview read → detail read →
   writes-via-mutators → show tools. snake_case names, JSON-schema parameters,
   `{ ok }` returns.
3. Call it from the page component; scope `domain:page`; `enabled` on data
   readiness; `pageInstructions` = context + "read overview first" + any
   domain grammar (e.g. "statements are single declarative sentences").
4. Verify: open the page → widget [fast] count rises → ask the bot to read, then to
   make one small edit → the UI updates and the change persists on reload →
 navigate away → [fast] drops.
5. Server-side twins: if the domain also has an adapter (functioncalls/MCP),
   keep page tools for *stateful, open-document* work and adapter tools for
   *stateless* work — don't duplicate one as the other.


---
Source: .claude/skills/leviathan-page-capabilities/SKILL.md
Canonical: https://docs.leumas.tech/p/skills/leviathan-page-capabilities
