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.
/whoamiin 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:
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:roNext to it, a .env:
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:
mkdir weather llama publicGive 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
docker compose up -d --build
docker compose logs weather-bot llama-botEach bot logs its address and who it answers:
Weather 0x5C1f… on production, answering 0xyouraddress onlyIn the app:
/addbot weather@bots.example.org
/startbot weather@bots.example.org
/addbot llama@bots.example.org
/startbot llama@bots.example.orgEach bot appears in your chat list, and /start brings back its card with
buttons.
Keep them running
- Back up the
weatherandllamadirectories. 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.