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

  1. Edit files in the forks (~/takopi/ or ~/takopi-plugins/takopi_plugins/)
  2. Run ~/reinstall-takopi.sh — auto-bumps version and reinstalls
  3. 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
  • --reinstall ensures 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 check
  • tail -f ~/takopi.log — live log
  • cat ~/.takopi/wrapper.pid — wrapper PID
  • cat ~/.takopi/takopi.pid — current takopi PID
  • takopi 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 photo
  • BotClient.send_media_group() — media group (up to 10)
  • BotClient.send_photos() — wrapper: 1 photo → send_photo, multiple → send_media_group
  • CommandExecutor.send_photos() — protocol for plugins
  • TelegramTransport.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 broken create_subprocess_shell with f-string interpolation)
  • Agent gets --dangerously-skip-permissions, --allowedTools flags
  • 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