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.
| Guarantee | State | Active when |
|---|---|---|
| Apps available to a tenant | ⚙️ Off by default | the mesa-apps-enabled flag is on for that tenant. Resolved per tenant, not globally |
| Commands dispatch to apps | ✅ Active | Telegram. 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.
| Command | Does |
|---|---|
/learn | Saves the message you replied to as a quote |
/quote | Fetches a quote by person, optionally by tag |
/quoterandom | Fetches a random quote |
/quotelist | Lists everyone who has been quoted |
Birthday Tracker
| Command | Does |
|---|---|
/birthday | Reads a birthday — or sets one if you pass a date |
/birthdayset | Sets 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
| Command | Does |
|---|---|
/counter | Increments 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. Four operations sit behind that conversation:
| Operation | What happens |
|---|---|
draft.save | Compiles your description into a spec, or returns the specific problems with it |
| dry run | Runs the real engine against your draft, then rolls everything back |
draft.publish | Makes the commands live. Requires your explicit confirmation |
app.instantiate | Creates 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.
Limits
| Limit | Value |
|---|---|
| Active apps per tenant | 25 |
| Records per app | 10,000 |
| Commands per minute, per person per app | 30 |
| Drafts per tenant | 100 |
| New apps per hour, per person | 5 (max 2 per minute) |
| Change-feed history | 90 days |
| Audit history | 400 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.
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.