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 backendstakopi.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):
- Exactly one
StartedEvent(first) - Exactly one
CompletedEvent(last) CompletedEvent.resume == StartedEvent.resume
Runners extend JsonlSubprocessRunner + ResumeTokenMixin and implement:
build_args(...)/stdin_payload(...)— build subprocess commanddecode_jsonl(...)— parse one JSONL line via msgspectranslate(...)— pure function converting engine events toTakopiEventsformat_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
StartedEventmust 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).