Skip to main content

Apps

An app adds commands to your bot. Ask it for a quote book and /quote, /quotelist and /learn start working in your chat — no deploy, no code in your repository.

Apps are data, not code. An app is a declared shape: what it stores, what operations it performs, and which commands trigger them. That is why one can be created during a conversation and be live moments later, and why it cannot do anything the platform has not given it a way to do.

Derived from backend/src/agent-apps/. Internally this subsystem is called Weft; you will see the name in logs and API paths.

Availability

Apps are off by default and enabled per tenant.

GuaranteeStateActive when
Apps available to a tenant⚙️ Off by defaultthe mesa-apps-enabled flag is on for that tenant. Resolved per tenant, not globally
Commands dispatch to apps✅ ActiveTelegram. No other platform dispatches app commands today

The flag is checked per message, so turning it on or off takes effect without a restart. With it off, a command bound to an app falls back to a built-in implementation where one exists, and otherwise does nothing.

Command bindings are cached for 15 seconds, so publishing, enabling or disabling a command takes effect within that window — again with no restart.

Starting from a template

Three templates ship. Installing one creates an app and its commands in a single step; the command names are defaults you can change per installation.

Quote Book

Save and recall quotes by person, with an optional tag.

CommandDoes
/learnSaves the message you replied to as a quote
/quoteFetches a quote by person, optionally by tag
/quoterandomFetches a random quote
/quotelistLists a person's quotes

Birthday Tracker

CommandDoes
/birthdayReads a birthday — or sets one if you pass a date
/birthdaysetSets a birthday explicitly

/birthday is a single command that reads or writes depending on its argument. The write is a separate grant, so a chat can be given the read without the write.

Counter

CommandDoes
/counterIncrements a shared counter and replies with the old and new values

Building your own

You do not write the app yourself. You describe it to the bot, and it drafts, checks and publishes it for you.

Who can author from chat: drafting, publishing and creating apps from chat requires your messaging account to be linked (via /link) to a web account that holds the tenant-admin role. Anyone else who asks the bot to create or change an app gets a clear refusal explaining how to get access. Using the apps — typing their commands — needs no link and no role.

Four operations sit behind that conversation:

OperationWhat happens
draft.saveCompiles your description into a spec, or returns the specific problems with it
dry runRuns the real engine against your draft, then rolls everything back
draft.publishMakes the commands live. Requires your explicit confirmation
app.instantiateCreates an app from a template

The dry run is a real run

A preview that passes against a fake engine tells you nothing about the real one, so previews here execute the actual interpreter inside a transaction that is then rolled back. Index selection, unique-constraint collisions and timeouts all surface. Nothing survives the preview — not even the sandbox app.

A publish will only accept a dry run from the last 10 minutes. An older preview forces a re-run, so the confirmation you give always reflects current behaviour.

Publishing again

Publishing an app that already exists republishes it in place. Commands that disappeared from the spec are removed, new ones added, and commands whose action changed are repointed — so a command keeps its enabled state across a republish.

You are warned before confirming if a republish would change a stored layout or drop fields that the live version has data in.

Safety you do not have to think about

Concurrency is handled by the compiler, not by you. Locked reads and uniqueness claims are emitted when a spec is compiled, so two people running the same command at the same instant cannot corrupt each other's writes. There is no way to author an unsafe action, because authors are never given the choice.

Apps and your agent

An app's operations are available to your agent as well as to people typing commands. When the agent uses one it goes through exactly the same path — the same permission check, the same audit record — so an agent can only do what some command binding already permits for your tenant.

Authoring has one extra requirement, and it applies to the agent too: when the agent drafts or publishes an app, it acts for the person who asked, and that person's messaging account must be identity-linked to a tenant admin. There are two ways to hold the authoring permission, one per surface — in the admin console it is your web account's tenant-admin role; in chat it is an identity link from your messaging account to such a web account. There is no third path.

Limits

LimitValue
Active apps per tenant25
Records per app10,000
Commands per minute, per person per app30
Drafts per tenant100
New apps per hour, per person5 (max 2 per minute)
Change-feed history90 days
Audit history400 days

Warnings surface at 80% of a quota; the limit itself blocks at 100%.

A repeatedly failing operation is tripped out rather than left to fail forever: 20 failures — or 5 timeouts — within a rolling 5-minute window disables that operation until it recovers. Every action also runs under a 2-second database timeout.

Managing apps

The admin console's Apps section lists your apps, the commands each one binds, and their health, and lets you enable or disable individual commands. The same operations are available over the API under /api/admin/weft — see the API reference.

It also lets you edit a custom app's spec directly: the editor shows validation issues inline, runs a dry-run and shows its preview, and publishes with a single Save & publish — the same draft-validate-preview-publish ceremony the bot uses, never bypassed. Republish warnings (for example a changed index layout or a removed field) must be explicitly acknowledged before the publish goes through. If a publish fails, the draft stays saved and the editor says so unmistakably; retrying is safe. Template apps have no editable spec — only custom apps built from a description can be edited here.

Apps in the web app

The web app has an Apps area of its own (in the navigation dock). Every signed-in member of a tenant can browse it read-only: the list of the tenant's apps, and for each app its commands and the current published spec. The commands view is command-first: one row per command — /command, what it does, and an at-a-glance rollup of where it is enabled ("enabled on 3 of 3 bots", "on 1 of 3 bots", "disabled", or "not installed") — and expanding a row shows exactly which bots carry it, by name and platform. An app installed on three bots shows each command once, never three times.

Each app's current spec now renders as a guided summary rather than a wall of JSON: entities with their fields and bounds, actions as humanized step lists ("3 steps · replies with audio"), and commands with their argument shapes. Anything a newer spec grammar adds before this view learns it appears as a clearly marked raw-JSON island — the page never breaks. The full raw JSON stays one disclosure away.

Members see no edit controls anywhere. Holders of the tenant-admin role additionally get an Edit button on each custom app, which opens the same edit flow as the admin console — spec editing with inline validation issues, a dry-run preview, acknowledgment checkboxes for republish warnings, and one Save & publish — so quick changes do not require switching to the admin console. The same rules apply: template apps are not editable, a failed publish keeps the draft and says so unmistakably, and retrying is safe.

The editor itself opens with two tabs: Guided and Advanced (the JSON editor with the unchanged save-validate-preview-publish ceremony). In Guided, every section is editable with structured controls. Commands: add, remove, rename, describe, and map arguments against the target action's declared input. Data: entities and their fields — name, type, bounds (the server's autofill defaults show as placeholders), a queryable toggle with its three-slot meter, PII classification (required for suggestive names), and key/uniqueness constrained to the declared fields — with the five-entity ceiling metered at the point of edit. Actions: ordered step lists with typed cards for every step kind (read, query, compute, put, delete, reply, require), the reply's three forms (text, audio, record-driven), query filters and sorting constrained to queryable fields, and guards — each step names the variables in scope at its position, and an expression reference lists exactly the functions the server accepts. A delete step is flagged where you add it: publishing it requires the high-risk approval. Argument-conditional arms and replied-to capture still edit as inline JSON islands.

Both tabs drive the same ceremony and the same guarantees: any edit invalidates the previous validation, acknowledgments, and dry-run preview, so a stale preview can never publish; after a save the editor adopts the spec exactly as the server stored it, and when the server adjusted values while saving (autofix), a notice lists exactly which ones. A header badge always shows whether your current state is saved as a draft, and leaving the page with unsaved changes asks first. Validation problems appear on the control they belong to — a command-name collision lands on that command's name field, with the server's suggested fix.

In the commands view, tenant admins can also enable or disable a command per bot with an inline toggle (taking effect within the binding cache window, like every binding change) and install the app on another bot from a picker that offers only the tenant's not-yet-bound bots.

Tenant admins also get a read-only Records browser on each app's detail page: the app's stored data grouped by entity, opened progressively — entity group → record row (id, version, who updated it and when) → the record's JSON, collapsed beyond its first level. An entity filter and a load-more control page through larger sets (the server caps a page at 200 records). Members never see this section, and the record data is never fetched for them.

Tenant admins also see a Health & Quota card on each app's detail page — the same operational view the admin console has. It rolls the app's state into one status pill (worst of breaker and quota), shows record-quota usage with a meter that escalates as the limit nears, and lists any action the circuit breaker has recorded failures or timeouts for, with the thresholds and window taken from the server's configuration. A tripped action carries a Re-enable & reset button that re-enables the bindings the breaker disabled; the breaker's slate is wiped by the re-enable, and the bot picks the commands back up within the binding cache window. Members never see this card, and the health data is never fetched for them.

What gets recorded

Every action writes an audit record, including denials. Separately, a change feed records every record mutation in the same transaction as the change itself, so the log cannot disagree with the data.