Each agent gets its own Docker container, its own compose file, and its own state volume. Adding a new agent never touches an existing one.
Prerequisites
- Docker with Compose v2
- A clone of the term-llm repo
- At least one LLM API key (Anthropic, OpenAI, Venice, etc.)
Scaffold a new agent
From the term-llm repo root:
docker/init.sh myagent
This creates a standalone project directory:
myagent/
├── docker-compose.yml
├── .env ← API keys, web token
├── init.sh ← boot hook (installs runit services)
├── agents/
│ └── myagent/
│ ├── agent.yaml
│ ├── soul.md ← voice, values, personality
│ └── system.md ← operational context
└── services/
├── webui/run ← web UI on port 8081
├── jobs/run ← job scheduler
└── bootstrap-jobs/run ← creates default jobs on first boot
You can also specify a custom output directory:
docker/init.sh myagent ~/agents/myagent
Configure
API keys
Edit .env and add at least one API key:
ANTHROPIC_API_KEY=sk-ant-...
The .env file also has a pre-generated WEB_TOKEN for authenticating the web UI and a WEB_PORT you can change if running multiple agents.
Personality
The scaffold separates personality from operations:
soul.md: voice, values, and boundaries. This is who the agent is. Edit it to change tone, principles, or guardrails.system.md: operational context. Who the user is, what tools are available, domain-specific instructions. Edit it to change what the agent knows about its environment.
Both files are bind-mounted into the container as seed files. On first boot they are copied into the agent’s config directory on the state volume. After that, the container owns them. Your local copies are the seed, not the live version.
Agent settings
agent.yaml controls model, tools, and behavior:
name: myagent
description: "AI assistant"
include: ["soul.md"]
tools:
enabled: [read_file, write_file, edit_file, glob, grep, shell, ...]
shell:
auto_run: true
allow: ["*"]
max_turns: 200
search: true
The include field loads additional markdown files (like soul.md) after the system prompt. See Agents for the full configuration reference.
Start
cd myagent
docker compose up -d
The web UI will be at http://localhost:8081/chat (or whatever WEB_PORT you set).
Authenticate with the bearer token from .env:
Authorization: Bearer <WEB_TOKEN>
How it works
The container uses the same term-llm Docker image for every agent. No agent-specific files are baked into the image.
On first boot, the entrypoint:
- Copies seed files from
/seed/into the state volume (only if they don’t already exist) - Runs
/seed/init.shwhich installs runit services (web UI, job scheduler, job bootstrapper) - Starts runit as PID 1
After the first boot, the state volume owns all agent config. Your local agents/ directory remains the seed. Edit it and delete the volume to re-seed, or edit the live files inside the container directly.
The built-in term-llm contain new agent image keeps PID 1 and runit supervision as root so services can be linked under /etc, but the Web UI, jobs service, bootstrap jobs, interactive shells, and normal agent work run as the non-root Linux user agent with home /home/agent. Use explicit passwordless sudo inside the container for package maintenance or other root-only operations.
Services
| Service | What it does |
|---|---|
webui |
Web UI and HTTP API on port 8081 |
jobs |
Background job scheduler |
bootstrap-jobs |
Creates default scheduled jobs on first boot, then sleeps |
On first boot, the bootstrap-jobs service waits for the jobs API, then creates four default jobs:
| Job | Schedule | What it does |
|---|---|---|
mine-sessions |
Every 30 min | Extracts memory fragments from session transcripts |
update-recent |
Every 10 min | Promotes recent fragments into the agent’s context |
memory-gc |
Daily at 4am UTC | Garbage-collects stale or duplicate memory fragments |
system-upgrade |
Daily at 5am | Keeps the container’s distro packages current (pacman on Arch, dnf on Fedora) |
These jobs are yours after creation. Edit or delete them with term-llm jobs list and term-llm jobs update.
All services are managed by runit. Check status:
docker exec myagent sv status /etc/runit/runsvdir/webui/
Running multiple agents
Each scaffolded directory is fully independent. Run as many as you like:
docker/init.sh agent-a
docker/init.sh agent-b ~/agents/agent-b
Set different WEB_PORT values in each .env to avoid port conflicts:
# agent-a/.env
WEB_PORT=8081
# agent-b/.env
WEB_PORT=8082
Each agent gets its own Docker volume for state, its own compose project, and its own container. They share only the term-llm image.
Updating
When you pull a new version of term-llm, rebuild the image and restart:
cd myagent
docker compose up -d --build
The state volume persists across rebuilds. Agent config, memory, and session history are preserved. The init.sh boot hook re-installs runit services on every boot, so new services added to your services/ directory will be picked up automatically.
Transcript access
Session transcripts are stored in the state volume. To access them from the host:
docker exec myagent term-llm sessions list --agent myagent
docker exec myagent term-llm sessions show <session-id> --agent myagent
The mine-sessions job automatically extracts key facts from transcripts into searchable memory fragments. See the bootstrapped jobs above for the full list of default scheduled tasks.