Files

3.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# Run all checks (format, lint, type, tests)
just check

# Individual checks
uv run ruff format --check src tests
uv run ruff check src tests
uv run ty check src tests
uv run pytest

# Single test
uv run pytest tests/test_runner_contract.py

# Run directly without install
uv run takopi --help

# Mutation testing
uv run mutmut run

# Docs
just docs-serve

Architecture

Takopi is a Telegram bridge for agent CLIs (Claude Code, Codex, OpenCode, Pi). It polls Telegram, routes messages to agent runners, streams progress back, and embeds resume tokens so sessions can continue.

Layer stack (top → bottom)

Layer Key modules
CLI cli.py — entry point, config, lock
Plugins plugins.py, engines.py, transports.py, commands.py, api.py
Orchestration router.py (auto-route by resume token), scheduler.py (per-thread FIFO queue), transport_runtime.py
Bridge telegram/bridge.py (poll loop, parse directives), runner_bridge.py (progress + final render)
Runner runner.py (protocol + JsonlSubprocessRunner), runners/*.py, schemas/*.py
Transport transport.py, presenter.py, telegram/client.py, telegram/render.py
Domain model.py, events.py, progress.py

Plugin system

Engines, transports, and commands are discovered via Python entrypoints:

  • takopi.engine_backends — runner backends (id must match entrypoint name)
  • takopi.transport_backends — transport backends
  • takopi.command_backends — in-chat command handlers

Public API surface for plugin authors: takopi.api. Internal modules should not be imported directly from plugins.

Runner contract

Every runner must yield events satisfying (enforced by tests/test_runner_contract.py):

  1. Exactly one StartedEvent (first)
  2. Exactly one CompletedEvent (last)
  3. CompletedEvent.resume == StartedEvent.resume

Runners extend JsonlSubprocessRunner + ResumeTokenMixin and implement:

  • build_args(...) / stdin_payload(...) — build subprocess command
  • decode_jsonl(...) — parse one JSONL line via msgspec
  • translate(...) — pure function converting engine events to TakopiEvents
  • format_resume() / extract_resume() / is_resume_line() — resume codec

Resume flow

Resume tokens are embedded as inline code in the final Telegram message (e.g., `claude --resume abc123`). When a user replies to that message, the bridge extracts the token and passes it to the matching runner. Each runner owns its own resume regex and format.

Thread scheduling

ThreadScheduler maintains per-thread FIFO queues. Same-thread jobs run sequentially; different threads run in parallel. The "thread" key is the Telegram message thread / reply chain.

Adding a runner

See docs/how-to/add-a-runner.md. Short version: add src/takopi/runners/<engine>.py + src/takopi/schemas/<engine>.py, expose BACKEND = EngineBackend(...), add entrypoint in pyproject.toml. No changes to bridge or CLI needed.

Key invariants

  • StartedEvent must be emitted as soon as the session ID is known (enables early lock acquisition).
  • Runners must not invent new event types — translate everything into StartedEvent, ActionEvent, CompletedEvent.
  • Schema decoding uses msgspec (not pydantic); decoders live in schemas/.
  • Config lives in ~/.takopi/takopi.toml; loaded via pydantic-settings.
  • Coverage threshold: 81% (pyproject.toml--cov-fail-under=81).