Skip to content

52 · Your own app

Your bot can work with a program you run yourself, on this computer or your own network: a till, a booking sheet, a script you wrote. The bot calls its tools, the program tells the bot when something happens, and a conversation the program holds with your customers can be answered by the bot. Nothing it sends passes through Stalwart: the program and the bot talk on your machine. Your own apps are on the paid and enterprise plans.

Language support

The selector reads turns written in English and in Brazilian Portuguese (pt-BR). The language is chosen, never guessed from the turn: the console's Settings page (Language the selector reads), language on a plan request, or the settings.language of your vocabulary file; with none of them, English. A Portuguese turn may be typed with its accents or without them. European Portuguese is not a grammar the selector has, and a turn in any other language is unread.

Writing the app

stalwart_agents.local_app is the app's side. Name the app as the bot's file names it, give it tools, and run it:

from stalwart_agents.local_app import LocalApp, Refused

app = LocalApp("till")                     # its secret is read from STALWART_APP_SECRET

@app.tool
def stock_of(item: str) -> dict:
    """How many of an item are left."""
    return {"item": item, "left": 12}

@app.on_send
def send(to: str, text: str, in_reply_to: str) -> None:
    my_chat.send(to, text)                 # raise Refused("why") to tell the owner it could not be sent

app.run()                                  # serves the tools at http://127.0.0.1:8765/mcp
  • Its tools are what @app.tool marks, each read from its type hints and its docstring. They are served on this computer only; another machine on your network reaches them only where the app is made with lan=True and the bot's file says lan: allow.
  • What it tells the bot -- app.event("new_order", {"order_id": "4411"}, id="order-4411") starts the task whose trigger names that event; app.message("chat:+447700900123", "do you have oat milk?", id="m-1") is a turn of that person's conversation. Each is signed with the app's secret. An id the bot has seen starts nothing, so the app may send again whatever it is unsure arrived.
  • Its conversation's replies come back through the function @app.on_send names, which the bot calls as the tool send_message. A refusal you raise there is shown on the owner's card, in your words.

Telling the bot about it

The bot's file names each app under own_apps (chapter 48), with its address and, where it holds conversations, channel: true:

own_apps:
  till: {address: "http://127.0.0.1:8765/mcp", channel: true}
triggers:
  new_order: {kind: webhook, app: till}
  till_chat: {kind: message, in: custom, app: till}

Its tools join the bot as any server's do. The bot holds each one unclassified until you classify it, so every write waits for your approval.

Its secret

On the host's Approvals page, Your own apps lists each app: where the bot reaches it, where its conversations come in, and whether its secret is set. Make its secret makes one, kept sealed on this computer and shown to you once: put it in the app's settings (STALWART_APP_SECRET) straight away. Making a new one stops the old one working. A secret your bot's file names as ${SOME_VARIABLE} is yours to keep where you keep it, and the page makes none for it.

What it cannot do

  • An app is reached on this computer, or on your own network where you allow it; never at an address on the internet, which the bot refuses.
  • On the free plan, the bot refuses everything an app sends it.
  • The app's tools are checked as any tool is, their descriptions included; nothing is trusted because it runs on your machine.