N AgentNavaKit
agentnava.com →

What lives where

Widgets cross a clean boundary. The SDK ships the protocol; you ship the components.

PieceOwned byNotes
Widget event protocol (widget-update / widget-remove SSE) @agentnava/kit The typed contract between agent and UI. Lives with the SDK.
Agent emit API (ctx.emitWidget({ kind, props }), ctx.removeWidget(id)) @agentnava/kit Usable from any tool handler or workflow step.
Vanilla subscribe helper (subscribeWidgets(...)) @agentnava/kit Typed event stream you read from any JS stack.
Thin React drop-in (<AgentWidgets renderers={{ ... }} />) @agentnava/widgets-react · sibling MIT package · optional soon Wires subscribe + dispatch + mount in three lines. Stripe pattern — separate from the core SDK.
Your actual widget components (the JSX/HTML) Your code Your UI, your stack, your design system. You write the components; we deliver the data.
Widget starter templates (gallery of copy-paste components) agentnava.com Content offering, not an SDK dependency. Grab a starter and customize.
Workspace renderer (canvas pane on workspace.agentnava.com) AgentNava platform Renders any widget kind a workspace renderer is registered for. Automatic — install nothing.

How a widget flows from agent to screen

Your agent code Tool handler decides to surface a widget ctx.emitWidget({ kind: 'listings-grid', props: { rows } })
via @agentnava/kit
AgentNavaKit runtime Emits a typed SSE widget-update event Same stream as chat tokens, tool calls, and phase events. The protocol is the contract.
SSE over HTTPS
UI subscription <AgentWidgets renderers=…> or subscribeWidgets(...) Dispatches each event on kind. Filters by renderIn for the current surface.
kind → component
Your component Renders. Your JSX, your stack. Optional: start from a template at agentnava.com → Widgets and customize.

Emitting a widget from the agent

A tool handler (or workflow step) calls ctx.emitWidget with a kind name you control and props you define. Re-emitting with the same widgetId updates the widget; removeWidget unmounts it.

// agents/<agent-name>/tools/search-listings.ts
import { defineTool, t } from '@agentnava/kit';

export const searchListings = defineTool({
  name: 'search_listings',
  description: 'Search MLS for listings in a city',
  input: t.object({ city: t.string(), beds: t.number().optional() }),
  handler: async ({ city, beds }, ctx) => {
    const rows = await fetchMls(city, beds);

    // Tell the UI: render a 'listings-grid' widget with these props.
    ctx.emitWidget({
      widgetId: 'main-listings',          // optional; stable ID lets the same widget update in place
      kind:     'listings-grid',          // a name you invented, shared with your UI
      props:    { rows, columns: ['address', 'price', 'beds'] },
      renderIn: ['workspace', 'embed', 'own-ui'],  // optional; default = everywhere
    });

    return { rows };
  },
});

The shape of props is yours. The SDK doesn't validate it — your UI does. Type safety comes from a small shared file you keep in your project:

// shared/widgets.ts — imported by both your agent and your UI
export type WidgetKinds = {
  'listings-grid':  { rows: Listing[]; columns: string[] };
  'mortgage-calc':  { homePrice: number; down: number; rate: number };
  'status-banner':  { message: string; tone: 'info' | 'warning' };
};

Pass that to the generic version of emitWidget for compile-time checks against the kind + props:

import type { WidgetKinds } from '../shared/widgets';

ctx.emitWidget<WidgetKinds>({
  kind: 'listings-grid',
  props: { rows, columns: ['address', 'price', 'beds'] },   // type-checked against WidgetKinds['listings-grid']
});

Rendering in your UI

Two paths. Both subscribe to the same typed event stream.

React — @agentnava/widgets-react

One component. Pass a renderers object mapping each kind to a React component:

import { AgentWidgets } from '@agentnava/widgets-react';
import type { WidgetKinds } from './shared/widgets';

import { ListingsGrid }  from './components/ListingsGrid';
import { MortgageCalc }  from './components/MortgageCalc';
import { StatusBanner }  from './components/StatusBanner';

<AgentWidgets<WidgetKinds>
  agent="agt_2b8e"
  sessionId={sid}
  renderers={{
    'listings-grid': ListingsGrid,
    'mortgage-calc': MortgageCalc,
    'status-banner': StatusBanner,
  }}
/>

The drop-in handles SSE subscription, lifecycle (mount on first event, update on same widgetId, unmount on widget-remove), and renderIn filtering for whichever surface you're on.

Any other stack — subscribeWidgets

Subscribe to the typed stream yourself. Dispatch on kind:

import { subscribeWidgets } from '@agentnava/kit';
import type { WidgetKinds } from './shared/widgets';

const unsub = subscribeWidgets<WidgetKinds>(
  { agent: 'agt_2b8e', sessionId },
  (event) => {
    if (event.type === 'widget-update') {
      switch (event.kind) {
        case 'listings-grid':  mountOrUpdate(event.widgetId, ListingsGridVue, event.props); break;
        case 'mortgage-calc':  mountOrUpdate(event.widgetId, MortgageCalcVue, event.props); break;
        case 'status-banner':  mountOrUpdate(event.widgetId, StatusBannerVue, event.props); break;
      }
    } else if (event.type === 'widget-remove') {
      unmount(event.widgetId);
    }
  },
);

Works in Vue, Svelte, Solid, server-rendered HTML — anywhere you can read SSE and call a function.

Widget event protocol

The full contract. kind is any string you choose; props is any JSON-serializable object. Type safety is layered on top by your shared types.

type Surface = 'workspace' | 'embed' | 'own-ui';

type WidgetEvent<K = Record<string, unknown>> =
  | {
      type:     'widget-update';
      widgetId: string;            // stable per widget instance; re-emit to update in place
      kind:     keyof K & string;  // your kind name
      props:    K[keyof K];        // your props shape
      renderIn: Surface[];         // default: all surfaces
    }
  | {
      type:     'widget-remove';
      widgetId: string;
    };

These events appear alongside chat events in the same SSE stream — message-delta, tool-start, tool-end, phase, widget-update, widget-remove, done. The host UI dispatches on type; the widget renderer dispatches on kind.

renderIn — controlling surfaces

Every emitWidget call can declare which surfaces should receive the event:

ctx.emitWidget({
  kind:     'admin-stats',
  props:    { totals, errors },
  renderIn: ['own-ui'],          // hide from the AgentNava workspace and the embed widget
});

Renderers receive only events whose renderIn includes their surface. Default is all three surfaces.

Discovery (optional)

If you want operators of the workspace or other UIs to know what kinds an agent might emit, declare them in meta:

await an.agents.configure({
  // ...
  meta: {
    widgetKinds: ['listings-grid', 'mortgage-calc', 'status-banner'],
  },
});

This is metadata only — purely for discovery. The runtime does not enforce it; you can emit any kind at any time.