Claude Code as a personal agent. Runs on your Claude Pro/Max/Teams subscription. No API key needed.
In April 2026, Anthropic cut off subscription access for third-party wrappers like OpenClaw, forcing them onto pay-per-token API keys. Toris still runs on subscription because it drives the Claude Agent SDK directly — the same SDK that powers the claude CLI in your terminal. Paste your claude setup-token OAuth into /setup and start talking.
Voice in, voice out. Full agent tools: Bash, Read, Grep, WebSearch, Edit, Write. Sandboxed writes. Persistent sessions you can resume, switch, search, and compact. Tool approval, watch mode, pluggable MCP memory. Multi-persona.
| Feature | Description |
|---|---|
| Voice in/out | ElevenLabs or OpenAI — choose per-user via /settings |
| Agentic execution | Claude Agent SDK with Bash, Read, Grep, WebSearch, Edit, Write |
| Sandboxed writes | All writes and command execution confined to a sandbox directory |
| Session management | /new, /continue, /sessions, /switch, /search, /compact |
| Approval modes | "Go All" (auto) or "Approve" (confirm each tool call) |
| Watch mode | Stream tool calls live to chat — Off / Live / Debug |
| Automations | List & toggle scheduled tasks via /automations |
| Conversational setup | /setup walks you through credentials + voice config in chat |
| Token verification | Every credential is tested before it's saved |
| Multi-persona | Run multiple AI personas from one codebase |
| Topic filtering | Multiple personas in one Telegram group (forum topics) |
| Rate limiting | 2s cooldown + 10/min per user |
| Admin-gated setup | ALLOWED_USER_IDS + ADMIN_USER_IDS for multi-user chats |
Telegram (voice / text / photo)
│
▼
┌──────────────────┐
│ python-telegram │
│ -bot │
└────────┬─────────┘
│
┌────┴────┐
│ STT │ ← ElevenLabs Scribe OR OpenAI Whisper (voice only)
└────┬────┘
▼
┌──────────────────┐
│ Claude Agent SDK │ ← Bash, Read, Grep, WebSearch, Edit, Write
│ + tool approval │
│ + session mgmt │
└────────┬─────────┘
│
┌────┴────┐
│ TTS │ ← ElevenLabs OR OpenAI (if audio enabled)
└────┬────┘
▼
Telegram
- Telegram Bot — Create one via @BotFather
- Voice provider (optional) — ElevenLabs or OpenAI API key. Can be configured later via
/setupor skipped for text-only mode. - Claude access — API key or subscription OAuth token. Can be configured via
/setupin Telegram.
For Docker deployment: Docker and Docker Compose. For non-Docker deployment: Python 3.11+ and Node.js 20+ (for the Claude Code CLI).
Choose ONE of these methods:
| Method | Best For | How |
|---|---|---|
| API Key | Docker, CI/CD, teams | Paste sk-ant-api-... from console.anthropic.com via /setup, or set ANTHROPIC_API_KEY in env |
| Subscription OAuth | Personal use, Pro/Max/Teams plans | Run claude setup-token on any machine with a browser, paste the result via /setup, or set CLAUDE_CODE_OAUTH_TOKEN in env |
| Mounted credentials | Docker with existing claude /login |
Mount ~/.claude/.credentials.json into the container (see docker-compose.yml) |
# Clone the repository
git clone --recurse-submodules https://github.com/toruai/toris-agent.git
cd toris-agent
# Configure
cp docker/toris.env.example docker/toris.env
# Edit docker/toris.env — you only need TELEGRAM_BOT_TOKEN to start.
# Everything else (Claude auth, voice provider) can be set via /setup in Telegram.
# Start
docker-compose up -d
# View logs
docker-compose logs -f toris
# Stop
docker-compose downBenefits:
- Isolated sandbox for file operations
- Automatic restarts on failure
- Persistent state across restarts (volumes)
- No Python / Node installation on the host
# Clone and setup
git clone --recurse-submodules https://github.com/toruai/toris-agent.git
cd toris-agent
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Install Claude Code CLI
npm install -g @anthropic-ai/claude-code
# Configure — only TELEGRAM_BOT_TOKEN is needed to start
cp .env.example .env
# Edit .env with your bot token
# Run
python bot.pySee Systemd Deployment for a production unit file.
After starting the bot for the first time, open your Telegram chat and run /setup. The bot walks you through a conversational onboarding:
- Name — how the bot should address you
- Claude auth — choose API Key or OAuth (Claude Pro/Max/Teams subscription)
- API Key: paste your
sk-ant-api-...from console.anthropic.com - OAuth: run
claude setup-tokenon any machine with a browser, paste the result
- API Key: paste your
- Voice provider — ElevenLabs, OpenAI, or skip (text-only)
- Verification — the bot tests every credential against the live API before saving
Your tokens are deleted from the Telegram chat immediately after being verified and stored. You can re-run /setup at any time, or use targeted commands: /claude_token, /elevenlabs_key, /openai_key.
If a message delete fails (missing Telegram permissions), the bot refuses to save the token and asks you to delete it manually — no token ever stays visible in chat.
| Variable | Description |
|---|---|
TELEGRAM_BOT_TOKEN |
Bot token from @BotFather |
TELEGRAM_DEFAULT_CHAT_ID |
Your Telegram chat ID (security: only this chat can use the bot; set to 0 during first setup, then lock it down) |
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY |
Claude API access via API key |
CLAUDE_CODE_OAUTH_TOKEN |
Claude via subscription OAuth (Pro/Max/Teams) |
ELEVENLABS_API_KEY |
ElevenLabs voice provider |
OPENAI_API_KEY |
OpenAI voice provider |
| Variable | Default | Description |
|---|---|---|
TELEGRAM_ALLOWED_USER_IDS |
(empty) | Comma-separated user IDs; empty = all users in chat allowed |
TELEGRAM_ADMIN_USER_IDS |
(empty) | Comma-separated admin IDs; empty = all authorized users are admins |
TELEGRAM_TOPIC_ID |
(empty) | Restrict bot to a specific Telegram forum topic |
PERSONA_NAME |
Assistant |
Display name in logs / greetings |
SYSTEM_PROMPT_FILE |
(default minimal) | Path to a persona prompt file (e.g. prompts/toris.md) |
TTS_PROVIDER |
auto-detect | elevenlabs or openai |
STT_PROVIDER |
auto-detect | elevenlabs or openai |
STT_LANGUAGE |
auto | e.g. en, pl |
ELEVENLABS_VOICE_ID |
JBFqnCBsd6RMkjVDRZzb (George) |
See ElevenLabs voice library |
OPENAI_VOICE_ID |
coral |
OpenAI voices: alloy, ash, ballad, cedar, coral, echo, fable, juniper, marin, onyx, nova, sage, shimmer, verse |
OPENAI_TTS_MODEL |
gpt-4o-mini-tts |
Or tts-1, tts-1-hd |
OPENAI_STT_MODEL |
whisper-1 |
Or gpt-4o-mini-transcribe, gpt-4o-transcribe |
MAX_VOICE_RESPONSE_CHARS |
500 |
Truncate TTS input (controls cost) |
CLAUDE_TIMEOUT |
300 |
Max seconds to wait for a Claude response |
CLAUDE_WORKING_DIR |
$HOME |
Directory Claude can read from |
CLAUDE_SANDBOX_DIR |
$HOME/claude-sandbox |
Directory Claude can write to and execute in |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR, CRITICAL |
Use /settings in Telegram to configure per-user preferences:
- Mode — Go All (auto-approve tools) or Approve (confirm each tool call)
- Watch — Off / Live (tool calls as they happen) / Debug (full SDK events)
- Audio — voice responses on/off
- Speed — voice playback speed (0.8x – 1.3x)
- Automation cards — compact or full display style
Run multiple AI personalities from the same codebase. Each gets its own:
- Telegram bot
- Voice and personality
- Sandbox directory
- Topic filter (for group chats)
Duplicate the service in docker-compose.yml with different env files:
services:
toris:
env_file: docker/toris.env
volumes:
- toris-state:/home/claude/state
- toris-sandbox:/home/claude/sandbox
assistant2:
env_file: docker/assistant2.env
volumes:
- assistant2-state:/home/claude/state
- assistant2-sandbox:/home/claude/sandboxSee prompts/toris.md for the default persona. Key elements:
# TORIS — Your Second Brain
You are TORIS, a voice-powered thinking partner built on Claude.
## Your Capabilities
- READ files from {read_dir}
- WRITE and EXECUTE in {sandbox_dir}
- Web search, research, note-taking
## CRITICAL — Voice Output Rules
- NO markdown formatting
- Speak in natural flowing sentences# Build the image
docker-compose build
# Start
docker-compose up -d
# View logs
docker-compose logs -f toris
# Restart
docker-compose restart toris
# Stop
docker-compose down
# Stop and remove volumes (WARNING: deletes session history)
docker-compose down -vcp docker/toris.env.example docker/toris.env
# Edit docker/toris.env — you only need TELEGRAM_BOT_TOKEN to start.See the Configuration section above for the full env var reference.
If you want to use an existing claude /login session from your host machine:
# In docker-compose.yml, uncomment:
- ~/.claude/.credentials.json:/home/claude/.claude/.credentials.json:ro| Volume | Contents | Location |
|---|---|---|
toris-state |
Session history & user settings | /home/claude/state |
toris-sandbox |
File operations sandbox | /home/claude/sandbox |
toris-claude-config |
Claude credentials & settings | /home/claude/.claude |
Backup state:
docker cp claude-voice-toris:/home/claude/state ./backup-statedocker-compose ps
docker-compose logs -f torisFor non-Docker production deployments on Linux.
# Create deployment directory
sudo mkdir -p /opt/toris-agent
cd /opt/toris-agent
# Clone and install
sudo git clone --recurse-submodules https://github.com/toruai/toris-agent.git .
sudo python3 -m venv venv
sudo venv/bin/pip install -r requirements.txt
# Install Claude Code globally
sudo npm install -g @anthropic-ai/claude-code
# Create config
sudo mkdir -p /etc/toris-agent
sudo cp .env.example /etc/toris-agent/toris-agent.env
sudo $EDITOR /etc/toris-agent/toris-agent.envCreate /etc/systemd/system/toris-agent.service:
[Unit]
Description=Toris Agent — Claude voice bot for Telegram
After=network.target
[Service]
Type=simple
User=claude
Group=claude
WorkingDirectory=/opt/toris-agent
EnvironmentFile=/etc/toris-agent/toris-agent.env
ExecStart=/opt/toris-agent/venv/bin/python bot.py
Restart=always
RestartSec=10
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/toris-agent/sandbox /var/lib/toris-agent/state
[Install]
WantedBy=multi-user.targetsudo useradd -r -s /bin/false claude
sudo mkdir -p /var/lib/toris-agent/{state,sandbox}
sudo chown -R claude:claude /var/lib/toris-agent
echo "CLAUDE_SANDBOX_DIR=/var/lib/toris-agent/sandbox" | sudo tee -a /etc/toris-agent/toris-agent.env
echo "STATE_DIR=/var/lib/toris-agent/state" | sudo tee -a /etc/toris-agent/toris-agent.envsudo systemctl daemon-reload
sudo systemctl enable toris-agent
sudo systemctl start toris-agent
sudo systemctl status toris-agent
sudo journalctl -u toris-agent -f| Command | Description |
|---|---|
/start |
Welcome / help |
/setup |
Conversational credential setup |
/health |
System & provider health check |
/new [name] |
Start a new session |
/continue |
Resume the last session |
/sessions |
List recent sessions |
/switch <id> |
Switch to a session by ID |
/search <term> |
Search sessions by keyword |
/cancel |
Cancel the current request |
/compact |
Summarize & compress the current session |
/status |
Current session info |
/settings |
Voice, mode, speed, watch mode |
/automations |
List & toggle scheduled automations |
- Chat ID restriction — only the configured chat ID can interact with the bot
- Per-user allowlist —
TELEGRAM_ALLOWED_USER_IDSrestricts which users are authorized inside that chat - Admin gating —
TELEGRAM_ADMIN_USER_IDSrestricts/setupand credential commands - Anonymous denied — when an allowlist is configured, anonymous / channel posts are rejected
- Sandbox isolation — Claude can only write / execute in the sandbox directory
- Rate limiting — 2s cooldown + 10/min per user
- Token hygiene — onboarding tokens are deleted from chat before saving; if delete fails, the bot refuses to save
- No secrets in prompts — keep API keys in env /
/setup, never in persona prompt files
bot.py # Handler registration + startup
handlers/
session.py # /start /new /continue /sessions /switch /status /cancel /compact /search
admin.py # /setup /claude_token /elevenlabs_key /openai_key + settings callbacks
messages.py # voice / text / photo / onboarding / automations callbacks
auth.py # Authorization, rate limiting, topic filtering
state_manager.py # Thread-safe sessions + settings with atomic persistence
claude_service.py # Claude Agent SDK wrapper + working indicator
voice_service.py # TTS/STT with provider failover + health checks
automations.py # RemoteTrigger integration
shared_state.py # Cross-module pending approvals + cancel events
config.py # Env var single source of truth
prompts/ # Persona prompt files
tests/ # 170+ unit tests (uses asyncio.run, not pytest-asyncio)
source venv/bin/activate
pip install -r requirements.txt
# Run tests
pytest tests/ -v
# With coverage
pytest tests/ --cov=. --cov-report=term-missingSee CONTRIBUTING.md for contribution guidelines and the test convention.
Voice message arrives. STT transcribes it (ElevenLabs Scribe or OpenAI Whisper). The transcript goes to the Claude Agent SDK, which decides what tools to call and runs them. With watch mode on, tool calls stream into the chat live. With approve mode on, Claude waits for a ✓ before anything with side effects runs. The response comes back as text; TTS speaks it.
Usually a few seconds end to end. The SDK runs a full agent loop, not a chat-completion wrapper.