Skip to content

Tools Guide

A tool is a business action that the agent is authorized to trigger. This page covers how to write good tools, their lifecycle, and common mistakes to avoid.


What Makes a Good Tool

A tool should have:

  • A stable name (verb + noun, e.g. add_to_cart, archive_ticket)
  • A clear business description (what it does for the user, not how it works internally)
  • A precise argument schema (with Zod or equivalent)
  • A coherent HITL risk level reflecting real user impact
  • A handler that performs the action and returns a useful result

Dynamic Lifecycle

Client tools are dynamic. They live at the rhythm of the interface:

UI CycleAgentic UI SDK Effect
Component mountedTool can be registered
Component destroyedTool must be removed
NavigationTool list changes
Context modifiedServer receives a new CONTEXT_UPDATE

This model prevents giving the LLM a global list of out-of-context actions. The agent sees only the actions relevant to the current screen.

Per-SDK Integration

SDKDeclarationRegistrationCleanup
ReactuseAgentTool()useEffect() registers on mountHook cleanup, unless global: true
VueuseAgentTool()onMounted() registersonUnmounted() removes, unless global
Svelteuse:agentToolSvelte action registers on nodedestroy() removes
Angularservice, directive, or resolverService registers in OwlLayerClientOnDestroy / explicit cleanup
BrowserOwlLayer.registerTool() or auto-discoveryRuntime registers as global or DOM-discoveredunregisterTool() or DOM removal detected

Local vs Global Tools

Not all tools should follow a specific component.

A local tool must disappear with its component. Examples: select_product_card, edit_current_row, open_visible_modal.

A global tool can survive navigation. Examples: navigate, open_cart, set_theme, logout. Declare it explicitly as global or place it in a central resolver.

Practical rule: if the user can no longer see the object or screen in question, the LLM should no longer see the corresponding tool.


Execution Contract

When the server sends a TOOL_CALL, OwlLayerClient finds the matching local tool, executes its handler, and returns a TOOL_RESULT.

The key contract: The client runtime awaits only the Promise returned by the tool handler.

All async work necessary for the result must be awaited or returned in that handler.

Correct

ts
owllayer.registerTool(
  { name: 'archive_ticket', description: 'Archive the current ticket' },
  async ({ ticketId }) => {
    const result = await api.archiveTicket(ticketId);
    return { archived: true, id: result.id };
  }
);

Incorrect

ts
owllayer.registerTool(
  { name: 'archive_ticket', description: 'Archive the current ticket' },
  ({ ticketId }) => {
    api.archiveTicket(ticketId); // detached!
    return { archived: true };
  }
);

In the second example, the API call is detached. OwlLayer may send TOOL_RESULT before the actual action completes.

Common Pitfalls

  • Promises launched without return or await
  • RxJS subscribe() not converted to Promise when the result matters
  • setTimeout() used as an un-awaited side effect
  • State mutations triggered after the handler returns that are part of the expected result

Angular + RxJS

In Angular apps using RxJS, prefer an explicit Promise when the tool depends on the result:

ts
import { firstValueFrom } from 'rxjs';

owllayer.registerTool(
  { name: 'load_order', description: 'Load the current order' },
  async ({ orderId }) => {
    const order = await firstValueFrom(orderService.load(orderId));
    return { orderId: order.id, status: order.status };
  }
);

All SDKs share the same execution contract: return a Promise that resolves only once the tool result is actually available.


Anti-patterns

These practices weaken the Agentic UI model:

  • Declaring all tools globally at startup instead of mounting them with their UI
  • Giving the LLM tools out of context (tool for a modal that isn't open)
  • Ambiguous tool names (do_action, handle_click)
  • Business logic in description instead of the handler
  • Returning success before async completes
  • Confusing context with action (context is read-only, tools are actions)
  • Risky action with risk: 'none' (payment without confirmation)
  • One tool per list item instead of a parameterized tool (e.g. one add_to_cart({ productId }) not 50 add_product_123)

The Agentic UI SDK works best when the application exposes few actions, but accurate, contextualized, and verifiable ones.

Released under the MIT License.