Open-source, model-agnostic, self-hosted personal AI agents. Your agents, your models, your data.
Python
0
192 commits
updated Oct 7, 2026
Open-source, model-agnostic, self-hosted personal AI agents.
An alternative to OpenAI Dots that works with any LLM, runs on your infrastructure, and respects your privacy.
| Feature | OpenAI Dots | Motes |
|---|---|---|
| Model choice | GPT only | Any LLM (OpenAI, Anthropic, local) |
| Data location | OpenAI servers | Your server |
| EU/UK | Not available | Works everywhere |
| Integrations | Limited | 20 services, community-driven |
| Voice calls | Yes | Yes (Azure Realtime API) |
| iOS app | No | Native SwiftUI |
| Self-hosted | No | Docker Compose |
| Open source | No | MIT License |
Built-in (free): Weather, Web Search, Apple Reminders, Apple Notes, Maps, News, Local Files
OAuth: Gmail, Google Calendar
API Key: GitHub, Slack, Todoist, Notion, Spotify, Home Assistant, Outlook, Flights & Hotels, Telegram
Background Scanner (every 60s)
├─ scan_gmail() → new email? → notify in chat
├─ scan_calendar() → upcoming event? → notify in chat
└─ check_patterns() → time-based suggestion? → notify in chat
│
┌──────────────┼──────────────┐
│ │ │
💬 Chat 📞 Voice Call 🔔 Stored
(SSE push) (TTS speech) (for later)
Pattern Learning: Every message (chat or voice) is analyzed for topics. After 3+ messages about "email" at the same hour, Motes proactively suggests checking your inbox at that time.
Memory Tools: The agent has memory_save and memory_recall tools.
When you say "recuerda que mi hija se llama Victoria", the agent calls
memory_save(fact="User's daughter is named Victoria", category="family").
Next time you ask about family, it recalls this fact.
git clone https://github.com/levalencia/motes.git
cd motes
docker compose up -d # PostgreSQL + Redis + Jaeger
cd backend
cp .env.example .env # Edit with your API keys
uv sync
uv run uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
cd frontend
npm install
npm run dev # http://localhost:5173
Open http://localhost:5173. Create your account (first user = admin).
All config via MOTES_ prefix or .env file in backend/:
# Required: at least one LLM provider
MOTES_DATABASE_URL=postgresql+asyncpg://motes:motes_dev@localhost:5433/motes
MOTES_REDIS_URL=redis://localhost:6380/0
MOTES_SECRET_KEY=your-secret-key-here # Auto-generated if not set, but set it to survive restarts
# LLM Provider (example: Azure AI Foundry with Anthropic)
# Configure via the web UI at /providers
Motes needs at least one LLM provider to chat. Configure it via the web UI after first login:
http://localhost:5173 and create your account| Provider | Base URL | API Key | Model |
|---|---|---|---|
| OpenAI | https://api.openai.com/v1 | sk-... | gpt-4o |
| Anthropic | https://api.anthropic.com/v1 | sk-ant-... | claude-sonnet-4-20250514 |
| Azure AI Foundry | https://your-resource.openai.azure.com/openai/deployments/your-deployment | Azure key | gpt-4o |
| OpenRouter | https://openrouter.ai/api/v1 | sk-or-... | anthropic/claude-sonnet-4 |
| Ollama (local) | http://localhost:11434/v1 | (empty) | llama3.1 |
openai for most providers, anthropic for Anthropic directQuickest start: Sign up at OpenRouter (free tier available), paste the API key, and pick any model.
Voice calls require Azure OpenAI Realtime API. If you only need chat, skip this.
gpt-4o-realtime-preview or gpt-realtime-2.1-mini modelwss://YOUR-RESOURCE.openai.azure.com/openai/v1/realtime?model=gpt-realtime-2.1-mini| Model | Latency | Cost | Recommendation |
|---|---|---|---|
gpt-realtime-2.1-mini | ~200ms | Low | Best for voice (fast + cheap) |
gpt-4o-realtime-preview | ~400ms | High | Better reasoning, slower |
No Azure? Voice calls won't work, but chat, tools, proactive — everything else works fine with any provider.
Set a custom voice personality per user. Go to Settings or call the API:
curl -X POST http://localhost:8001/api/voice/voice-personality \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"personality": "Colombian paisa accent, warm and casual"}'
Go to Services in the web UI:
The iOS app requires HTTPS (App Transport Security). Tailscale provides free, trusted HTTPS certs:
# Get your Tailscale domain
tailscale status --json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['CertDomains'])"
# Output: ['your-machine.tailXXXXX.ts.net']
# Generate cert
cd backend
tailscale cert --cert-file ts-cert.pem --key-file ts-key.pem your-machine.tailXXXXX.ts.net
# Start backend with HTTPS
uv run uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload \
--ssl-keyfile ts-key.pem --ssl-certfile ts-cert.pem
cd MotesiOS/MotesiOS
open MotesiOS.xcodeproj
https://your-machine.tailXXXXX.ts.net:8001"App Transport Security" error: The server must use HTTPS with a trusted cert. Use tailscale cert as described above. Self-signed certs do NOT work on iOS.
"Agent not found" on Call tab: Make sure you've created at least one agent (happens automatically on first login).
Call audio not working: Ensure microphone permission is granted. Echo cancellation requires setVoiceProcessingEnabled(true) on AVAudioEngine (already implemented).
Login screen doesn't appear after reinstall: Keychain persists across installs. The app auto-clears stale tokens when the server URL isn't configured.
# If running with tee
tail -f /tmp/motes-backend.log
# Or check uvicorn output directly
# Logs use structlog JSON format with correlation IDs
scanner_gmail_notification — New email detected
event_bus_published — Proactive notification sent
realtime_call_connected — Voice call established
realtime_call_audio_forwarded — iOS audio forwarded to Azure
realtime_call_wav_sent — Agent audio sent to client
realtime_call_tool_call — Agent used a tool during call
In Xcode console, look for [Motes] prefix:
[Motes] Audio sent OK: 12345 chars
[Motes] WebSocket send error: ...
graph TB
subgraph Clients
Web["🌐 SvelteKit<br/>:5173"]
iOS["📱 iOS SwiftUI<br/>via Tailscale"]
API["🔌 Any HTTP Client"]
end
subgraph Backend["FastAPI Backend :8001"]
Chat["💬 Chat SSE<br/>Streaming"]
Voice["📞 Voice WS<br/>Azure Realtime"]
Tools["🔧 Tools (20)<br/>Gmail, Calendar,<br/>Weather, Maps..."]
Memory["🧠 Memory<br/>Save & Recall"]
Proactive["⚡ Proactive<br/>Scanner (60s)"]
EventBus["📡 Event Bus<br/>Pub/Sub"]
Logging["📊 structlog<br/>+ OTEL"]
end
subgraph Infrastructure
PG["🐘 PostgreSQL<br/>:5433"]
Redis["⚡ Redis<br/>:6380"]
Jaeger["🔍 Jaeger<br/>:16687"]
end
subgraph External
LLM["🤖 LLM Provider<br/>OpenAI / Anthropic / Azure"]
AzureRT["🎙️ Azure Realtime<br/>gpt-realtime-2.1-mini"]
Gmail["📧 Gmail API"]
CalAPI["📅 Calendar API"]
end
Web --> Chat
iOS --> Chat
iOS --> Voice
API --> Chat
Chat --> Tools
Chat --> Memory
Voice --> Tools
Voice --> Memory
Voice --> AzureRT
Chat --> LLM
Proactive --> EventBus
EventBus --> Chat
EventBus --> Voice
Proactive --> Gmail
Proactive --> CalAPI
Tools --> Gmail
Tools --> CalAPI
Backend --> PG
Backend --> Redis
Logging --> Jaeger
sequenceDiagram
participant iPhone
participant Backend
participant Azure as Azure Realtime API
iPhone->>Backend: WebSocket connect (wss://)
iPhone->>Backend: {"type": "auth", "token": "..."}
Backend->>Azure: WebSocket connect (wss://)
Backend->>Azure: session.update (semantic_vad, 24kHz PCM16)
Backend->>Azure: response.create (greeting)
Azure-->>Backend: audio chunks (PCM16)
Backend-->>iPhone: {"type": "audio_wav", "data": "base64..."}
Note over iPhone: User speaks
iPhone->>Backend: {"type": "audio", "data": "base64 PCM16 24kHz"}
Backend->>Azure: input_audio_buffer.append
Note over Azure: Semantic VAD detects end of speech
Azure-->>Backend: response audio chunks
Backend-->>iPhone: {"type": "audio_wav", "data": "base64..."}
iPhone->>Backend: {"type": "end"}
Backend->>Azure: close
flowchart LR
Scanner["🔄 Background<br/>Scanner<br/>(every 60s)"]
Scanner --> Gmail["📧 Gmail<br/>new emails?"]
Scanner --> Cal["📅 Calendar<br/>upcoming events?"]
Scanner --> Patterns["💡 Patterns<br/>time-based habits?"]
Gmail --> Notify["📡 Event Bus"]
Cal --> Notify
Patterns --> Notify
Notify --> Chat["💬 Chat<br/>(SSE push)"]
Notify --> Voice["📞 Voice Call<br/>(TTS speech)"]
Notify --> DB["💾 Database<br/>(for later)"]
| Service | Port | Description |
|---|---|---|
| Backend | 8001 | FastAPI + WebSocket |
| Frontend | 5173 | SvelteKit dev server |
| PostgreSQL | 5433 | Database |
| Redis | 6380 | Cache |
| Jaeger | 16687 | Trace UI |
cd backend && uv run pytest -m unitcd backend && uv run ruff check .cd frontend && npx svelte-checkprint())MIT
Open-source, model-agnostic, self-hosted personal AI agents. Your agents, your models, your data.
Python
0
192 commits
updated Oct 7, 2026
Open-source, model-agnostic, self-hosted personal AI agents.
An alternative to OpenAI Dots that works with any LLM, runs on your infrastructure, and respects your privacy.
| Feature | OpenAI Dots | Motes |
|---|---|---|
| Model choice | GPT only | Any LLM (OpenAI, Anthropic, local) |
| Data location | OpenAI servers | Your server |
| EU/UK | Not available | Works everywhere |
| Integrations | Limited | 20 services, community-driven |
| Voice calls | Yes | Yes (Azure Realtime API) |
| iOS app | No | Native SwiftUI |
| Self-hosted | No | Docker Compose |
| Open source | No | MIT License |
Built-in (free): Weather, Web Search, Apple Reminders, Apple Notes, Maps, News, Local Files
OAuth: Gmail, Google Calendar
API Key: GitHub, Slack, Todoist, Notion, Spotify, Home Assistant, Outlook, Flights & Hotels, Telegram
Background Scanner (every 60s)
├─ scan_gmail() → new email? → notify in chat
├─ scan_calendar() → upcoming event? → notify in chat
└─ check_patterns() → time-based suggestion? → notify in chat
│
┌──────────────┼──────────────┐
│ │ │
💬 Chat 📞 Voice Call 🔔 Stored
(SSE push) (TTS speech) (for later)
Pattern Learning: Every message (chat or voice) is analyzed for topics. After 3+ messages about "email" at the same hour, Motes proactively suggests checking your inbox at that time.
Memory Tools: The agent has memory_save and memory_recall tools.
When you say "recuerda que mi hija se llama Victoria", the agent calls
memory_save(fact="User's daughter is named Victoria", category="family").
Next time you ask about family, it recalls this fact.
git clone https://github.com/levalencia/motes.git
cd motes
docker compose up -d # PostgreSQL + Redis + Jaeger
cd backend
cp .env.example .env # Edit with your API keys
uv sync
uv run uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
cd frontend
npm install
npm run dev # http://localhost:5173
Open http://localhost:5173. Create your account (first user = admin).
All config via MOTES_ prefix or .env file in backend/:
# Required: at least one LLM provider
MOTES_DATABASE_URL=postgresql+asyncpg://motes:motes_dev@localhost:5433/motes
MOTES_REDIS_URL=redis://localhost:6380/0
MOTES_SECRET_KEY=your-secret-key-here # Auto-generated if not set, but set it to survive restarts
# LLM Provider (example: Azure AI Foundry with Anthropic)
# Configure via the web UI at /providers
Motes needs at least one LLM provider to chat. Configure it via the web UI after first login:
http://localhost:5173 and create your account| Provider | Base URL | API Key | Model |
|---|---|---|---|
| OpenAI | https://api.openai.com/v1 | sk-... | gpt-4o |
| Anthropic | https://api.anthropic.com/v1 | sk-ant-... | claude-sonnet-4-20250514 |
| Azure AI Foundry | https://your-resource.openai.azure.com/openai/deployments/your-deployment | Azure key | gpt-4o |
| OpenRouter | https://openrouter.ai/api/v1 | sk-or-... | anthropic/claude-sonnet-4 |
| Ollama (local) | http://localhost:11434/v1 | (empty) | llama3.1 |
openai for most providers, anthropic for Anthropic directQuickest start: Sign up at OpenRouter (free tier available), paste the API key, and pick any model.
Voice calls require Azure OpenAI Realtime API. If you only need chat, skip this.
gpt-4o-realtime-preview or gpt-realtime-2.1-mini modelwss://YOUR-RESOURCE.openai.azure.com/openai/v1/realtime?model=gpt-realtime-2.1-mini| Model | Latency | Cost | Recommendation |
|---|---|---|---|
gpt-realtime-2.1-mini | ~200ms | Low | Best for voice (fast + cheap) |
gpt-4o-realtime-preview | ~400ms | High | Better reasoning, slower |
No Azure? Voice calls won't work, but chat, tools, proactive — everything else works fine with any provider.
Set a custom voice personality per user. Go to Settings or call the API:
curl -X POST http://localhost:8001/api/voice/voice-personality \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"personality": "Colombian paisa accent, warm and casual"}'
Go to Services in the web UI:
The iOS app requires HTTPS (App Transport Security). Tailscale provides free, trusted HTTPS certs:
# Get your Tailscale domain
tailscale status --json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['CertDomains'])"
# Output: ['your-machine.tailXXXXX.ts.net']
# Generate cert
cd backend
tailscale cert --cert-file ts-cert.pem --key-file ts-key.pem your-machine.tailXXXXX.ts.net
# Start backend with HTTPS
uv run uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload \
--ssl-keyfile ts-key.pem --ssl-certfile ts-cert.pem
cd MotesiOS/MotesiOS
open MotesiOS.xcodeproj
https://your-machine.tailXXXXX.ts.net:8001"App Transport Security" error: The server must use HTTPS with a trusted cert. Use tailscale cert as described above. Self-signed certs do NOT work on iOS.
"Agent not found" on Call tab: Make sure you've created at least one agent (happens automatically on first login).
Call audio not working: Ensure microphone permission is granted. Echo cancellation requires setVoiceProcessingEnabled(true) on AVAudioEngine (already implemented).
Login screen doesn't appear after reinstall: Keychain persists across installs. The app auto-clears stale tokens when the server URL isn't configured.
# If running with tee
tail -f /tmp/motes-backend.log
# Or check uvicorn output directly
# Logs use structlog JSON format with correlation IDs
scanner_gmail_notification — New email detected
event_bus_published — Proactive notification sent
realtime_call_connected — Voice call established
realtime_call_audio_forwarded — iOS audio forwarded to Azure
realtime_call_wav_sent — Agent audio sent to client
realtime_call_tool_call — Agent used a tool during call
In Xcode console, look for [Motes] prefix:
[Motes] Audio sent OK: 12345 chars
[Motes] WebSocket send error: ...
graph TB
subgraph Clients
Web["🌐 SvelteKit<br/>:5173"]
iOS["📱 iOS SwiftUI<br/>via Tailscale"]
API["🔌 Any HTTP Client"]
end
subgraph Backend["FastAPI Backend :8001"]
Chat["💬 Chat SSE<br/>Streaming"]
Voice["📞 Voice WS<br/>Azure Realtime"]
Tools["🔧 Tools (20)<br/>Gmail, Calendar,<br/>Weather, Maps..."]
Memory["🧠 Memory<br/>Save & Recall"]
Proactive["⚡ Proactive<br/>Scanner (60s)"]
EventBus["📡 Event Bus<br/>Pub/Sub"]
Logging["📊 structlog<br/>+ OTEL"]
end
subgraph Infrastructure
PG["🐘 PostgreSQL<br/>:5433"]
Redis["⚡ Redis<br/>:6380"]
Jaeger["🔍 Jaeger<br/>:16687"]
end
subgraph External
LLM["🤖 LLM Provider<br/>OpenAI / Anthropic / Azure"]
AzureRT["🎙️ Azure Realtime<br/>gpt-realtime-2.1-mini"]
Gmail["📧 Gmail API"]
CalAPI["📅 Calendar API"]
end
Web --> Chat
iOS --> Chat
iOS --> Voice
API --> Chat
Chat --> Tools
Chat --> Memory
Voice --> Tools
Voice --> Memory
Voice --> AzureRT
Chat --> LLM
Proactive --> EventBus
EventBus --> Chat
EventBus --> Voice
Proactive --> Gmail
Proactive --> CalAPI
Tools --> Gmail
Tools --> CalAPI
Backend --> PG
Backend --> Redis
Logging --> Jaeger
sequenceDiagram
participant iPhone
participant Backend
participant Azure as Azure Realtime API
iPhone->>Backend: WebSocket connect (wss://)
iPhone->>Backend: {"type": "auth", "token": "..."}
Backend->>Azure: WebSocket connect (wss://)
Backend->>Azure: session.update (semantic_vad, 24kHz PCM16)
Backend->>Azure: response.create (greeting)
Azure-->>Backend: audio chunks (PCM16)
Backend-->>iPhone: {"type": "audio_wav", "data": "base64..."}
Note over iPhone: User speaks
iPhone->>Backend: {"type": "audio", "data": "base64 PCM16 24kHz"}
Backend->>Azure: input_audio_buffer.append
Note over Azure: Semantic VAD detects end of speech
Azure-->>Backend: response audio chunks
Backend-->>iPhone: {"type": "audio_wav", "data": "base64..."}
iPhone->>Backend: {"type": "end"}
Backend->>Azure: close
flowchart LR
Scanner["🔄 Background<br/>Scanner<br/>(every 60s)"]
Scanner --> Gmail["📧 Gmail<br/>new emails?"]
Scanner --> Cal["📅 Calendar<br/>upcoming events?"]
Scanner --> Patterns["💡 Patterns<br/>time-based habits?"]
Gmail --> Notify["📡 Event Bus"]
Cal --> Notify
Patterns --> Notify
Notify --> Chat["💬 Chat<br/>(SSE push)"]
Notify --> Voice["📞 Voice Call<br/>(TTS speech)"]
Notify --> DB["💾 Database<br/>(for later)"]
| Service | Port | Description |
|---|---|---|
| Backend | 8001 | FastAPI + WebSocket |
| Frontend | 5173 | SvelteKit dev server |
| PostgreSQL | 5433 | Database |
| Redis | 6380 | Cache |
| Jaeger | 16687 | Trace UI |
cd backend && uv run pytest -m unitcd backend && uv run ruff check .cd frontend && npx svelte-checkprint())MIT