Your app as a data layer for AI
Every app Fayz generates is born agent-ready. It isn't a feature you switch on later: the same manifest that describes screens, routes and data also describes, as a typed contract, what an AI is allowed to read and do in that app. An agent that understands the contract can check the calendar, summarize the books or adjust inventory — without you writing an AI endpoint from scratch.
This page covers the aiTools contract, what you get for free, how the chat shell connects to an LLM, and how your own plugin joins this layer.
The aiTools contract
Any plugin can declare an aiTools field in its manifest (PluginManifest.aiTools?: PluginAITool[], in @fayz-ai/core). Each tool is a function an agent can call, described in a format LLMs understand natively — the same function calling shape OpenAI and Anthropic use:
type PluginAITool = {
id: string // 'agenda.create-appointment'
name: string // 'createAppointment' — the name the LLM calls
description: string // what the tool does, in natural language
mode: 'read' | 'persist' // does it only read data, or write/change state?
parameters?: { // JSON Schema: type:'object', properties, required
type: 'object'
properties: Record<string, { type: string; description?: string }>
required?: string[]
}
permission?: { feature: string; action: 'read' | 'create' | ... }
suggestions?: { label: string }[] // suggestion chips in the chat
category?: string
}
Two details matter:
modeseparates reads from writes.readqueries;persistcreates or changes state. The shell uses this to filter tools, and so you can require confirmation before anypersist.permissionbinds the tool to access control. The same feature and permission that guards the screen guards the tool. An agent running as a given user only sees the tools that user could use.
14 of the 22 plugins already declare aiTools today — agenda, conversations, crm, dashboard, financial, forms, inventory, marketing, menu, orders, shop, tables, tasks and admin. Enable the plugin and you get its tools in the contract.
Real examples from the SDK
These are tools that exist in plugin code today — not invented examples:
| Plugin | Tool | mode | What it does |
|---|---|---|---|
agenda | listAppointments | read | Lists appointments for a date or period, filterable by professional. |
agenda | createAppointment | persist | Creates an appointment for a client with a professional and a service. |
agenda | checkAvailability | read | Checks a professional's free slots on a given date. |
menu | toggleMenuItemAvailability | persist | Marks a menu item as available or sold out. |
financial | getRevenue | read | Returns revenue for a period. |
orders | createOrder | persist | Opens a new order. |
inventory | getLowStock | read | Lists items below the reorder point. |
conversations | sendMessage | persist | Sends a message in a conversation. |
dashboard | getKpiSummary | read | Summarizes the business KPIs. |
What you get for free: core tools + registries
On top of what each plugin declares, the Fayz shell (@fayz-ai/saas) injects three core tools that are always present, defined in core-ai-tools.ts:
getBusinessSummary(read) — the business at a glance.getTeamMembers(read) — who's on the team.navigateTo(read) — takes the user to a screen.
And there's a multiplier: every non-readonly registry (entity) in your app automatically becomes a list<Entity> tool. Register products and you get listProducts without writing anything — the comment in the code says it literally: "Each registry gets a basic 'list' tool so plugins get AI capabilities for free." Your entities become queryable by an agent just by existing.
The chat shell — and the honest boundary
The shell ships the UI: a chat FAB and a panel (ChatFab, ChatPanel), with suggestion chips sourced from aiTools and filtering by permission and vertical via the useAITools hook. Users see the right tools and ready-made suggestions.
But useChat is bring-your-own-endpoint (BYO endpoint). It POSTs the conversation history to an apiEndpoint in OpenAI format that you configure. With no endpoint, the response is a demo mock.
The SDK does not execute tools. PluginAITool has no handler — it is a schema, not an implementation. Receiving the tool_call from the LLM, running the action against Supabase (honoring RLS) and returning the result is the job of the app owner's backend. The SDK gives you the contract and the UI; the execution loop is yours. Never assume Fayz "runs the agent" for you.
The target architecture, honest about what exists:
┌──────────────┐ history (tools stay in your backend) ┌────────────────────┐
│ ChatPanel │ ───────────────────────────────────────▶ │ your apiEndpoint │
│ (shell UI) │ │ (your backend) │
│ useAITools │ ◀─────────────────────────────────────── │ + LLM (Claude/…) │
└──────────────┘ response / tool_call └─────────┬──────────┘
▲ │ runs the tool
│ aiTools schemas ▼
│ (from the manifest) ┌────────────────────┐
└───────────────────────────────────────────────── │ Supabase + RLS │
│ (tenant data) │
└────────────────────┘
The shell supplies the schemas (what exists) and the UI. Your endpoint supplies the LLM and the execution. RLS guarantees the agent only touches the right tenant's data.
Practical scenarios
Written as target architecture on top of what already exists — the schemas are ready; you just need to plug in the endpoint:
- An assistant that checks the calendar and books. The LLM receives
listAppointments,checkAvailabilityandcreateAppointment. "Can you fit Sarah in tomorrow morning?" → it callscheckAvailability, picks a slot and proposescreateAppointment(which ispersist— so you ask for confirmation). - Financial summaries over chat.
getRevenue+getKpiSummaryanswer "how was this week?" with real tenant numbers, not estimates. - An inventory agent.
getLowStockfinds what's running out; a restockpersist(once you declare one) closes the loop.
A real aiTool, in JSON
The schema your endpoint hands to the LLM — adapted from the real createAppointment in plugin-agenda:
{
"name": "createAppointment",
"description": "Creates a new appointment for a client with a specific professional and service.",
"parameters": {
"type": "object",
"properties": {
"client": { "type": "string", "description": "Client name" },
"professional": { "type": "string", "description": "Professional name" },
"service": { "type": "string", "description": "Service name" },
"date": { "type": "string", "description": "Date (YYYY-MM-DD)" },
"time": { "type": "string", "description": "Time (HH:MM)" }
},
"required": ["client", "professional", "service", "date", "time"]
}
}
mode: 'persist' and permission stay in the manifest (they never reach the LLM) — they're what the shell and your backend use for gating and confirmation.
How your own plugin joins this layer
It's simple: declare aiTools in the manifest. fayz create plugin already emits the (empty) array — you just fill it in. A well-built tool:
- Has a clear verb-noun
name(listLeads,createOrder). - Declares an honest
mode—persistfor anything that changes state. - Describes
parameterswith adescriptionon every field (the LLM reads them). - Ties
permissionto the same feature as the screen.
Once that's done, your plugin shows up in the chat, in the suggestions and in the contract the agent reads. For the full field list, see the Plugin manifest reference. To connect an agent to this Dev Center, see Connect your agent.