botctl is a process manager for autonomous AI bots. Each bot is defined by a single BOT.md file — YAML config and a markdown prompt. The harness runs each bot in a loop: execute, log, sleep, repeat. No servers, no infrastructure, just a Go binary.

Bots persist across runs with session memory. You can send messages, resume sessions, and monitor everything from a TUI, web dashboard, or CLI.

macOS / Linux

shell
$ curl -fsSL https://botctl.dev/install.sh | sh

Windows (PowerShell)

powershell
> irm https://botctl.dev/install.ps1 | iex

Go

shell
$ go install github.com/montanaflynn/botctl@latest

Pre-built binaries for macOS, Linux, and Windows are on the releases page.

Each bot lives at ~/.botctl/bots/{name}/ and is defined by a BOT.md file. YAML frontmatter sets the config, the markdown body is the bot's prompt.

BOT.md
--- name: weather-bot id: weather-001 interval_seconds: 300 max_turns: 20 workspace: local skills_dir: ./skills log_retention: 30 env: API_KEY: ${WEATHER_API_KEY} --- # Weather Monitor You are an autonomous agent that monitors weather APIs and alerts on severe storms. ## Steps - Check api.weather.gov for active alerts - Compare against previous state - Write summary to workspace/alerts.json - Log any new severe weather events

The markdown body becomes the bot's system prompt. Claude sees it every run along with workspace path, skills, and turn limit. Edit the file and changes take effect on the next run — no restart needed.

FieldTypeDescription
namestringDisplay name for the bot (required)
idstringStable database key — survives folder renames. Defaults to folder name.
interval_secondsintSeconds between runs. Default: 60
max_turnsintTurn limit per run. 0 = unlimited. When hit, the session is saved for resume.
workspacestringlocal (per-bot) or shared (global). Default: local
skills_dirstringRelative path to skill directories
log_dirstringCustom log directory. Default: logs/
log_retentionintNumber of run logs to keep before pruning. Default: 30
envmapEnvironment variables. Supports ${VAR} references to system env.

Skills are directories containing a SKILL.md file with YAML frontmatter (name and description) and a markdown body. The harness parses frontmatter from all discovered skills and lists them by name and description in the system prompt. Claude loads full skill content on-demand via the Skill tool.

Skills are discovered from three locations (first occurrence of a name wins):

  1. ~/.agents/skills/ — cross-agent shared skills
  2. ~/.botctl/skills/ — botctl-wide shared skills
  3. Bot's skills_dir — per-bot skills
file structure
my-bot/ BOT.md skills/ api-access/ SKILL.md safety-rules/ SKILL.md workspace/ state.json

Skills are re-discovered every run, so you can add, edit, or remove them without restarting the bot.

Managing Skills

The botctl skills commands let you search, install, view, and remove skills from the CLI.

search & install
$ botctl skills search slack NAME SOURCE INSTALLS slack-notify montanaflynn/agent-skills 2.1K slack-reader acme/slack-tools 840 $ botctl skills add montanaflynn/agent-skills --skill slack-notify Installing skills from montanaflynn/agent-skills... installed slack-notify → ~/.botctl/skills/ 1 skill(s) installed

By default, skills install to ~/.botctl/skills/ (shared across all bots). Use flags to change the destination:

FlagDestination
--bot <name>Bot's skills_dir (per-bot only)
--global~/.agents/skills/ (cross-agent, shared with other tools)
(default)~/.botctl/skills/ (all botctl bots)
view & remove
# View a skill's content and files $ botctl skills view slack-notify # List all discovered skills $ botctl skills list $ botctl skills list --bot my-bot # Remove a skill $ botctl skills remove slack-notify
  • Local (workspace: local) — ~/.botctl/bots/{name}/workspace/ — private to this bot
  • Shared (workspace: shared) — ~/.botctl/workspace/ — shared across all bots that opt in

Claude's working directory is set to the workspace. All file operations happen there by default.

The env field in BOT.md sets environment variables for the bot process. Values support ${VAR} references that resolve against system environment variables.

BOT.md frontmatter
env: API_KEY: ${WEATHER_API_KEY} LOG_LEVEL: debug

Set MM_HOME to override the default ~/.botctl directory.

CommandDescription
botctlOpen the TUI dashboard (--web-ui for web, --port to set port)
botctl create [name]Create a new bot via Claude (-d description, -i interval, -m max turns)
botctl start [name]Start a bot (-d detach, -m message, --once single run)
botctl stop [name]Stop a bot (no args = stop all)
botctl pause <name>Pause a running or sleeping bot
botctl play <name>Resume a paused bot
botctl listList all bots with status
botctl statusDetailed status of all bots
botctl logs [name]View logs (-n lines, -f follow)
botctl delete <name>Delete a bot and its data (-y skip confirmation)
botctl skills listList discovered skills (--bot to filter by bot)
botctl skills search <query>Search skills.sh for community skills (-n limit)
botctl skills add <owner/repo>Install skills from a GitHub repo (--skill, --bot, --global)
botctl skills view <name>View a skill's SKILL.md and list its files
botctl skills remove <name>Remove an installed skill
botctl updateSelf-update to the latest release
botctl --versionShow version and commit hash

Run botctl with no arguments to open the terminal dashboard. Two-pane layout: bot list on the left, streaming logs on the right.

Keybindings

sStart / stop bot
rResume session
mSend message
enterSend message
nCreate new bot
cClear logs / runs
dDelete bot
oOpen bot directory
f / tabFilter bots
j / kNavigate list
qQuit

Tip: To select text in the TUI, hold Option (macOS) or Shift (other terminals) while clicking and dragging.

shell
$ botctl --web-ui # default port 4444 $ botctl --web-ui --port 8080 # custom port

Browser-based dashboard with the same capabilities as the TUI. Real-time log streaming via Server-Sent Events (SSE). Accessible from any device on your network.

API Endpoints

EndpointDescription
GET /api/botsList all bots with status and stats
POST /api/bots/{name}/startStart a bot
POST /api/bots/{name}/stopStop a bot
POST /api/bots/{name}/messageSend a message to a running bot
GET /api/bots/{name}/logsGet log entries
GET /api/bots/{name}/logs/streamSSE stream of log entries

The harness is the background process that executes each bot. It runs a continuous loop:

  1. Reload BOT.md config from disk
  2. Check the pending message queue
  3. Execute Claude with the bot's prompt, tools, and workspace
  4. Record stats (cost, turns, session ID) to the database
  5. Write structured log entries
  6. Sleep for interval_seconds (woken early by messages or resume signals)

Config is reloaded every iteration, so changes to BOT.md take effect on the next run without restarting the harness.

When a run hits max_turns, the session ID is saved in the database. You can resume from exactly where Claude left off:

  • Press r in the TUI — opens an input pre-filled with the current max_turns. Edit the number and press enter.
  • The harness detects the resume signal, applies the new turn limit, and calls Claude with the saved session ID.

Messages sent to a running bot (via m in the TUI or botctl start --message) wake the harness early via SIGUSR1.

SQLite at ~/.botctl/data/botctl.db with WAL mode. Stores:

TableDescription
runsExecution history — duration, cost, turns, session ID, log file
pidsActive process tracking (bot name, PID, started_at)
messagesRaw Claude API response JSON per run
pending_messagesMessage queue for operator-to-bot communication
log_entriesStructured log records (bot_id, run_id, kind, heading, body)
~/.botctl/
data/ botctl.db # SQLite database (WAL mode) workspace/ # Shared workspace bots/ my-bot/ BOT.md # Config + system prompt skills/ # Skill directories (discovered each run) research.md # Each file is an instruction set formatting.md workspace/ # Local workspace (if workspace: local) logs/ 20260208-142305.log # Per-run logs boot.log # Harness startup