A self-hosted workspace for small teams — tasks, chat, docs, and an AI agent (Hermes)
that connects to your calendar, email, GitHub, and finances via Telegram or the web.
- What is Loop?
- Features
- Architecture
- Tech Stack
- Prerequisites
- Quick Start — Local Development
- Environment Variables
- Production Deployment
- Hermes AI Agent
- Integrations
- Adding Skills to Hermes
- Database Migrations
- Backups
- Upgrading
- Development Guide
- Contributing
- License
Loop is a full-stack, self-hosted workspace OS built for small teams who want to own their data. It replaces the fragmented combination of Notion + Linear + Slack + a generic AI assistant with a single platform you deploy and control.
The core differentiator is Hermes — an autonomous AI agent sidecar that connects to every part of your workspace via the Model Context Protocol (MCP). Hermes can read your open tasks, upcoming calendar events, unread emails, and recent GitHub activity, and act on them — proactively, on a schedule, or on demand from Telegram.
- Kanban boards with drag-and-drop columns
- Projects with descriptions, owners, and status
- Tasks with assignees, due dates, priorities, labels, and subtasks
- Task comments and activity history
- Channels (public/private) with threaded replies
- Direct messages
- Reactions, pinning, and message search
- Real-time delivery via WebSocket (Redis pub/sub fan-out)
- Collaborative markdown documents scoped to workspaces
- Rich editing with live save
- Claude / OpenRouter-powered agent connected to all workspace data via MCP
- Reachable via Telegram bot or built-in web chat UI
- Runs scheduled jobs (daily digest, memory gardening, etc.)
- Semantic workspace memory backed by pgvector
- Extensible via plain-text skill files
- Parallel subagent delegation for complex multi-step tasks
- Manual income and expense ledger with categorisation
- Summary views and reporting via Hermes
- Markdown blog with slug management and SEO metadata
- SEO audit tools connected to Google Search Console
- Optionally paired with a separate Vercel/Supabase marketing site
- Google Calendar — read events, create meetings
- Gmail — read and draft emails
- Google Drive — browse and fetch files
- Google Search Console — search analytics, URL inspection, sitemap management
- GitHub — repository data, issues, PRs, webhook events (GitHub App)
Browser / Telegram
│
▼
┌─────────────┐ REST / WS ┌──────────────────────────────┐
│ Next.js 15 │ ◄────────────────► │ FastAPI (api:8000) │
│ (web:3000) │ │ ├─ REST /api/v1/… │
└─────────────┘ │ ├─ WS /ws/{workspace_id} │
│ └─ MCP /mcp/mcp │
└────────────┬─────────────────┘
│ MCP (HTTP)
┌────────────▼─────────────────┐
│ Hermes agent (hermes:9119) │
│ ├─ Telegram gateway │
│ ├─ Web chat gateway │
│ └─ Cron scheduler │
└──────────────────────────────┘
Infrastructure
PostgreSQL + pgvector ← primary database + semantic search
Redis ← WebSocket pub/sub + caching
MinIO ← S3-compatible file storage
SearXNG ← private web search (no API key required)
Caddy ← reverse proxy + automatic TLS (production)
Data flow examples:
| Trigger | Path |
|---|---|
| User creates a task in the browser | Browser → Next.js → FastAPI REST → PostgreSQL |
| Hermes sends a channel message | Hermes → MCP tool → FastAPI → Redis pub/sub → WebSocket → Next.js |
| Telegram message arrives | Telegram → Hermes gateway → MCP tools → FastAPI → PostgreSQL |
| Scheduled digest | Cron → Hermes → MCP tools → lno_send_channel_message → WS → Next.js |
| Layer | Technology |
|---|---|
| Frontend | Next.js 15, TypeScript, Tailwind CSS, Better Auth |
| Backend | FastAPI, Python 3.12, SQLAlchemy 2, Alembic |
| Database | PostgreSQL 16 + pgvector |
| Cache / PubSub | Redis 7 |
| File storage | MinIO (S3-compatible) |
| AI agent | Hermes CLI, OpenRouter / Claude, MCP protocol |
| Web search | SearXNG (self-hosted, no API key) |
| Infrastructure | Docker Compose, Caddy 2, GitHub Actions |
All environments:
- Docker 24+
- Docker Compose v2 (
docker compose, notdocker-compose) - Git
Production only:
- A Linux VPS or bare-metal server with at least 2 GB RAM (4 GB recommended if running Hermes)
- A domain name with an A record pointing to your server's IP
- Ports 80 and 443 open in your firewall
Optional (for full functionality):
- Telegram Bot token (for the Hermes Telegram gateway)
- OpenRouter API key (for Hermes AI — free tier available)
- Google OAuth credentials (for Calendar, Gmail, Drive, Search Console integrations)
- GitHub App credentials (for GitHub integration)
# 1. Clone the repository
git clone https://github.com/LNO360/LOOP.git
cd LOOP
# 2. Copy the development environment file
cp .env.example .env
# 3. Edit .env — the minimum required values are pre-filled for local dev.
# Review and adjust if needed (see Environment Variables below).
nano .env
# 4. Start all services
docker compose up -d
# 5. Run database migrations
docker compose exec api alembic upgrade head
# 6. Open the app
open http://localhost:3000Local service URLs:
| Service | URL |
|---|---|
| Web UI | http://localhost:3000 |
| API (FastAPI docs) | http://localhost:8000/docs |
| MinIO console | http://localhost:9001 |
| SearXNG | http://localhost:8080 |
To watch logs across all services:
docker compose logs -fTo stop everything:
docker compose downCopy .env.example (development) or .env.prod.example (production) and fill in the values. Every variable has an inline comment in those files — this table is a summary.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string, e.g. postgresql+asyncpg://lno:password@postgres:5432/lno_os |
SYNC_DATABASE_URL |
Yes | Same host/db but with postgresql:// (used by Alembic) |
REDIS_URL |
Yes | Redis connection string, e.g. redis://redis:6379 |
BETTER_AUTH_SECRET |
Yes | Random 32-char secret for session signing. Generate: openssl rand -hex 32 |
BETTER_AUTH_URL |
Yes | Your app's public URL, e.g. https://yourdomain.com |
NEXT_PUBLIC_API_URL |
Yes | Public API URL, e.g. https://yourdomain.com |
NEXT_PUBLIC_WS_URL |
Yes | Public WebSocket URL, e.g. wss://yourdomain.com |
| Variable | Required | Description |
|---|---|---|
S3_ENDPOINT_URL |
Yes | MinIO endpoint, e.g. http://minio:9000 |
S3_ACCESS_KEY |
Yes | MinIO root user |
S3_SECRET_KEY |
Yes | MinIO root password |
S3_BUCKET_NAME |
Yes | Bucket name (created automatically on first run) |
S3_REGION |
No | Region string (default: us-east-1) |
| Variable | Required | Description |
|---|---|---|
INTEGRATION_ENCRYPTION_KEY |
Yes | Fernet key for encrypting OAuth tokens at rest. Generate: python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" |
| Variable | Required | Description |
|---|---|---|
HERMES_SERVICE_TOKEN |
Yes | Shared token between API and Hermes. Generate: python3 -c "import secrets; print(secrets.token_hex(32))" |
OPENROUTER_API_KEY |
For Hermes | OpenRouter API key (primary). Free tier works. |
OPENROUTER_API_KEY_SECONDARY |
No | Second OpenRouter key for fallback quota |
GROQ_API_KEY |
No | Groq API key as a third fallback |
| Variable | Required | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
No | From @BotFather |
TELEGRAM_ALLOWED_USERS |
No | Comma-separated numeric Telegram user IDs allowed to message Hermes |
TELEGRAM_BOT_NAME |
No | Your bot's @username (without the @) |
| Variable | Required | Description |
|---|---|---|
GOOGLE_CLIENT_ID |
No | OAuth 2.0 client ID from Google Cloud Console |
GOOGLE_CLIENT_SECRET |
No | OAuth 2.0 client secret |
| Variable | Required | Description |
|---|---|---|
GITHUB_APP_ID |
No | GitHub App ID (from app settings page) |
GITHUB_APP_NAME |
No | GitHub App slug name |
LNO_SECRETS_DIR |
No | Path to directory containing github-app.private-key.pem (default: ~/.lno-secrets) |
| Variable | Required | Description |
|---|---|---|
POSTGRES_USER |
Prod | PostgreSQL user |
POSTGRES_PASSWORD |
Prod | PostgreSQL password (use a strong random value) |
POSTGRES_DB |
Prod | Database name |
HERMES_CONTAINER |
No | Docker container name of the Hermes service (default: lno-os-hermes-1) |
DOCKER_GID |
No | Host Docker socket GID — needed for docker exec from API workers. Get with: stat -c '%g' /var/run/docker.sock |
DEFAULT_WORKSPACE_ID |
No | UUID of the primary workspace (used by Hermes scheduled digest) |
DIGEST_WORKSPACE_IDS |
No | Comma-separated workspace IDs for the morning digest cron |
Any Linux VPS works. Tested on Ubuntu 22.04. Minimum specs:
- 2 GB RAM (4 GB if running Hermes with browser toolset)
- 20 GB disk
- Ports 80, 443, and 22 open
Install Docker:
curl -fsSL https://get.docker.com | shgit clone https://github.com/LNO360/LOOP.git /opt/loop
cd /opt/loopcp .env.prod.example .env.prod
nano .env.prodEvery variable has an inline comment. At minimum, set:
POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DBBETTER_AUTH_SECRET(random 32-char hex)BETTER_AUTH_URL,NEXT_PUBLIC_API_URL,NEXT_PUBLIC_WS_URL(your domain)S3_ACCESS_KEY,S3_SECRET_KEYINTEGRATION_ENCRYPTION_KEY(Fernet key)HERMES_SERVICE_TOKEN(random 32-char hex)OPENROUTER_API_KEY(for Hermes)
Edit Caddyfile and replace the placeholders:
{
email [email protected] # real email — used for Let's Encrypt cert notices
}
yourdomain.com {
@backend path /api/* /ws/*
reverse_proxy @backend api:8000
reverse_proxy web:3000
encode gzip zstd
}Caddy fetches a free TLS certificate from Let's Encrypt automatically on first start. Your DNS A record must be pointing at the server before you deploy.
If using the GitHub integration, place the private key at ~/.lno-secrets/github-app.private-key.pem on the server (never inside the repo tree). The LNO_SECRETS_DIR variable controls where Docker mounts it.
bash deploy.shThe deploy.sh script:
- Pulls the latest code (
git pull) - Creates a timestamped PostgreSQL backup to
/opt/lno-backups/(retains last 7) - Builds Docker images for
apiandweb - Runs Alembic migrations (
alembic upgrade head) - Restarts all services via
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d - Runs an API health check
On subsequent deploys, just re-run bash deploy.sh.
The included workflow at .github/workflows/deploy.yml triggers a Coolify deploy webhook on every push to main. Add these secrets to your GitHub repository:
| Secret | Value |
|---|---|
COOLIFY_WEBHOOK_URL |
Your Coolify deploy webhook URL |
COOLIFY_WEBHOOK_TOKEN |
The webhook token |
If you're not using Coolify, you can adapt the workflow to SSH into your server and run deploy.sh directly.
Hermes is the AI brain of Loop. It runs as a separate Docker container alongside the API, connects to it via MCP, and can read and write everything in your workspace.
- The Loop API exposes an MCP server at
/mcp/mcp - Hermes authenticates with a shared
HERMES_SERVICE_TOKEN - Users message Hermes via Telegram or the built-in web chat
- Hermes invokes MCP tools to read/write tasks, memory, channels, integrations, etc.
- Scheduled cron jobs run Hermes automatically (every 2 hours + morning digest)
Hermes uses OpenRouter by default, which gives access to hundreds of models including free-tier options. Edit hermes/config.yaml to change the model:
model:
default: "openrouter/owl-alpha" # change to any OpenRouter model ID
provider: "openrouter"Popular choices:
openrouter/owl-alpha— fast, free tier availableanthropic/claude-opus-4-8— most capable (paid)anthropic/claude-sonnet-4-6— strong balance of capability and cost (paid)deepseek/deepseek-v4-flash— fast and cheap
You can also configure Claude directly (without OpenRouter) by adding the Anthropic provider:
providers:
anthropic:
api_key: "${ANTHROPIC_API_KEY}"
model:
default: "claude-sonnet-4-6"
provider: "anthropic"Hermes supports automatic fallback when the primary model hits rate limits:
fallback_providers:
- provider: custom:openrouter2
model: openrouter/free
- provider: custom:groq
model: llama-3.3-70b-versatileToolsets are enabled in hermes/config.yaml under toolsets:. Each toolset adds a set of capabilities:
| Toolset | What it enables |
|---|---|
hermes-cli |
File system access, memory read/write, terminal commands |
mcp |
All Loop workspace tools (tasks, channels, memory, integrations) |
skills |
Reads skill files from apps/hermes-skills/ and apps/user-agent-skills/ |
web |
Web search (via SearXNG) and page fetching |
delegation |
Spawn parallel subagents for multi-step tasks |
session_search |
Search past conversation history |
todo |
Multi-step planning within a single turn |
cronjob |
Create and edit scheduled jobs from chat |
vision |
Analyse images and screenshots sent via Telegram |
code_execution |
Run sandboxed Python scripts for data processing |
clarify |
Ask the user for clarification when a request is ambiguous |
browser |
Headless Chromium for JS-heavy websites |
- Create a bot via @BotFather → get a bot token
- Get your numeric Telegram user ID (message @userinfobot)
- Set in
.env/.env.prod:TELEGRAM_BOT_TOKEN=your_bot_token_here TELEGRAM_ALLOWED_USERS=123456789 # your numeric ID TELEGRAM_BOT_NAME=your_bot_username - Restart Hermes:
docker compose restart hermes - Send a message to your bot — it should respond within a few seconds
The built-in web chat is available in the Loop UI without any additional setup. It communicates with Hermes via the FastAPI /hermes/chat endpoint, which proxies to the Hermes dashboard HTTP API.
Hermes maintains a tiered memory system:
| Tier | Storage | When to use |
|---|---|---|
| Hot | apps/hermes-data/memories/MEMORY.md |
Critical facts needed every session (≤ 6 KB) |
| Warm | PostgreSQL workspace_memories table |
Everything else — searchable via lno_search_workspace_memory |
| Cold | Past chat sessions | Historical context — searchable via session_search |
You can ask Hermes to remember things directly: "Remember that our sprint ends every Friday" and it will store that in the appropriate tier.
Ask Hermes to create a cron:
"Every morning at 8am, check my tasks and send me a digest in #general"
Hermes creates a cron configuration and runs it using a lightweight, cost-optimised model profile (hermes/profile-cron-config.yaml). You can manage crons from Telegram or the web chat.
- Go to Google Cloud Console
- Create a project → APIs & Services → Enable APIs
- Enable: Gmail API, Google Calendar API, Google Drive API, Google Search Console API
- OAuth consent screen → configure with your domain and scopes:
https://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/calendarhttps://www.googleapis.com/auth/drive.readonlyhttps://www.googleapis.com/auth/webmasters.readonly
- Credentials → Create OAuth 2.0 Client ID (type: Web application)
- Authorised redirect URI:
https://yourdomain.com/api/v1/integrations/google/callback
- Authorised redirect URI:
- Set in
.env.prod:GOOGLE_CLIENT_ID=your_client_id GOOGLE_CLIENT_SECRET=your_client_secret
- In the Loop UI → Settings → Integrations → connect Google
- Go to GitHub Settings → Developer settings → GitHub Apps → New GitHub App
- Set the webhook URL to
https://yourdomain.com/api/v1/github/webhook - Grant the permissions your team needs (e.g. issues read/write, pull requests read)
- Generate a private key and download the
.pemfile - Place the
.pemfile on your server at~/.lno-secrets/github-app.private-key.pem - Set in
.env.prod:GITHUB_APP_ID=123456 GITHUB_APP_NAME=your-app-slug LNO_SECRETS_DIR=/root/.lno-secrets
See Hermes — Telegram setup above.
Skills are plain-text markdown files that give Hermes new workflows. Drop a .md file in apps/hermes-skills/ — Hermes picks it up automatically (the directory is bind-mounted into the container).
Example skill (apps/hermes-skills/weekly-review.md):
# Weekly Review
Run every Friday at 5pm. Summarise this week's completed tasks across all projects,
highlight anything overdue, and post the summary to #general.
## Steps
1. Call lno_list_tasks with status=done and filter to this week
2. Call lno_list_tasks with status=overdue
3. Format a markdown summary grouped by project
4. Call lno_send_channel_message to post it to #generalUser-created skills (written from within the UI or by Hermes itself) are stored in apps/user-agent-skills/ — a writable mount that persists across container restarts.
See apps/hermes-skills/ for the included example skills.
Loop uses Alembic for schema migrations.
Run pending migrations (done automatically by deploy.sh in production):
docker compose exec api alembic upgrade headCheck current migration state:
docker compose exec api alembic currentCreate a new migration after changing a SQLAlchemy model:
docker compose exec api alembic revision --autogenerate -m "add widget table"Review the generated file in apps/api/alembic/versions/ before applying it.
Roll back one migration:
docker compose exec api alembic downgrade -1deploy.sh automatically creates a compressed backup to /opt/lno-backups/ before every deploy and retains the last 7 backups.
Manual backup:
docker compose exec postgres pg_dump -U lno lno_os | gzip > backup-$(date +%Y%m%d).sql.gzRestore:
gunzip -c backup-20260101.sql.gz | docker compose exec -T postgres psql -U lno lno_osHermes memory files live in the hermes-data Docker volume and in the bind-mounted apps/hermes-data/ directory.
Back up the volume:
docker run --rm \
-v lnoos_hermes-data:/data \
-v /opt/lno-backups:/out \
alpine tar czf /out/hermes-data-$(date +%Y%m%d).tar.gz -C /data .Or use the included backup script to sync Hermes data to the host:
pnpm backup:hermes # sync memory + knowledge files
pnpm backup:hermes:full # sync + include database backupUse the MinIO Client (mc) to mirror your bucket:
mc alias set loop http://localhost:9000 your_access_key your_secret_key
mc mirror loop/your-bucket /opt/lno-backups/minio/cd /opt/loop
# Pull latest changes
git pull
# Deploy (builds images, runs migrations, restarts services)
bash deploy.shThe deploy script handles migrations automatically. Check the release notes for any manual steps before upgrading major versions.
API only (with hot-reload):
docker compose up -d postgres redis minio searxng
cd apps/api
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp ../../.env .env
uvicorn main:app --reload --port 8000Frontend only:
pnpm install
pnpm dev:webBoth (Docker Compose dev mode includes all services):
docker compose up -dpnpm dev:web # start Next.js dev server
pnpm dev:api # start FastAPI with hot-reload (uvicorn)
pnpm db:migrate # run alembic upgrade head
pnpm db:makemigration "message" # create a new migration
pnpm backup:hermes # sync Hermes memory/knowledge to host
pnpm cleanup:workspaces # list stale workspaces
pnpm cleanup:workspaces:dry # dry-run stale workspace cleanupLoop/
├── apps/
│ ├── api/ # FastAPI backend
│ │ ├── routers/ # Route handlers (tasks, channels, auth, …)
│ │ ├── models/ # SQLAlchemy models
│ │ ├── services/ # Business logic
│ │ ├── mcp_server/ # MCP server exposed to Hermes
│ │ ├── agents/ # AI agent runners
│ │ └── alembic/ # Database migrations
│ ├── web/ # Next.js 15 frontend
│ │ └── src/
│ │ ├── app/ # App Router pages
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks
│ │ ├── lib/ # API client, utilities
│ │ └── store/ # Zustand state
│ ├── hermes-skills/ # Built-in Hermes skill files (read-only mount)
│ ├── user-agent-skills/ # User-created skill files (writable mount)
│ └── hermes-data/ # Hermes memory files (bind-mounted)
│ ├── memories/
│ │ ├── MEMORY.md # Hot memory (loaded every session)
│ │ └── USER.md # User profile memory
│ └── knowledge/ # Long-form reference documents
├── hermes/ # Hermes container build context
│ ├── Dockerfile
│ ├── config.yaml # Hermes agent configuration
│ ├── SOUL.md # Hermes system prompt / personality
│ └── profile-cron-config.yaml # Lightweight config for scheduled runs
├── packages/
│ └── shared/ # Shared TypeScript types
├── infra/
│ └── searxng/ # SearXNG configuration
├── docs/
│ ├── ARCHITECTURE.md
│ ├── DEPLOYMENT.md
│ └── HERMES.md
├── docker-compose.yml # Development compose file
├── docker-compose.prod.yml # Production compose file
├── Caddyfile # Caddy reverse proxy config
├── deploy.sh # Production deploy script
└── .env.example # Development environment template
# API tests (pytest)
docker compose exec api pytest
# Or locally with the venv active:
cd apps/api && pytestContributions are welcome. Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes and add tests where applicable
- Open a pull request against
main
For significant changes, open an issue first to discuss the approach.
Apache 2.0 — see LICENSE.
- Architecture — detailed system components and data flow
- Deployment — step-by-step deployment reference
- Hermes agent — Hermes configuration, skills, and toolsets





