Started with inspiration from the multitude of other 'claw' projects out there, but decided to vibe something from scratch that provided just the bare necessities of what I needed. The result is a self-hosted AI agent platform that connects to multiple LLM providers through a unified interface: web chat UI, Telegram bot, HTTP webhooks, and deterministic scheduled jobs. Agents can autonomously run CLI commands in a restricted workspace.
- Agents — Define agents in YAML with their own model, system prompt, tools, and workspace
- Cron jobs — Deterministic schedules that trigger either a project task or an explicit command
- Webhooks — Register HTTP POST endpoints for external services to trigger agent interactions
- Telegram bot — Chat with any agent via Telegram; receive cron notifications
- Web UI — Browser-based chat with streaming responses, plus dashboards for crons and webhooks
- CLI tool calling — Agents can autonomously run shell commands (restricted to their workspace)
- Multi-provider LLM — Pluggable provider system; run local models or connect to external APIs, per agent
- Memories — Persistent knowledge files that are keyword-matched and auto-injected into agent system prompts; agents can read and write them via tools
- Superpowers — Named external service bundles (config + secrets) keyword-injected into agent prompts; includes a "Bootstrap Memory" button to auto-document APIs
- Projects — Multi-component workflows with runnable tasks; includes a "Bootstrap Runbook" button that executes tasks and documents operational knowledge; project memories are automatically injected into task context
- Python 3.11+
- At least one configured LLM provider (e.g. a local Ollama instance)
# 1. Install dependencies
pip install -r requirements.txt
# 2. Edit config
# Set your api_key, Ollama URL, and optionally Telegram token
nano config.yaml
# 3. (Optional) Create a workspace directory for agent CLI execution
mkdir -p ~/workspaceRuns as a systemd user service — no sudo required after initial setup. Agents have direct access to the host filesystem within their configured workspace.
# Install the unit file
mkdir -p ~/.config/systemd/user
cp bareclaw.service ~/.config/systemd/user/
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now bareclaw
# View logs
journalctl --user -u bareclaw -fTo keep the service running at boot without an active login session, a sudoer must run once:
sudo loginctl enable-linger bareclawEasy to spin up, but agent file access is limited to volumes explicitly mounted into the container. By default only workspace/ is available to agents — to give agents access to other host paths, add volume mounts to docker-compose.yml and update the agent's workspace: in its YAML.
mkdir -p workspace # agent CLI sandbox (host-side)
docker compose up -d --build
docker compose logs -fOllama URL: inside the container localhost is the container itself. Set in config.yaml:
providers:
ollama:
type: ollama
base_url: http://host.docker.internal:11434extra_hosts: host.docker.internal:host-gateway in docker-compose.yml enables this on Linux. Mac/Windows Docker Desktop handles it automatically.
Volumes mounted from host (edit without rebuilding):
config.yaml,agents/,crons/,webhooks_config/— configdata/— SQLite DB (persists across restarts)memories/,superpowers/,secrets/,projects/— persistent knowledge, credentials, workflowsworkspace/→/root/workspace— agent CLI sandbox
Open http://localhost:8000 and log in with your API key.
# Named providers — add as many as you need.
# type "ollama" uses the Ollama SDK.
# type "openai" works with OpenAI, LM Studio, vLLM, llama.cpp, OpenRouter, Groq, etc.
providers:
ollama:
type: ollama
base_url: http://localhost:11434
openai:
type: openai
api_key: "" # set to enable real OpenAI
base_url: "" # optional: override for any OpenAI-compatible endpoint
# openrouter:
# type: openai
# base_url: https://openrouter.ai/api/v1
# # api_key loaded from secrets/openrouter.env
# lm-studio:
# type: openai
# base_url: http://localhost:1234/v1
# api_key: lm-studio
api_key: "changeme" # Web UI + webhook auth
telegram:
token: "" # BotFather token
allowed_user_ids: [] # Your Telegram numeric user ID
default_agent: defaultAgents reference providers by id (provider: ollama, provider: lm-studio, etc.). The legacy single-provider format (ollama: / openai: top-level keys) is still accepted for backwards compatibility.
Each config directory (agents/, crons/, webhooks_config/, memories/, superpowers/) contains an example.yaml that documents all available fields. These example files are:
- Ignored at runtime — never loaded as real agents, crons, or webhooks
- The only YAML files committed to git — your real configs are gitignored (as is
config.yaml)
To add a real config, copy the relevant example.yaml, rename it, and fill in your values:
cp agents/example.yaml agents/my-agent.yamlid: default
name: "Default Assistant"
provider: ollama # LLM provider; more coming
model: llama3.2
ollama_base_url: "" # Optional: override global ollama.base_url for this agent
system_prompt: |
You are a helpful assistant.
temperature: 0.7
workspace: ~/workspace # CLI commands confined here
tools:
- run_command
- read_file
max_iterations: 10Available tools: run_command, read_file
id: disk-check
schedule: "0 * * * *" # Standard 5-field cron expression
project: ops
task: check-disk
notify_telegram: trueExactly one target must be defined per cron job:
project+taskfor a scheduled project taskcommandfor an explicit shell command, optionally withworkspaceandtimeout
Example command cron:
id: disk-check
schedule: "0 * * * *"
command: "df -h"
workspace: ~/workspace
timeout: 30
notify_telegram: trueProject-task crons stay deterministic because the scheduler resolves project + task directly in Python and runs that task's prompt with agent resolution task.agent → project.agent → config.default_agent. Command crons run the configured shell command directly in the specified workspace. Cron jobs do not self-call the HTTP API.
id: my-webhook
path: /webhooks/my-webhook
secret: "" # Optional HMAC-SHA256 secret (GitHub-style)
agent: default
prompt_template: |
An event was received:
{{ body }}
Summarize what happened.Call it:
curl -X POST http://localhost:8000/webhooks/my-webhook \
-H "X-API-Key: changeme" \
-H "Content-Type: application/json" \
-d '{"event": "test"}'Persistent knowledge files agents can read and write. On each agent call, memories whose keywords match the conversation are automatically appended to the system prompt under ## Relevant memories. No restart needed — files are loaded fresh each call.
id: homeassistant-api
title: "Home Assistant API"
keywords:
- homeassistant
- home assistant
- hass
content: |
Base URL: http://homeassistant.local:8123
...Memory tools (available to all agents, no YAML config needed):
| Tool | Description |
|---|---|
list_memories |
Returns id, title, and keywords for all memories |
read_memory(id) |
Returns the full content of one memory |
write_memory(id, title, keywords, content) |
Creates or overwrites a memory file |
Named external service capabilities bundling config, secrets, and an optional bootstrap prompt. On each agent call, superpowers whose keywords match the conversation are automatically appended to the system prompt under ## Available superpowers.
superpowers/<id>.yaml (safe to commit — no secrets):
id: homeassistant
name: "Home Assistant"
description: "Local Home Assistant automation hub"
config:
base_url: "http://homeassistant.local:8123"
keywords:
- homeassistant
- home assistant
- lights
bootstrap_prompt: |
Explore the Home Assistant API at {base_url} using Bearer {token}.
Write a memory 'homeassistant-api' with your findings.
bootstrap_agent: default # optional; defaults to app's default_agentsecrets/<id>.yaml (always gitignored — flat key/value; filename must match superpower id):
token: "your-token-here"Consider chmod 600 secrets/<id>.yaml. Placeholders like {token} in bootstrap_prompt are interpolated from the merged config + secrets at bootstrap time.
Superpower tools (available to all agents, no YAML config needed):
| Tool | Description |
|---|---|
list_superpowers |
Returns id, name, description, and keywords for all superpowers |
read_superpower(id) |
Returns full config + secrets for the agent to use |
Bootstrap: clicking "Bootstrap Memory" on the /superpowers page runs the configured agent with the interpolated bootstrap_prompt. The agent typically uses run_command (curl) and write_memory to document the external API into memories. The button uses the bootstrap_agent (or falls back to the default agent).
Multi-component workflows the agent has explored and can execute. Each project defines named tasks — runnable prompts that can be triggered from the /projects UI and scheduled by cron jobs. On each agent call, projects whose keywords match the conversation are automatically appended to the system prompt under ## Relevant projects.
When a task runs, all memories listed in memories: are automatically injected into the task's context — no need for the agent to explicitly call read_memory().
id: home-network-security
name: "Home Network Security"
description: "Packet capture pipeline and security dashboard"
keywords:
- packet capture
- pcap
- network security
agent: default # default agent; falls back to config.default_agent
memories: # auto-injected into task context when tasks run
- home-network-security-runbook
- home-network-troubleshooting
tasks:
- id: run-pipeline
name: "Run Pipeline"
description: "Copy latest pcaps and run analysis containers"
prompt: |
Copy the latest pcap files from the router and run the analysis pipeline.
- id: check-dashboard
name: "Check Dashboard"
description: "Review dashboard for anomalies in the last 24h"
prompt: |
Check the security dashboard for anomalies in the last 24 hours.
agent: "" # optional per-task agent override
bootstrap_prompt: |
You are bootstrapping the project "{name}" (ID: {id}).
Description: {description}
Available tasks: {tasks}
Execute each task and document operational knowledge gained.
Create a memory called '{id}-runbook' with execution flow, file locations,
dependencies, timing, and troubleshooting tips.
bootstrap_agent: "" # optional; uses project.agent or config.default_agentProjects contain no secrets and are the canonical home for reusable operational prompts. Cron jobs reference these tasks by id instead of embedding their own prompts. Agent resolution: task.agent → project.agent → config.default_agent.
Bootstrap: clicking "Bootstrap Runbook" on the /projects page runs the configured agent with the interpolated bootstrap_prompt (placeholders: {id}, {name}, {description}, {tasks}, {memories}). The agent typically executes the project's tasks and uses write_memory to create an operational runbook. The button disappears once the {id}-runbook memory exists.
Project tools (available to all agents, no YAML config needed):
| Tool | Description |
|---|---|
list_projects |
Returns id, name, description for all projects |
read_project(id) |
Returns full project details including tasks and prompts |
| Route | Description |
|---|---|
GET / |
Chat interface |
GET /superpowers |
Superpower cards (config, secrets masked, bootstrap) |
GET /projects |
Project cards with per-task Run buttons |
GET /memories |
Memory browser |
GET /crons |
Cron job list + run history |
GET /webhooks |
Webhook list + call history |
WS /ws/chat?token=<key> |
WebSocket chat endpoint |
| Command | Description |
|---|---|
/start or /help |
Show available commands |
/agents |
List configured agents |
/agent <id> |
Switch active agent |
/crons |
List cron jobs |
/clear |
Clear conversation history |