Docs
/

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

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.

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

  1. Return small JSON, always { ok, ... } on writes — errors as

{ ok: false, error }, never throws the model can't read. Cap list sizes.

  1. "Show" tools drive the UI (select, focus, navigate) so the user sees

what the bot means. Selection/spotlight setters are capabilities too.

  1. Mark AI writes where the data model supports it (e.g. statements carry

source: 'ai') so provenance survives.

  1. One scope per page, domain:page id, enabled: gated on data readiness.

Keep 5–12 tools; more dilutes tool choice.

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

  1. 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").

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

  1. 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 (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt