Your own bots

This page runs two bots on a home server, a weather bot and a chat bot for the language models on your own llama-swap, and adds them to the app by name. It is the setup the developer runs at home: one Linux machine with Docker, reachable over the home network and Tailscale.

Both bots answer you and nobody else. Allow an hour. You need:

  • A Linux machine that stays on, with Docker and Docker Compose.
  • Your address. /whoami in the app shows it.
  • For the chat bot, llama-swap running somewhere the machine can reach.
  • To add the bots by name, a domain and a reverse proxy that serves HTTPS. The examples use Caddy and example.org.

Plugins & slash commands explains what a bot is and what it can do in a chat.

What they do

The weather bot answers a city name with the weather there now, and /week with the next seven days, from Open-Meteo. It can also send you your home forecast every morning.

The llama-swap bot passes what you write to one of your models and sends back the answer. It keeps the last twenty messages as context. /models lists the models and switches between them, and /new starts over.

Both reply with cards and buttons, and /start shows what they can do.

How they stay private

A bot is an XMTP inbox, and anyone who knows its address can write to it. Every XMTP message is signed by its sender's keys, so the bot knows for certain who wrote it, and these two answer only the address you give them as OWNER. Anyone else gets "This bot is private." once, and after that the bot ignores them. It never calls Open-Meteo or your models for them.

The bots make outgoing connections only, to XMTP's nodes, Open-Meteo and llama-swap. No port is opened for them. Each one runs as an ordinary user in a container with a read-only filesystem, no Linux capabilities and a memory limit, and can write only to its own directory.

The machine running the bots decrypts what you send them, as the other end of any chat does. The llama-swap bot sends your messages to your own models and nowhere else. The weather bot sends Open-Meteo the city names you ask about.

The compose file

Each bot is built from its own directory in this app's repository, weather-bot and llama-bot. Put this in an empty directory as docker-compose.yml:

yaml
x-bot: &bot
  user: "1000:1000"
  read_only: true
  cap_drop: [ALL]
  security_opt: [no-new-privileges:true]
  mem_limit: 256m
  tmpfs: [/tmp]
  restart: unless-stopped

x-env: &env
  OWNER: ${OWNER}
  XMTP_ENV: production
  PUBLIC_DIR: /public
  TZ: Europe/Paris

services:
  weather-bot:
    <<: *bot
    build:
      context: https://github.com/alaibe/statim.git#main:examples
      dockerfile: weather-bot/Dockerfile
    environment:
      <<: *env
      WEATHER_CITY: ${WEATHER_CITY}
      WEATHER_DAILY_AT: ${WEATHER_DAILY_AT}
    volumes:
      - ./weather:/data
      - ./public:/public

  llama-bot:
    <<: *bot
    build:
      context: https://github.com/alaibe/statim.git#main:examples
      dockerfile: llama-bot/Dockerfile
    environment:
      <<: *env
      LLM_BASE_URL: ${LLM_BASE_URL}
      LLM_MODEL: ${LLM_MODEL}
      LLM_API_KEY: ${LLM_API_KEY}
    volumes:
      - ./llama:/data
      - ./public:/public

  bots-web:
    image: nginx:1.29-alpine
    volumes:
      - ./public:/usr/share/nginx/html:ro

Next to it, a .env:

sh
OWNER=0xYourAddress
WEATHER_CITY=Paris
WEATHER_DAILY_AT=07:30
LLM_BASE_URL=http://llm-host:8080/v1
LLM_MODEL=
LLM_API_KEY=

WEATHER_DAILY_AT is the time of the morning forecast; leave it empty for none. With LLM_MODEL empty the bot uses the first model llama-swap lists. LLM_API_KEY is only needed if your llama-swap asks for one.

Create the three directories yourself, so they belong to your user and not to root. The bots cannot write to them otherwise:

sh
mkdir weather llama public

Give them names

Each bot writes a small file about itself when it starts: public/.well-known/status-bot/weather.json holds its address, name and a line about what it does. bots-web serves that directory, and when you type weather@bots.example.org the app reads https://bots.example.org/.well-known/status-bot/weather.json to find the bot.

With Caddy:

bots.example.org {
    header Access-Control-Allow-Origin *
    reverse_proxy bots-web:80
}

Caddy has to reach bots-web, so put them on a shared Docker network. The header lets the Mac app read the file.

The name only has to work for you. If the machine is only on your home network and Tailscale, a local DNS entry for bots.example.org and a DNS-01 certificate are enough, and nothing has to be added at your DNS provider beyond what the certificate needs. Away from home the name resolves while your phone is on Tailscale. The bots themselves work from anywhere, because they do not use this server at all.

You can skip names entirely and add the bots by address instead.

Start them

sh
docker compose up -d --build
docker compose logs weather-bot llama-bot

Each bot logs its address and who it answers:

Weather 0x5C1f… on production, answering 0xyouraddress only

In the app:

/addbot weather@bots.example.org
/startbot weather@bots.example.org
/addbot llama@bots.example.org
/startbot llama@bots.example.org

Each bot appears in your chat list, and /start brings back its card with buttons.

Keep them running

  • Back up the weather and llama directories. Each holds its bot's key. Lose it and the bot comes back with a new address, and you add it again.
  • Messages sent while a bot is down are answered when it comes back.
  • To update, run docker compose up -d --build.

Write your own

Each bot is a directory in examples with one bot.js. They share bot-lib, which handles the key, the owner check and catching up, and calls your function with each message from you and a way to reply, as text or as a card. echo-bot is the smallest, at about twenty lines. Copy it, and add a service like the two above with its dockerfile.

Edit this page on GitHub