This guide covers deploying Chapter3 bots to production, including the trace viewer.
Recommended production layout (compatible with chapter2):
/opt/chapterx/ # Main installation
├── config/
│ ├── shared.yaml # Shared vendor configs (API keys)
│ └── bots/
│ ├── BotName1.yaml # Per-bot configurations
│ └── BotName2.yaml
├── logs/
│ ├── traces/ # Activation traces
│ │ ├── BotName1/ # Per-bot trace directories
│ │ └── BotName2/
│ │ └── index.jsonl # Shared trace index
│ ├── llm-requests/ # Full LLM request bodies
│ └── llm-responses/ # Full LLM response bodies
├── cache/ # Persistent cache
│ └── plugins/ # Plugin state
├── src/
├── dist/
├── node_modules/
├── discord_token # Default Discord token
└── package.json
| Variable | Description | Default |
|---|---|---|
BOT_CONFIG |
Path to bot config file | Auto-detected |
DISCORD_TOKEN_FILE |
Path to Discord token file | ./discord_token |
CACHE_PATH |
Cache directory | ./cache |
CONFIG_PATH |
Config directory | ./config |
LOGS_DIR |
Logs directory | ./logs |
TRACE_DIR |
Trace files directory | ./logs/traces |
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3847 |
AUTH_TOKEN |
Bearer token for authentication | (none - no auth) |
LOGS_DIR |
Base logs directory | ./logs |
# From /opt/chapterx
npm run start -- --config ./config/bots/MyBot.yaml
# Or with environment variables
DISCORD_TOKEN_FILE=./tokens/mybot_token \
CACHE_PATH=./cache/mybot \
npm run start -- --config ./config/bots/MyBot.yamlEach bot needs its own:
- Discord token
- Cache directory
- Bot config file
# Start bot 1
screen -dmS bot1 bash -c 'cd /opt/chapterx && npm run start -- --config ./config/bots/Bot1.yaml'
# Start bot 2
screen -dmS bot2 bash -c 'cd /opt/chapterx && \
DISCORD_TOKEN_FILE=./tokens/bot2_token \
CACHE_PATH=./cache/bot2 \
npm run start -- --config ./config/bots/Bot2.yaml'
# List screens
screen -ls
# Attach to a screen
screen -r bot1Create /etc/systemd/system/chapterx-botname.service:
[Unit]
Description=Chapter3 Bot - BotName
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/chapterx
Environment=NODE_ENV=production
Environment=DISCORD_TOKEN_FILE=/opt/chapterx/tokens/botname_token
Environment=CACHE_PATH=/opt/chapterx/cache/botname
Environment=LOGS_DIR=/opt/chapterx/logs
ExecStart=/usr/bin/node dist/main.js --config ./config/bots/BotName.yaml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target# Enable and start
sudo systemctl enable chapterx-botname
sudo systemctl start chapterx-botname
# View logs
journalctl -u chapterx-botname -f# Development (no auth)
cd /opt/chapterx
npx tsx tools/trace-server.ts
# Production with auth
AUTH_TOKEN=your-secret-token \
PORT=3847 \
LOGS_DIR=/opt/chapterx/logs \
npx tsx tools/trace-server.tsCreate /etc/systemd/system/chapterx-traces.service:
[Unit]
Description=Chapter3 Trace Viewer
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/chapterx
Environment=NODE_ENV=production
Environment=AUTH_TOKEN=your-secret-token
Environment=PORT=3847
Environment=LOGS_DIR=/opt/chapterx/logs
ExecStart=/usr/bin/npx tsx tools/trace-server.ts
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetFor public access with HTTPS:
server {
listen 443 ssl;
server_name traces.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3847;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}providers:
anthropic:
api_key: sk-ant-api03-xxx
openai:
api_key: sk-xxx
openrouter:
api_key: sk-or-xxx
api_base: https://openrouter.ai/api/v1name: MyBot # Name used in LLM context
# Use shared provider
providers_config: ../shared.yaml # Relative to this config file
mode: prefill
prefill_thinking: true
continuation_model: claude-sonnet-4-5-20250929
temperature: 1.0
max_tokens: 4096
tool_plugins: ['config', 'notes']
plugin_config:
notes:
state_scope: channelcd /opt/chapterx
# Pull latest
git pull origin main
# Install dependencies
npm install
# Build TypeScript
npm run build
# Restart bots
systemctl restart chapterx-bot1 chapterx-bot2
# Or with screens
screen -S bot1 -X stuff $'\003' # Send Ctrl+C
screen -S bot1 -X stuff 'npm run start -- --config ./config/bots/Bot1.yaml\n'# Systemd
journalctl -u chapterx-botname -f --since "1 hour ago"
# Screen
screen -r botname# Count traces per bot
for d in /opt/chapterx/logs/traces/*/; do
echo "$(basename $d): $(ls $d/*.json 2>/dev/null | wc -l) traces"
done
# Recent failed traces
grep '"success":false' /opt/chapterx/logs/traces/index.jsonl | tail -10du -sh /opt/chapterx/logs/*
du -sh /opt/chapterx/cache/*Traces and logs can grow large. Set up periodic cleanup:
# /etc/cron.daily/chapterx-cleanup
#!/bin/bash
# Remove traces older than 7 days
find /opt/chapterx/logs/traces -name "*.json" -mtime +7 -delete
# Remove old LLM request/response bodies
find /opt/chapterx/logs/llm-requests -name "*.json" -mtime +3 -delete
find /opt/chapterx/logs/llm-responses -name "*.json" -mtime +3 -delete
# Rebuild index (removes entries for deleted traces)
# TODO: Add index cleanup scriptChapter3 can coexist with Chapter2 deployments. The trace viewer supports viewing traces from multiple bot frameworks:
/opt/
├── chapter2/
│ └── ems/
│ ├── config.yaml # Chapter2 shared config
│ ├── bot1/
│ │ ├── config.yaml
│ │ └── discord_token
│ └── logs/
└── chapterx/
├── config/
│ └── bots/
└── logs/
└── traces/
The trace viewer at /opt/chapterx/tools/trace-server.ts automatically discovers bot directories under logs/traces/.