# Tools and widgets

In Witbitz, tools are how an agent reaches capability, and widgets are how tool results become shared application
state. The important distinction is that a widget is not one user's private chat decoration. It is part of the Space
that every member can see.

---

## Tools are declared by the Space

A Space's sealed config names the tools available to the agent:

```js
await client.create({
  agentName: 'Planner',
  persona: 'Plan trips with the group and keep decisions clear.',
  tools: ['search_places', 'show_places', 'show_chart', 'write_pdf']
})
```

The render opens this config during a turn and exposes the declared tools to the agent. If the config is sealed, the
platform cannot read the persona, tool list, admission policy, or model config at rest.

## The built-in catalogue

These are the tools a hosted Space can name in its config today. A Space only gets what it declares — an empty
`tools` list is an agent that can talk and nothing else.

| Tool | What it does | Produces |
|---|---|---|
| `search_places` | Query Google Places for candidates. | Text — the agent then picks |
| `show_places` | Put chosen places (by id) on the shared map. | `places` widget |
| `add_place` | Add one place to the existing map doc. | same `places` widget |
| `search_flights` | Query Duffel for real offers. | Text — the agent then picks |
| `show_flights` | Put chosen offers (by id) on the shared card. | `flights` widget |
| `set_itinerary` | Replace the whole day-by-day plan. | `itinerary` widget |
| `add_to_itinerary` | Append to the existing plan. | same `itinerary` widget |
| `show_chart` | Draw data as SVG. | `chart` widget |
| `show_diagram` | Draw a relationship graph as SVG. | `diagram` widget (same page) |
| `show_photo` | Put an image in the reply. | Sealed out-of-line blob |
| `read_file` | Read a shared PDF / Word / Excel / CSV. | Text |
| `read_url` | Fetch a public URL (SSRF-guarded). | Text |
| `write_pdf` | Generate a PDF from Markdown. | File on the reply |

**Query-then-show is deliberate.** `search_*` returns candidates to the agent; a second call puts the *chosen* ids on
the shared doc. The member sees a curated set, not a raw result dump, and the agent's choice is the part that gets
sealed into the room.

Two widget kinds are **not** agent-produced: `location` (a member shares live location from the composer) and `nav`
(turn-by-turn, opened from a place card). The client can also render `trip` and `shop` docs, which no current
first-party tool emits.

> Any shared view can be opened full-screen from the room's **⋯** menu — a widget is a living document, so reaching it
> must not depend on scrolling back to the message that first posted it.

## Tool results become room artifacts

Tools can return different kinds of results:

| Result | Example |
|---|---|
| Text | A searched page summary or calculation result. |
| Widget | A map, chart, diagram, itinerary, route, or flights card. |
| File | A generated PDF attached to the agent reply. |
| Image/blob | A sealed out-of-line photo or generated image. |
| Proposal | A high-stakes action waiting for human approval. |

The agent reply can carry pointers to widgets or files created during the same turn. The client renders those pointers
as live shared UI.

## Shared widget state

The `state` op stores a named shared widget document. A member or tool can set a patch and summary; later reads return
the latest state. The state is sealed with the room key.

```js
await client.state(room, mk, 'trip-plan', {
  days: [
    { name: 'Day 1', items: [{ text: 'Arrive and check in' }] }
  ]
}, 'One-day starter itinerary', 'Ada')
```

Because the document belongs to the Space, every member sees the same widget state.

## High-stakes tools become proposals

An agent should not directly run actions that spend money, change external systems, or cross a policy boundary.
Instead, the turn records a proposal. A human later approves or denies it:

```js
const pending = await client.pending(room, mk)

await client.decide(room, mk, pending.proposals[0].id, true, 'Ada')
```

Only an approved proposal executes, and only once.

Read: [Delegated authority](./delegated-authority.md)

## Air-gap behavior

When `AIRGAP=1` is enabled in an on-prem deployment, internet-touching tools are refused server-side. Local tools such
as chat, document reading, PDF generation, charts, and diagrams continue to work. Map/photo/search/flight tools need
either local replacements or must stay disabled.

Read: [Enterprise and on-prem](./on-prem.md)

## Code map

| Concern | File |
|---|---|
| Tool declaration and validation | `agent/affordance.mjs`, `agent/agentRecord.mjs` |
| Space tool execution | `agent/spaceHandler.mjs`, `agent/spaceService.mjs` |
| Shared widget docs | `agent/sessionStore.mjs`, `spaces/public/spaceClient.js` |
| Widget renderers | `spaces/public/*Widget.*` |
| Delegated actions | `agent/delegatedAuthority.mjs` |

**See also — the inverse direction:** a widget puts *your app's UI inside a room*; [Embed a Space](./embed.md) puts a
whole *room inside your app's page* (a cross-origin iframe + an origin-checked bridge, content-blind either way).
