# 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. ## 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](./trust-model.md#delegated-authority) ## 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](./deployment-options.md#your-boundary) ## 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).