Takopi Development
Guide for making changes to the takopi and takopi-plugins forks.
Project Structure
- Core fork:
/home/ubuntu/takopi/— takopi fork with custom features (see “Core fork changes” below) - Plugins:
/home/ubuntu/takopi-plugins/takopi_plugins/— commands/publish,/restart,/health - Installed copy (read-only):
/home/ubuntu/.local/share/uv/tools/takopi/lib/python3.14/site-packages/takopi_plugins/
Making Changes
- Edit files in the forks (
~/takopi/or~/takopi-plugins/takopi_plugins/) - Run
~/reinstall-takopi.sh— auto-bumps version and reinstalls - Restart the bot:
~/stop-takopi.sh && ~/start-takopi.sh
Important: uv caches packages by version — without a version bump, changes won’t apply. The reinstall-takopi.sh script handles this automatically.
Management Scripts
~/start-takopi.sh — starts takopi in an infinite restart loop (wrapper)
- Double-launch protection via flock
- Saves
~/.takopi/wrapper.pid(wrapper PID) and~/.takopi/takopi.pid(takopi PID) - Trap on SIGTERM/SIGINT — gracefully stops takopi when wrapper exits
- All output to
~/takopi.log
~/stop-takopi.sh — stops wrapper and takopi
- Sends SIGTERM to wrapper (via
wrapper.pid), which stops takopi via trap - Fallback: if wrapper is already dead — kills takopi directly (via
takopi.pid) - Waits for processes to exit (up to 10 seconds), then SIGKILL
~/reinstall-takopi.sh — reinstall after code changes
- Auto-bumps patch version in
takopi-plugins/pyproject.toml - Runs
uv tool install --force --reinstall ~/takopi --with ~/takopi-plugins --reinstallensures core fork is also rebuilt (without it, uv caches by version)- Verifies via
takopi doctor - Does NOT restart the bot automatically
Hot Restart from Telegram
/restart in Telegram kills only the takopi process (not the wrapper). The wrapper automatically restarts takopi after 5 seconds.
Use /restart when you need a quick restart without stopping the wrapper.
Diagnostics
takopi doctor— config and dependency checktail -f ~/takopi.log— live logcat ~/.takopi/wrapper.pid— wrapper PIDcat ~/.takopi/takopi.pid— current takopi PIDtakopi plugins list— installed commands
Common Errors
uv cache: if the bot behaves the same after changes — version wasn’t bumped. Use ~/reinstall-takopi.sh instead of manual installation.
Nested Claude session: if a Claude agent runs inside takopi (like in /publish), it inherits env vars from the current session. This can cause conflicts — check ANTHROPIC_API_KEY and other variables.
Config validation: after core fork changes, check ~/.takopi/takopi.toml — new fields may require config updates.
NEVER install bare takopi from PyPI — always use local fork: uv tool install --force ~/takopi --with ~/takopi-plugins
Core Fork Changes
Full list of changes in ~/takopi/ compared to upstream. All changes must be maintained when updating upstream.
1. startup_chat_id — startup message to DM
Files: settings.py, bridge.py, backend.py, loop.py
New field startup_chat_id in [transports.telegram]. When set, the “Kent is online” startup message is sent to the specified chat (DM with Ilya) instead of the group.
Config: startup_chat_id = 29462028
2. user_names — user names in prompts
Files: settings.py, bridge.py, backend.py, loop.py
New field user_names in [transports.telegram] — mapping user_id → display_name. Each message gets a prefix like [Ilya]: or [Katya]: so Claude knows who is writing.
Config:
[transports.telegram.user_names]
29462028 = "Ilya"
2101298884 = "Katya"
Implementation: substitution in loop.py → _dispatch_pending_prompt() and handle_prompt_upload(). Validator _coerce_user_names_keys in settings.py converts TOML string keys to int.
3. send_photos — photo sending via Telegram
Files: commands.py, client_api.py, bridge.py, executor.py
Added methods for sending one or multiple photos:
BotClient.send_photo()— single photoBotClient.send_media_group()— media group (up to 10)BotClient.send_photos()— wrapper: 1 photo →send_photo, multiple →send_media_groupCommandExecutor.send_photos()— protocol for pluginsTelegramTransport.send_photos()— transport layer
Used by plugins for sending images (e.g. in the nail-ideas command).
4. append_system_prompt — per-chat system prompt
Files: settings.py, config.py, runners/run_options.py, runners/claude.py, commands/executor.py, transport_runtime.py
New field append_system_prompt in [projects.*]. Text is appended to the base CLAUDE.md via --append-system-prompt Claude CLI flag.
Implementation: ContextVar in run_options.py, set in executor._run_engine(), read in ClaudeRunner._build_args().
Current config:
default-workspace(group) — “General assistant for Ilya and Katya…”ilya-dev(DM,chat_id = 29462028) — “Technical chat with Ilya…“
5. CLAUDECODE env cleanup — nested session fix
Files: runners/claude.py
ClaudeRunner.env() removes CLAUDECODE and CLAUDE_CODE_ENTRYPOINT variables from the subprocess environment. Without this, Claude CLI throws a “nested session” error when takopi is launched from Claude Code.
Also fixed logic: env dict is now always created (not only when use_api_billing is not True), ensuring a clean environment.
6. thread_id in append_system_prompt
Files: commands/executor.py
When append_prompt and thread_id are both present, the current Telegram thread_id is appended to the system prompt. This allows Claude to know which thread it’s running in.
Plugins
Plugins in ~/takopi-plugins/takopi_plugins/:
backend.py — /publish
- Claude agent launched via
create_subprocess_exec+ stdin pipe (instead of brokencreate_subprocess_shellwith f-string interpolation) - Agent gets
--dangerously-skip-permissions,--allowedToolsflags - Agent output shown in progress messages and final result
restart.py — /restart
No changes relative to upstream.
health.py — /health
Shows bot status: uptime, engines, projects, MCP servers (real ping via JSON-RPC initialize), skills.
See also: Takopi Commands