https://github.com/jahwag/clem
Continuously Looping Engineering Machines. docker-compose for Claude Code agents - persistent teams, 24/7, on any Linux host.
https://github.com/jahwag/clem
agents ai claude claude-code llm
Last synced: 23 days ago
JSON representation
Continuously Looping Engineering Machines. docker-compose for Claude Code agents - persistent teams, 24/7, on any Linux host.
- Host: GitHub
- URL: https://github.com/jahwag/clem
- Owner: jahwag
- License: mit
- Created: 2026-04-04T11:29:09.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-16T19:00:40.000Z (about 1 month ago)
- Last Synced: 2026-05-16T20:45:14.183Z (about 1 month ago)
- Topics: agents, ai, claude, claude-code, llm
- Language: Go
- Homepage: https://myclementine.ai
- Size: 414 KB
- Stars: 7
- Watchers: 0
- Forks: 2
- Open Issues: 47
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
clem
Continuously Looping Engineering Machines.
docker-compose for Claude Code agents.
myclementine.ai ·
Quickstart ·
Download ·
Discord
`clem` runs a team of Claude Code agents 24/7 on any Linux host. Each agent is a separate OS user in a tmux session under systemd. Agents coordinate over Discord or Slack, pick up tasks, write code, and open PRs. A watchdog restarts anything that crashes. You configure it once and walk away.
---
## Feature map
| | |
|---|---|
| **Per-agent OS identity** | Each agent is its own Linux user - own home dir, own git identity, own GitHub PRs, own Discord/Slack bot. Crash boundaries are real. |
| **Multi-backend coordination** | Discord + Slack today via swappable `coordination.backend:` in `clem.yaml`. One config knob. |
| **Multi-runtime** | `runtime: claude-code \| opencode`. Mix Anthropic cloud, Bedrock, Vertex, Ollama, OpenAI-compat - one surface. |
| **Encrypted secrets** | Per-agent `.env` materialised from age/sops vaults at provision time. Never leave the host after. |
| **Self-healing** | systemd + tmux per agent. Watchdog timer restarts dead or stalled sessions. Alerts fire only after repeated failures. |
| **Bring your own model** | Default Claude; one flag away from Ollama Cloud / Bedrock / Vertex / local models. Tested end-to-end on NVIDIA Nemotron. |
| **Live ops** | `clem status` shows health per agent. Optional ttyd web terminal per agent - attach in your browser. |
| **Works locally** | Laptop, home server, Raspberry Pi, small VPS. No Kubernetes. No cloud services required. |
---
## Contents
1. [How it works](#how-it-works)
2. [Requirements](#requirements)
3. [Install](#install)
4. [Quickstart](#quickstart)
5. [Discord setup](#discord-setup)
6. [GitHub setup](#github-setup)
7. [CLI reference](#cli-reference)
8. [`clem.yaml` reference](#clemyaml-reference)
9. [Secrets](#secrets)
10. [Deploy to a VPS](#deploy-to-a-vps)
11. [Troubleshooting](#troubleshooting)
12. [License](#license)
---
## How it works
```
┌──────────────────────────────────────────────────────┐
│ Linux host (your laptop · home server · VPS · …) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ OS user: │ │ OS user: │ systemd + │
│ │ myteam-lead │ │ myteam-worker│ tmux per user │
│ │ claude loop │ │ claude loop │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ └──── MCP (stdio) ─┘ │
│ │ │
│ ┌──────────────────┴──────────────────┐ │
│ │ watchdog timer (every 5 min) │ │
│ │ restarts dead agents → #alerts │ │
│ └─────────────────────────────────────┘ │
└───────────────────┬──────────────────────────────────┘
│ coordination backend API
┌─────────▼──────────┐
│ Discord or Slack │
│ #general #tasks │
│ #alerts #lessons │
└────────────────────┘
```
Each agent runs a loop: launch `claude` (or `opencode`), inject a prompt, wait for the session to finish (up to 2h hard cap), sleep the configured `iteration` duration, repeat. Secrets live encrypted in `secrets.sops.yaml` (age/sops); `clem provision` decrypts them into per-agent `.env` files on the host.
---
## Requirements
**Host** - any Linux box with systemd (Ubuntu 24.04 recommended). Can be your laptop, a home server, a Pi, or a cloud VPS. Must have `tmux`, `git`, `python3`, `age`, `sops`, `yq`, and `curl`. Plus the MCP server for your chosen coordination backend - `mcp-discord` (Python) or `slack-mcp-server` (binary) - reachable via `$PATH`. `clem provision` installs the runtime CLI (Claude Code or opencode) per agent.
**Local machine** - where you run `clem` commands (may be the same box as the host):
- `go` 1.22+ (to build `clem`)
- `age`, `sops`, `yq` - to edit secrets locally
- `gh` - GitHub CLI
**Accounts:**
- A coordination backend:
- **Discord** - a private server + one bot token per agent, or
- **Slack** - a workspace + one Slack app per agent (bot user token `xoxb-…`)
- A GitHub token per agent (fine-grained PAT or App)
---
## Install
Build from source:
```bash
git clone https://github.com/jahwag/clem.git
cd clem
go build -ldflags "-X github.com/jahwag/clem/cmd.Version=$(git describe --tags --always)" -o clem .
sudo install -m 0755 clem /usr/local/bin/clem
clem --version
```
To upgrade later, once releases are published:
```bash
sudo clem update
```
---
## Quickstart
Full local setup on one Linux box. If you want to provision on a separate remote host, see [Deploy to a VPS](#deploy-to-a-vps).
**Try clem without touching your host:** sandboxed samples under [`samples/`](samples/README.md) -
- [`ollama-nemotron-4b`](samples/ollama-nemotron-4b/README.md) - Discord + local NVIDIA Nemotron 3 Nano 4B (~2.8 GB)
- [`slack-nemotron-4b`](samples/slack-nemotron-4b/README.md) - Slack + same local model
```bash
# 1. new team repo (replace with your org)
gh repo create my-team --private --clone && cd my-team
# 2. scaffold config
clem init
```
Edit `clem.yaml`:
- Set `project:` (becomes OS user prefix, e.g. `myteam-lead`)
- Pick `coordination.backend:` (`discord` or `slack`)
- Paste your server/workspace ID and channel IDs - see [Discord setup](#discord-setup) below. Slack setup lives in [`samples/slack-nemotron-4b/README.md`](samples/slack-nemotron-4b/README.md).
- Adjust agent `name`, `role`, `model`, `iteration` (Go duration: `30s`, `1m30s`, `2h`), `runtime`, `provider`
Edit `CLAUDE.shared.md` - describe your project, fill in tiers T2-T4. Edit each `CLAUDE..md` with per-agent specifics.
```bash
# 3. generate age keypair + .sops.yaml
clem vault init
# 4. store per-agent secrets (see Discord/GitHub setup below)
clem vault set github GH_TOKEN="ghp_..."
clem vault set discord-lead DISCORD_TOKEN="Bot "
clem vault set discord-worker DISCORD_TOKEN="Bot "
# 5. commit config (secrets.sops.yaml is encrypted - safe)
git add clem.yaml CLAUDE.*.md .sops.yaml secrets.sops.yaml
git commit -m "init team config"
git push
# 6. provision - creates OS users, installs services, writes .env
sudo clem provision
# 7. authenticate each agent with Claude (opens browser per agent)
sudo clem login
# 8. start and check
sudo clem up
clem status
```
`clem status` shows systemd state, tmux liveness, token expiry, and last log line per agent. Once `SYSTEMD=active` and `TMUX=yes`, agents are running.
Watch an agent:
```bash
clem logs lead
```
---
## Discord setup
Create a **private** Discord server (not a public one). Discord membership is the access control layer - agents act on instructions from anyone who can post in the channels.
**Channels to create:**
| Name | Type | Purpose |
|------------|--------|-----------------------------------------|
| `#general` | Text | Status updates, operator comms |
| `#tasks` | Forum | Task board - agents claim threads here |
| `#alerts` | Text | Critical issues, watchdog alerts |
| `#lessons` | Forum | Post-mortems, learnings |
Enable **Developer Mode** (Settings → Advanced), then right-click the server icon and each channel to copy their IDs into `clem.yaml`.
**Bot per agent** - one application per agent gives each a distinct name and avatar in task threads:
1. https://discord.com/developers/applications → **New Application** (name it after the agent)
2. **Bot** tab → **Reset Token** → copy
3. Enable **Server Members Intent** and **Message Content Intent**
4. **OAuth2 → URL Generator**: scopes `bot`; permissions `Send Messages`, `Read Message History`, `Attach Files`, `Manage Threads`, `Create Public Threads`
5. Open the generated URL in a browser, add the bot to your server
6. Save the token: `clem vault set discord- DISCORD_TOKEN="Bot "`
Repeat per agent.
---
## GitHub setup
Each agent needs its own GitHub token so PRs and commits show distinct authors.
**Fine-grained PAT** (simplest, good for personal projects):
1. https://github.com/settings/tokens?type=beta → **Generate new token**
2. Select the target repositories
3. Permissions: `Contents` (RW), `Pull requests` (RW), `Issues` (RW), `Workflows` (RW)
4. `clem vault set github GH_TOKEN="ghp_..."` (or a per-agent vault if you want separate tokens)
**Git identity per agent** - so PRs are authored by the agent's name, not root. Run after `clem provision`:
```bash
sudo -u myteam-lead git config --global user.name "Amara"
sudo -u myteam-lead git config --global user.email "amara@yourproject.com"
sudo -u myteam-lead git config --global credential.helper store
echo "https://amara:ghp_...@github.com" | \
sudo -u myteam-lead tee /home/myteam-lead/.git-credentials
```
Repeat per agent.
**GitHub App** (recommended for teams) - create one app per agent, exchange the private key for a short-lived installation token each iteration. See [GitHub App authentication](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app).
---
## CLI reference
```
clem --version Print the installed version
clem update Download and install the latest release
clem init Scaffold clem.yaml + CLAUDE.{shared,}.md
clem vault init Generate age keypair + .sops.yaml
clem vault set KEY=value Set a secret in a vault
clem vault get KEY Read a decrypted secret
clem vault list List all vaults and their keys (values hidden)
clem vault delete [KEY] Delete a secret or entire vault
clem provision [--remote HOST] Create OS users, write .env, install services (root)
clem login [agent...] Run `claude /login` as each agent (one-time)
clem up [agent...] Start agent systemd services (root)
clem down [agent...] Stop agent systemd services (root)
clem status Table: systemd · tmux · token expiry · last log
clem logs Tail an agent's runner log
```
Flags:
- `--config ` - override the default `clem.yaml` path
- `--remote ` on `provision`/`login` - run against a remote host over SSH
---
## `clem.yaml` reference
```yaml
project: string # OS user and service name prefix
primary_milestone: string # optional - referenced by CLAUDE.shared.md
coordination:
backend: string # discord (default) | slack
server_id: string # Discord guild ID or Slack workspace ID
channels:
general: string # channel ID (C... for Slack, numeric for Discord)
tasks: string # task board channel (forum on Discord)
alerts: string # alerts channel
lessons: string # lessons channel (forum on Discord)
agents:
: # lowercase; used in CLI + OS username
name: string # display name in Claude + Discord
role: string # human-readable
model: string # model ID (Claude or Ollama/etc. name per provider)
iteration: duration # go-style duration: "30s", "1m30s", "2h" (default 5m)
vaults: [string] # vault names merged into .env (later vaults win)
prompt: string # injected at start of each session
web_terminal_port: int # optional - ttyd port (1024-65535) for read-only viewing
caveman: off|lite|full|ultra # optional - install caveman plugin (compresses output ~75%); true → ultra (legacy compat)
subagent_model: string # optional - CLAUDE_CODE_SUBAGENT_MODEL for Task tool / Explore / general-purpose; defaults to claude-sonnet-4-6; set to "off" to inherit main model
provider: string # optional - anthropic (default) | bedrock | vertex | ollama | openai-compat
provider_url: string # required when provider is ollama or openai-compat
runtime: string # optional - claude-code (default) | opencode
```
**Runtimes:**
| `runtime` | CLI binary | Notes |
|---------------|-------------------------------------|-------------------------------------------------------------------------------------|
| `claude-code` | `~/.local/bin/claude` | Default. Anthropic-native wire format. Best for cloud Claude. |
| `opencode` | `~/.opencode/bin/opencode` | Talks natively to 75+ providers via models.dev. Better tool-use on local models. |
**Providers:**
| `provider` | Extra env `clem` writes into `.env` | Notes |
|------------------|------------------------------------------------------------------------------|-----------------------------------------------------------|
| `anthropic` | none (default behaviour) | Uses Claude Code's OAuth or `ANTHROPIC_API_KEY` |
| `bedrock` | `CLAUDE_CODE_USE_BEDROCK=1` | Agent also needs AWS creds in a vault |
| `vertex` | `CLAUDE_CODE_USE_VERTEX=1` | Agent also needs `GOOGLE_APPLICATION_CREDENTIALS` |
| `ollama` | `ANTHROPIC_BASE_URL=` · `ANTHROPIC_MODEL=` · `ANTHROPIC_AUTH_TOKEN=none` | Ollama natively speaks Anthropic API - no proxy needed |
| `openai-compat` | same as `ollama` | Requires you to run an Anthropic-wire translator yourself |
Derived names:
- OS user: `-` (e.g. `myteam-lead`)
- Systemd service: `clem--.service`
- Web terminal: `clem-ttyd--.service`
---
## Secrets
Secrets live in `secrets.sops.yaml`, encrypted with age via sops. The file is safe to commit. The age private key (`~/.config/sops/age/keys.txt`) is the only thing you must keep out of git - back it up.
`clem provision` decrypts secrets into per-agent `/home//.env` (mode 0600). The runner sources this at the start of each iteration. Secrets never leave the host after provisioning.
Each agent's `vaults:` list specifies which vaults to merge, in order. Later vaults overwrite earlier keys - useful for shared tokens with per-agent overrides.
Common secrets:
- `GH_TOKEN` - GitHub access
- `DISCORD_TOKEN` - Discord bot (**raw token, no `Bot ` prefix** - `discord.py` adds it)
- `SLACK_MCP_XOXP_TOKEN` - Slack bot (`xoxb-…`) or user (`xoxp-…`) token
- `SSH_HOST`, `ES_PASSWORD` - optional, enables Prefect MCP server
- `WRANGLER_OAUTH_TOKEN` - optional, enables Cloudflare Workers MCP
---
## Deploy to a VPS
`clem` doesn't require a VPS - any Linux host works. But for always-on agents, a small cloud box (2-4 GB RAM) is cheap and keeps them running while your laptop sleeps.
Remote provisioning flow:
```bash
# on your local machine, inside your team repo
clem provision --remote root@ --gh-token ghp_...
clem login --remote root@
ssh "cd my-team && clem up && clem status"
```
See [docs/hetzner.md](docs/hetzner.md) for a Hetzner-specific walkthrough (cloud-init, `hcloud` CLI, SSH config).
---
## Troubleshooting
**`clem provision` fails with `useradd: command not found`**
Not Linux, or missing core userspace. Use a standard Ubuntu/Debian host.
**`clem status` shows `SYSTEMD=failed`**
Inspect the service: `systemctl status clem--.service`. Common causes: `.env` missing (run `clem provision` again after setting vaults), `claude` not installed per agent (provision reinstalls), or MCP server binary missing on PATH.
**Agent not posting to Discord/Slack**
Check `clem logs `. The runner logs MCP server startup. If `mcp-discord` is missing, install with `pipx` (recommended) so its dependencies live in an isolated venv: `pipx install git+https://github.com/Bytelope/mcp-discord.git`. Avoid `pip install --break-system-packages` for Python MCP servers - the agent service runs with `ProtectHome=read-only`, so any later dependency drift (e.g. a system `pydantic-core` upgrade desyncing from the wheel an MCP server was built against) cannot be self-healed from inside the sandbox and the MCP will fail to boot. `pipx` venvs decouple each MCP from system Python state and survive `apt upgrade`. Confirm the bot was invited to the server. **`DISCORD_TOKEN` must be the raw token** (no `Bot ` prefix); `discord.py` adds it internally - pasting `"Bot …"` yields 401. For Slack: use a bot token (`xoxb-`), not a user token (`xoxp-`) - user tokens post as you, not the bot.
**`clem login` keeps prompting daily / `clem status` flips to `EXPIRED` every 8 hours**
You probably ran a clem older than v0.8.4. The Claude Max access token genuinely lasts only ~8 hours, but Claude Code refreshes it automatically using the long-lived refresh token stored alongside it. Pre-0.8.4 `clem status` displayed the *access* token expiry and pre-0.8.4 `NeedsLogin` gated on a 7-day window - so it always reported "expired" and trained operators to log in daily for nothing. Upgrade to v0.8.4+; status now shows `auto-refresh` whenever a refresh token is present, and only reports `missing` when manual `clem login` is actually required.
**Token actually missing** (`clem status` shows `missing`)
Re-run `sudo clem login `. The refresh token itself is long-lived; you only need to re-login if the credentials file is wiped or the refresh token is server-side revoked.
**Agent wakes up and does nothing**
Open the task forum - threads must exist with `[TODO]` status. Agents only work what's on the board.
**Provisioning the same host twice**
Safe. `useradd` is idempotent; systemd units are overwritten; `.env` is regenerated from current vaults. Existing Claude OAuth tokens are preserved.
---
## Community
Questions, ideas, showing off your team - join the [ClaudeSync / Clem Discord](https://discord.gg/pR4qeMH4u4).
## License
MIT - see [LICENSE](LICENSE).