docs: restructure docs into diataxis (#121)

This commit is contained in:
banteg
2026-01-13 15:59:27 +04:00
committed by GitHub
parent d0e9a51a0f
commit e292c99ab0
52 changed files with 1538 additions and 1255 deletions
+17 -363
View File
@@ -1,372 +1,26 @@
# takopi - Developer Guide # Developer guide (moved)
This document describes the internal architecture and module responsibilities. This document was split into smaller Diátaxis pages.
See `specification.md` for the authoritative behavior spec.
## Development Setup ## Setup
```bash - [Dev setup](how-to/dev-setup.md)
# Clone and enter the directory
git clone https://github.com/banteg/takopi
cd takopi
# Run directly with uv (installs deps automatically) ## Understanding the codebase
uv run takopi --help
# Or install locally from the repo to test outside the repo - [Architecture](explanation/architecture.md)
uv tool install . - [Routing & sessions](explanation/routing-and-sessions.md)
takopi --help - [Module map](explanation/module-map.md)
# Run tests, linting, type checking ## Reference
uv run pytest
uv run ruff check src tests
uv run ty check .
# Or all at once - [Specification](reference/specification.md)
just check - [Plugin API](reference/plugin-api.md)
``` - [Environment variables](reference/env-vars.md)
- [Telegram transport](reference/transports/telegram.md)
- [Runners](reference/runners/index.md)
Takopi runs in **auto-router** mode by default. `default_engine` in `takopi.toml` selects ## Extending Takopi
the engine for new threads; engine subcommands override that default for the process.
## Module Responsibilities - [Write a plugin](how-to/write-a-plugin.md)
- [Add a runner](how-to/add-a-runner.md)
### `runner_bridge.py` - Transport-agnostic orchestration
The core handler module containing:
| Component | Purpose |
|-----------|---------|
| `ExecBridgeConfig` | Frozen dataclass holding transport + presenter config |
| `IncomingMessage` | Normalized incoming message shape |
| `handle_message()` | Per-message handler with progress updates and final render |
| `ProgressEdits` | Throttled progress edit worker |
| `RunningTask` | Cancellation + resume coordination for in-flight runs |
**Key patterns:**
- Progress edits are best-effort and only run when new events arrive (Telegram outbox handles rate limiting/coalescing)
- Resume tokens are runner-formatted command lines (e.g., `` `codex resume <token>` ``, `` `claude --resume <token>` ``, `` `pi --session <id>` ``)
- Resume lines are stripped from the prompt before invoking the runner
- Errors/cancellation render final status while preserving resume tokens when known
### `telegram/bridge.py` - Telegram bridge loop
The Telegram adapter module containing:
| Component | Purpose |
|-----------|---------|
| `TelegramBridgeConfig` | Frozen dataclass holding bot + router + exec config |
| `TelegramTransport` | `BotClient``Transport` adapter |
| `TelegramPresenter` | `ProgressState``RenderedMessage` adapter |
| `poll_updates()` | Async generator that drains backlog, long-polls updates, filters messages |
| `run_main_loop()` | TaskGroup-based main loop that spawns per-message handlers |
| `_handle_cancel()` | `/cancel` routing |
**Key patterns:**
- Bridge schedules runs FIFO per thread to avoid concurrent progress messages; runner locks enforce per-thread serialization
- `/cancel` routes by reply-to progress message id (accepts extra text)
- `/{engine}` on the first line selects the engine for new threads
- Resume parsing polls all runners via `AutoRouter.resolve_resume()` and routes to the first match
- Bot command menu is synced on startup (`cancel` + engine + project commands, capped at 100)
### `transport.py` - Transport protocol
Defines `Transport`, `MessageRef`, `RenderedMessage`, and `SendOptions`.
### `presenter.py` - Presenter protocol
Defines a renderer that converts `ProgressState` into `RenderedMessage` outputs.
### `transport_runtime.py` - Transport runtime facade
Provides the `TransportRuntime` helper used by transport backends to resolve
messages, select runners, and format context without depending on internal types.
### `transports.py` - Transport backend loading
Defines the transport backend protocol and entrypoint-backed loading helpers.
### `config_migrations.py` - Config migrations
Applies one-time edits to on-disk config (e.g., legacy Telegram key migration) before
`TakopiSettings` validation runs.
### `telegram/backend.py` - Telegram transport backend
Adapter that validates Telegram config, runs onboarding, and builds/runs the Telegram bridge.
### `cli.py` - CLI entry point
| Component | Purpose |
|-----------|---------|
| `run()` / `main()` | Typer CLI entry points |
| `_run_auto_router()` | Loads settings, resolves transport + engine, builds router, delegates to transport backend |
### `progress.py` - Progress tracking
| Function/Class | Purpose |
|----------------|---------|
| `ProgressTracker` | Stateful reducer of takopi events into progress snapshots |
| `ProgressState` | Snapshot of actions, resume token, and engine metadata |
### `markdown.py` - Markdown formatting
| Function/Class | Purpose |
|----------------|---------|
| `MarkdownFormatter` | Converts `ProgressState` into MarkdownParts |
| `MarkdownPresenter` | `ProgressState``RenderedMessage` (markdown text) |
| `MarkdownParts` | Header/body/footer building blocks for markdown output |
| `assemble_markdown_parts()` | Join MarkdownParts into a single markdown string |
| `render_event_cli()` | Format a takopi event for CLI logs |
| `format_elapsed()` | Formats seconds as `Xh Ym`, `Xm Ys`, or `Xs` |
### `telegram/render.py` - Telegram markdown rendering
| Function/Class | Purpose |
|----------------|---------|
| `render_markdown()` | Markdown → Telegram text + entities |
| `trim_body()` | Trim body to 3500 chars (header/footer preserved) |
| `prepare_telegram()` | Trim + render Markdown parts for Telegram |
### `telegram/client.py` - Telegram API wrapper
| Component | Purpose |
|-----------|---------|
| `BotClient` | Protocol defining the bot client interface |
| `TelegramClient` | HTTP client for Telegram Bot API (send, edit, delete messages) |
See `docs/transports/telegram.md` for outbox behavior, rate limiting, and retry rules.
### `runners/codex.py` - Codex runner
| Component | Purpose |
|-----------|---------|
| `CodexRunner` | Spawns `codex exec --json`, streams JSONL, emits takopi events |
| `translate_codex_event()` | Normalizes Codex JSONL into the takopi event schema |
| `manage_subprocess()` | Starts a new process group and kills it on cancellation (POSIX) |
**Key patterns:**
- Per-resume locks (WeakValueDictionary) prevent concurrent resumes of the same session
- Event delivery uses a single internal queue to preserve order without per-event tasks
- Stderr is drained into a bounded tail (debug logging only)
- Translation errors abort the run; keep event normalization defensive
### `runners/pi.py` - Pi runner
| Component | Purpose |
|-----------|---------|
| `PiRunner` | Spawns `pi --print --mode json`, streams JSONL, emits takopi events |
| `translate_pi_event()` | Normalizes Pi JSONL into the takopi event schema |
### `model.py` / `runner.py` - Core domain types
| File | Purpose |
|------|---------|
| `model.py` | Domain types: resume tokens, actions, events, run result |
| `runner.py` | Runner protocol + event queue utilities |
### `backends.py` - Engine backend contracts
Defines `EngineBackend`, `SetupIssue`, and the `EngineConfig` type used by
runner modules.
### `plugins.py` - Entrypoint discovery
Centralizes plugin discovery and lazy loading:
- lists IDs without importing plugin modules
- loads a specific entrypoint on demand
- captures load errors for diagnostics
- filters by enabled list (distribution names)
### `commands.py` - Command backend loading
Defines the command backend protocol, command context/executor helpers, and
entrypoint-backed loading for slash-command plugins.
### `ids.py` - Plugin ID validation
Defines the shared ID regex used for plugin IDs and Telegram command names.
### `api.py` - Public plugin API
Re-exports the supported plugin surface from `takopi.api` (stable API boundary).
### `engines.py` - Engine backend discovery
Loads engine backends via entrypoints (`takopi.engine_backends`), with lazy loading
and enabled list support.
### `runners/` - Runner implementations
| File | Purpose |
|------|---------|
| `codex.py` | Codex runner (JSONL → takopi events) + per-resume locks |
| `claude.py` | Claude runner (JSONL → takopi events) + per-resume locks |
| `opencode.py` | OpenCode runner (JSONL → takopi events) + per-resume locks |
| `pi.py` | Pi runner (JSONL → takopi events) + per-resume locks |
| `mock.py` | Mock runner for tests/demos |
### `schemas/` - JSONL decoding schemas
Self-documenting msgspec schemas for decoding engine JSONL streams.
| File | Purpose |
|------|---------|
| `codex.py` | `codex exec --json` event schemas |
| `claude.py` | `claude -p --output-format stream-json --verbose` event schemas |
| `opencode.py` | `opencode run --format json` event schemas |
| `pi.py` | `pi --print --mode json` event schemas |
### `utils/` - Utility modules
| File | Purpose |
|------|---------|
| `paths.py` | `relativize_path()`, `relativize_command()` helpers |
| `streams.py` | `iter_bytes_lines()`, `drain_stderr()` for async stream handling |
| `subprocess.py` | `manage_subprocess()`, `terminate_process()`, `kill_process()` |
### `router.py` - Auto-router
| Component | Purpose |
|-----------|---------|
| `AutoRouter` | Resolves resume tokens by polling all runners, routes to matching engine |
| `RunnerEntry` | Dataclass holding runner + backend metadata |
| `RunnerUnavailableError` | Raised when requested engine is not available |
### `scheduler.py` - Thread scheduling
| Component | Purpose |
|-----------|---------|
| `ThreadScheduler` | Per-thread FIFO job queuing with serialization |
| `ThreadJob` | Dataclass representing a queued job |
| `note_thread_known()` | Registers a thread as busy when token discovered mid-run |
### `events.py` - Event factory
| Component | Purpose |
|-----------|---------|
| `EventFactory` | Helper class for creating takopi events with consistent engine/resume |
| Builder methods | `started()`, `action()`, `action_started()`, `action_updated()`, `action_completed()`, `completed()`, `completed_ok()`, `completed_error()` |
### `lockfile.py` - Single-instance enforcement
| Component | Purpose |
|-----------|---------|
| `acquire_lock()` | Acquire lock for bot token, returns `LockHandle` context manager |
| `LockHandle` | Context manager for automatic lock release |
| `LockInfo` | Dataclass with `pid` and `token_fingerprint` |
| `token_fingerprint()` | SHA256 hash of bot token, truncated to 10 chars |
### `backends_helpers.py` - Backend utilities
| Function | Purpose |
|----------|---------|
| `install_issue()` | Creates `SetupIssue` with install instructions for missing CLI |
### `config.py` - Shared configuration errors
```python
class ConfigError(RuntimeError): ...
```
### `settings.py` - Settings loading
```python
def load_settings(path: str | Path | None = None) -> tuple[TakopiSettings, Path]:
# Loads ~/.takopi/takopi.toml (TOML + env), validates via pydantic-settings
```
### `config_store.py` - Raw TOML read/write
```python
def read_raw_toml(path: Path) -> dict:
# Loads TOML for merge/update without clobbering extra sections
```
### `logging.py` - Secure logging setup
```python
def setup_logging(*, debug: bool = False) -> None:
# Configures structlog pipeline, redaction, and output formatting.
```
Environment flags:
- `TAKOPI_LOG_LEVEL` (default `info`, `debug` forces `debug`)
- `TAKOPI_LOG_FORMAT` (`console` or `json`)
- `TAKOPI_LOG_COLOR` (`1/true/yes/on` to force color, `0/false/no/off` to disable)
- `TAKOPI_LOG_FILE` (append JSON lines to a file)
- `TAKOPI_TRACE_PIPELINE` (log pipeline events at info instead of debug)
- `TAKOPI_NO_INTERACTIVE` (disable interactive prompts for CI/non-TTY environments)
- `PI_CODING_AGENT_DIR` (override Pi agent session directory base path)
CLI flag: `--debug` enables debug logging (overrides `TAKOPI_LOG_LEVEL`).
CLI flag: `--transport <id>` overrides the configured transport backend.
### `telegram/onboarding.py` - Setup validation
```python
def check_setup(backend: EngineBackend) -> SetupResult:
# Validates engine CLI on PATH and config file
def render_setup_guide(result: SetupResult):
# Displays rich panel with setup instructions
```
## Adding a Runner
See `docs/adding-a-runner.md` for the full guide and a worked example.
## Data Flow
### New Message Flow
```
Telegram Update
telegram/bridge.poll_updates() drains backlog, long-polls, filters allowed chat ids
telegram/bridge.run_main_loop() spawns tasks in TaskGroup
router.resolve_resume(text, reply_text) → ResumeToken | None
router.entry_for(resume_token) or router.entry_for_engine(override/default) → RunnerEntry
runner_bridge.handle_message() spawned as task with selected runner
Send initial progress message (silent)
runner.run(prompt, resume_token)
├── Spawns engine subprocess (e.g., codex exec --json, pi --print --mode json)
├── Streams JSONL from stdout
├── Normalizes JSONL -> takopi events
├── Yields Takopi events (async iterator)
│ ↓
│ ProgressTracker.note_event()
│ ↓
│ ProgressEdits best-effort transport.edit(wait=False)
└── Ends with completed(resume, ok, answer)
render_final() with resume line (runner-formatted)
transport.send()/edit() final message, delete progress if needed
```
### Resume Flow
Same as above; auto-router polls all runners to extract resume tokens:
- Router returns first matching token (e.g. `` `claude --resume <id>` `` routes to Claude, `` `pi --session <id>` `` routes to Pi)
- Selected runner spawns with resume (e.g. `codex exec --json resume <token> -`, `pi --print --mode json --session <path> <prompt>`)
- Per-token lock serializes concurrent resumes on the same thread
## Error Handling
| Scenario | Behavior |
|----------|----------|
| `codex exec` fails (rc != 0) | Emits a warning `action` plus `completed(ok=false, error=...)` |
| `pi` fails (rc != 0) | Emits a warning `action` plus `completed(ok=false, error=...)` |
| Telegram API error | Logged, edit skipped (progress continues) |
| Cancellation | Cancel scope terminates the process group (POSIX) and renders `cancelled` |
| Errors in handler | Final render uses `status=error` and preserves resume tokens when known |
| No agent_message (empty answer) | Final shows `error` status |
+43
View File
@@ -0,0 +1,43 @@
# Explanation
Explanation docs answer **“how does this work?”** and **“why is it designed this way?”**
If you want step-by-step instructions, go to **[Tutorials](../tutorials/index.md)**.
If you want exact options and contracts, go to **[Reference](../reference/index.md)**.
## How Takopi works end-to-end
- Incoming Telegram message → resolve context (project/branch) → resolve resume token → select runner → stream events → render progress → send final + resume line.
Start here:
- [Architecture](architecture.md)
## Routing, sessions, and continuation
Takopi is stateless by default, but can provide “continuation” in multiple ways:
- reply-to-continue (always available)
- per-topic resume (Telegram forum topics)
- per-chat sessions (auto-resume)
- [Routing & sessions](routing-and-sessions.md)
## Plugins and extensibility
Takopi uses entrypoint-based plugins with lazy discovery so broken plugins dont brick the CLI.
- [Plugin system](plugin-system.md)
## Codebase orientation
If youre making changes, this is the “map of the territory”:
- [Module map](module-map.md)
## Where to look for hard rules
Explanation pages describe intent and tradeoffs. The *hard requirements* live in:
- [Reference: Specification](../reference/specification.md)
- [Reference: Plugin API](../reference/plugin-api.md)
+81
View File
@@ -0,0 +1,81 @@
# Module map
This page is a high-level map of Takopis internal modules: what they do and how they fit together.
## Entry points
| Module | Responsibility |
|--------|----------------|
| `cli.py` | Typer CLI entry point; loads settings, selects engine/transport, runs the transport backend. |
| `telegram/backend.py` | Telegram transport backend: validates config, runs onboarding, builds and runs the Telegram bridge. |
## Orchestration and routing
| Module | Responsibility |
|--------|----------------|
| `runner_bridge.py` | Transport-agnostic orchestration: per-message handler, progress updates, final render, cancellation, resume coordination. |
| `router.py` | Auto-router: resolves resume tokens by polling runners; selects a runner for a message. |
| `scheduler.py` | Per-thread FIFO job queueing with serialization. |
| `transport_runtime.py` | Facade used by transports and commands to resolve messages and runners without importing internal router/project types. |
## Domain model and events
| Module | Responsibility |
|--------|----------------|
| `model.py` | Domain types: resume tokens, events, actions, run results. |
| `runner.py` | Runner protocol and event queue utilities. |
| `events.py` | Event factory helpers for building Takopi events consistently. |
## Rendering and progress
| Module | Responsibility |
|--------|----------------|
| `progress.py` | Progress tracking: reduces takopi events into progress snapshots. |
| `markdown.py` | Markdown formatting for progress/final messages; includes helpers like elapsed formatting. |
| `presenter.py` | Presenter protocol: converts `ProgressState` into transport-specific messages. |
| `transport.py` | Transport protocol: send/edit/delete abstractions and message reference types. |
## Telegram implementation
| Module | Responsibility |
|--------|----------------|
| `telegram/bridge.py` | Telegram bridge loop: polls updates, filters messages, dispatches handlers, coordinates cancellation. |
| `telegram/client.py` | Telegram API wrapper with retry/outbox semantics. |
| `telegram/render.py` | Telegram markdown rendering and trimming. |
| `telegram/onboarding.py` | Interactive setup and setup validation UX. |
| `telegram/commands/*` | In-chat command handlers (`/agent`, `/file`, `/topic`, `/ctx`, `/new`, …). |
## Plugins
| Module | Responsibility |
|--------|----------------|
| `plugins.py` | Entrypoint discovery and lazy loading (capture load errors, filter by enabled list). |
| `engines.py` | Engine backend discovery and loading via entrypoints. |
| `transports.py` | Transport backend discovery and loading via entrypoints. |
| `commands.py` | Command backend discovery and loading via entrypoints; command execution helpers. |
| `ids.py` | Shared ID regex and collision checks for plugin ids and Telegram command names. |
| `api.py` | Public plugin API boundary (`takopi.api` re-exports). |
## Runners and schemas
| Module | Responsibility |
|--------|----------------|
| `runners/*` | Engine runner implementations (Codex, Claude, OpenCode, Pi). |
| `schemas/*` | msgspec schemas / decoders for engine JSONL streams. |
## Configuration and persistence
| Module | Responsibility |
|--------|----------------|
| `settings.py` | Loads `takopi.toml` (TOML + env), validates with pydantic-settings. |
| `config_store.py` | Raw TOML read/write (merge/update without clobbering extra sections). |
| `config_migrations.py` | One-time edits to on-disk config (e.g. legacy Telegram key migration). |
## Utilities
| Module | Responsibility |
|--------|----------------|
| `utils/paths.py` | Path/command relativization helpers. |
| `utils/streams.py` | Async stream helpers (`iter_bytes_lines`, stderr draining). |
| `utils/subprocess.py` | Subprocess management helpers (terminate/kill best-effort). |
+88
View File
@@ -0,0 +1,88 @@
# Plugin system
Takopi uses Python entrypoints to extend engines, transports, and commands.
## Why entrypoints
Entrypoints let Takopi discover plugins without hard dependencies on plugin packages.
Installed distributions declare what they provide, and Takopi can list and load them at runtime.
This makes it possible to:
- Add new engines/transports/commands without changing Takopi itself.
- Ship plugins independently.
- Keep the core CLI small.
## Why discovery is lazy
Takopi lists plugin IDs **without importing plugin code**, then imports a plugin only when:
- it is selected by routing (engine/transport), or
- it is invoked as a command, or
- you explicitly request loading via `takopi plugins --load`.
This keeps `takopi --help` fast and prevents a broken third-party plugin from bricking the CLI.
## Entrypoint rules (what Takopi expects)
Takopi uses three entrypoint groups:
```toml
[project.entry-points."takopi.engine_backends"]
myengine = "myengine.backend:BACKEND"
[project.entry-points."takopi.transport_backends"]
mytransport = "mytransport.backend:BACKEND"
[project.entry-points."takopi.command_backends"]
mycommand = "mycommand.backend:BACKEND"
```
Rules:
- The entrypoint **name** is the plugin id.
- The entrypoint value must resolve to a backend object:
- engine backend: `EngineBackend`
- transport backend: `TransportBackend`
- command backend: `CommandBackend`
- The backend object must have `id == entrypoint name`.
## Why there is an enabled list
Plugin visibility can be restricted via:
```toml
[plugins]
enabled = ["takopi-engine-acme", "takopi-transport-slack"]
```
When set, Takopi filters by **distribution name** (package metadata), not by entrypoint name.
This lets you:
- ship multiple entrypoints from one distribution, and
- enable/disable whole plugin packages predictably.
## IDs and collisions
Entrypoint names become plugin IDs and appear in user-facing surfaces (CLI subcommands, Telegram commands, `/engine` directives).
Takopi validates IDs and rejects collisions with reserved names.
Plugin IDs must match:
```
^[a-z0-9_]{1,32}$
```
Reserved IDs include core chat and CLI command names such as `cancel`, `init`, and `plugins`.
## How to debug discovery and loading
```sh
takopi plugins
takopi plugins --load
```
## Related
- [Write a plugin](../how-to/write-a-plugin.md)
- [Plugin API reference](../reference/plugin-api.md)
+44
View File
@@ -0,0 +1,44 @@
# Routing & sessions
Takopi is **stateless by default**: each message starts a new engine session unless a resume token is present.
## Continuation (how threads persist)
Takopi supports three ways to continue a thread:
1. **Reply-to-continue** (always available)
- Reply to any bot message that contains a resume line in the footer.
- Takopi extracts the resume token and resumes that engine thread.
2. **Forum topics** (optional)
- Topics can store resume tokens per topic and auto-resume new messages in that topic.
- Topic state is stored in `telegram_topics_state.json`.
- Reset with `/new`.
3. **Chat sessions** (optional)
- Set `session_mode = "chat"` to store one resume token per chat (per sender in groups).
- State is stored in `telegram_chat_sessions_state.json`.
- Reset with `/new`.
Reply-to-continue works even if topics or chat sessions are enabled.
## Routing (how Takopi picks a runner)
For each message, Takopi:
- parses directive prefixes (`/engine`, `/project`, `@branch`) from the first non-empty line
- attempts to extract a resume token by polling available runners
- if a resume token is found, routes to the matching runner; otherwise uses the configured default engine
## Serialization (why you dont get overlapping runs)
Takopi allows parallel runs across **different threads**, but enforces serialization within a thread:
- Telegram side: jobs are queued FIFO per thread.
- Runner side: runners enforce per-resume-token locks (so the same session cant be resumed concurrently).
The precise invariants are specified in the [Specification](../reference/specification.md).
## Related
- [Commands & directives](../reference/commands-and-directives.md)
- [Context resolution](../reference/context-resolution.md)
+32
View File
@@ -0,0 +1,32 @@
# Dev setup
Set up Takopi for local development and run the checks.
## Clone and run
```bash
git clone https://github.com/banteg/takopi
cd takopi
# Run directly with uv (installs deps automatically)
uv run takopi --help
```
## Install locally (optional)
```bash
uv tool install .
takopi --help
```
## Run checks
```bash
uv run pytest
uv run ruff check src tests
uv run ty check .
# Or all at once
just check
```
+59
View File
@@ -0,0 +1,59 @@
# File transfer
Upload files into the active repo/worktree or fetch files back into Telegram.
## Enable file transfer
```toml
[transports.telegram.files]
enabled = true
auto_put = true
auto_put_mode = "upload" # upload | prompt
uploads_dir = "incoming"
allowed_user_ids = [123456789]
deny_globs = [".git/**", ".env", ".envrc", "**/*.pem", "**/.ssh/**"]
```
Notes:
- File transfer is **disabled by default**.
- If `allowed_user_ids` is empty, private chats are allowed and group usage requires admin privileges.
## Upload a file (`/file put`)
Send a document with a caption:
```
/file put <path>
```
Examples:
```
/file put docs/spec.pdf
/file put /happy-gadgets @feat/camera assets/logo.png
```
If you send a file **without a caption**, Takopi saves it to `incoming/<original_filename>`.
Use `--force` to overwrite:
```
/file put --force docs/spec.pdf
```
## Fetch a file (`/file get`)
Send:
```
/file get <path>
```
Directories are zipped automatically.
## Related
- [Commands & directives](../reference/commands-and-directives.md)
- [Config reference](../reference/config.md)
+37
View File
@@ -0,0 +1,37 @@
# How-to guides
How-to guides are **goal-oriented recipes**. Pick the task youre trying to accomplish and follow the steps.
If youre learning from scratch, start with **[Tutorials](../tutorials/index.md)**.
If you need exact options and defaults, use **[Reference](../reference/index.md)**.
## Daily use
- [Switch engines](switch-engines.md) (`/codex`, `/claude`, `/opencode`, `/pi`)
- [Projects](projects.md) (register repos + run from anywhere)
- [Worktrees](worktrees.md) (run work on `@branch` without switching your main checkout)
- [Route by chat](route-by-chat.md) (dedicated chats per project)
- [Topics](topics.md) (forum threads bound to repo/branch + auto-resume)
- [Chat sessions](topics.md#chat-sessions) (auto-resume without replying)
## Messaging extras
- [Voice notes](voice-notes.md) (transcribe and run)
- [File transfer](file-transfer.md) (`/file put` and `/file get`)
- [Schedule tasks](schedule-tasks.md) (Telegram scheduled messages)
## Extending Takopi
- [Write a plugin](write-a-plugin.md) (engines, transports, commands)
- [Add a runner](add-a-runner.md) (implement a new engine backend)
- [Dev setup](dev-setup.md) (run from source, tests, linting, type checks)
## Debugging and operations
- [Troubleshooting](troubleshooting.md) (`--debug`, common gotchas, “why didnt it route?”)
## Not sure where to go?
- If your question starts with “**How do I…**” → youre in the right place.
- If your question starts with “**What are the exact options / defaults?**” → go to **[Reference](../reference/index.md)**.
- If your question starts with “**Why is it designed this way?**” → go to **[Explanation](../explanation/index.md)**.
+57
View File
@@ -0,0 +1,57 @@
# Projects
Projects let you route messages to repos from anywhere using `/alias`.
## Register a repo as a project
```sh
cd ~/dev/happy-gadgets
takopi init happy-gadgets
```
This adds a project to your config:
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
```
## Target a project from chat
Send:
```
/happy-gadgets pinky-link two threads
```
## Project-specific settings
Projects can override global defaults:
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
default_engine = "claude"
worktrees_dir = ".worktrees"
worktree_base = "master"
```
If you expect to edit config while Takopi is running, enable hot reload:
```toml
watch_config = true
```
## Set a default project
If you mostly work in one repo:
```toml
default_project = "happy-gadgets"
```
## Related
- [Context resolution](../reference/context-resolution.md)
- [Worktrees](worktrees.md)
+39
View File
@@ -0,0 +1,39 @@
# Route by chat
Bind a Telegram chat to a project so messages in that chat automatically route to the right repo.
## Capture a chat id and save it to a project
Run:
```sh
takopi chat-id --project happy-gadgets
```
Then send any message in the target chat. Takopi captures the `chat_id` and updates your config:
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
chat_id = -1001234567890
```
Messages from that chat now default to the project.
## Rules for chat ids
- Each `projects.*.chat_id` must be unique.
- A project `chat_id` must not match `transports.telegram.chat_id`.
- Telegram uses positive IDs for private chats and negative IDs for groups/supergroups.
## Capture a chat id without saving
```sh
takopi chat-id
```
## Related
- [Topics](topics.md)
- [Context resolution](../reference/context-resolution.md)
+7
View File
@@ -0,0 +1,7 @@
# Schedule tasks
Telegrams native message scheduling works with Takopi.
In Telegram, long-press the send button and choose **Schedule Message** to run tasks at a specific time.
You can also set up recurring schedules (daily/weekly) for automated workflows.
+43
View File
@@ -0,0 +1,43 @@
# Switch engines
Run a one-off message on a specific engine, or set a persistent default for a chat/topic.
## Use an engine for one message
Prefix the first non-empty line with an engine directive:
```
/codex hard reset the timeline
/claude shrink and store artifacts forever
/opencode hide their paper until they reply
/pi render a diorama of this timeline
```
Directives are only parsed at the start of the first non-empty line.
## Set a default engine for the current scope
Use `/agent`:
```
/agent
/agent set claude
/agent clear
```
- Inside a forum topic, `/agent set` affects that topic.
- In normal chats, it affects the whole chat.
- In group chats, only admins can change defaults.
Selection precedence (highest to lowest): resume token → `/engine` directive → topic default → chat default → project default → global default.
## Engine installation
Takopi shells out to engine CLIs. Install them and make sure theyre on your `PATH`
(`codex`, `claude`, `opencode`, `pi`). Authentication is handled by each CLI.
## Related
- [Commands & directives](../reference/commands-and-directives.md)
- [Config reference](../reference/config.md)
+53
View File
@@ -0,0 +1,53 @@
# Topics
Topics bind Telegram forum threads to a specific project/branch context. They can also store resume tokens and a default agent per topic.
## Enable topics
```toml
[transports.telegram.topics]
enabled = true
scope = "auto" # auto | main | projects | all
```
Your bot needs **Manage Topics** permission in the group.
If any `projects.<alias>.chat_id` are configured, topics are managed in those project chats; otherwise topics are managed in the main chat.
## Topic commands
Run these inside a topic thread:
| Command | Description |
|---------|-------------|
| `/topic <project> @branch` | Create a new topic bound to context |
| `/ctx` | Show the current binding |
| `/ctx set <project> @branch` | Update the binding |
| `/ctx clear` | Remove the binding |
| `/new` | Clear stored sessions for this topic |
In project chats, omit the project: `/topic @branch` or `/ctx set @branch`.
## Chat sessions
Chat sessions store one resume token per chat (per sender in groups) so new messages can auto-resume without replying.
Enable:
```toml
[transports.telegram]
session_mode = "chat" # stateless | chat
```
Reset the stored session with `/new`.
## State files
- Topic state: `telegram_topics_state.json`
- Chat sessions state: `telegram_chat_sessions_state.json`
- Chat defaults (e.g. `/agent`): `telegram_chat_prefs_state.json`
## Related
- [Switch engines](switch-engines.md)
- [Commands & directives](../reference/commands-and-directives.md)
+10
View File
@@ -0,0 +1,10 @@
# Troubleshooting
If something isnt working, rerun with debug logging enabled:
```sh
takopi --debug
```
Then check `debug.log` for errors and include it when reporting issues.
+27
View File
@@ -0,0 +1,27 @@
# Voice notes
Enable transcription so voice notes become normal text runs.
## Enable transcription
```toml
[transports.telegram]
voice_transcription = true
voice_transcription_model = "gpt-4o-mini-transcribe" # optional
```
Set `OPENAI_API_KEY` in your environment (uses OpenAIs transcription API).
To use a local OpenAI-compatible Whisper server, also set `OPENAI_BASE_URL`
(for example, `http://localhost:8000/v1`) and a dummy `OPENAI_API_KEY` if your server ignores it.
If your server requires a specific model name, set `voice_transcription_model` (for example, `whisper-1`).
## Behavior
When you send a voice note, Takopi transcribes it and runs the result as a normal text message.
If transcription fails, youll get an error message and the run is skipped.
## Related
- [Config reference](../reference/config.md)
+42
View File
@@ -0,0 +1,42 @@
# Worktrees
Use `@branch` to run tasks in a dedicated git worktree for that branch.
## Enable worktree-based runs for a project
Add a `worktrees_dir` (and optionally a base branch) to the project:
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
worktrees_dir = ".worktrees" # relative to project path
worktree_base = "master" # base branch for new worktrees
```
## Run in a branch worktree
Send a message like:
```
/happy-gadgets @feat/memory-box freeze artifacts forever
```
## Ignore `.worktrees/` in git status
If you use the default `.worktrees/` directory inside the repo, add it to a gitignore.
One option is a global ignore:
```sh
git config --global core.excludesfile ~/.config/git/ignore
echo ".worktrees/" >> ~/.config/git/ignore
```
## Context persistence
When project/worktree context is active, Takopi includes a `ctx:` footer in messages.
When you reply, this context carries forward (you usually dont need to repeat `/project @branch`).
## Related
- [Context resolution](../reference/context-resolution.md)
+122
View File
@@ -0,0 +1,122 @@
# Write a plugin
Takopi supports entrypoint-based plugins for engines, transports, and commands.
## Checklist
1. Pick a plugin id (must match `^[a-z0-9_]{1,32}$`).
2. Add a Python entrypoint in your packages `pyproject.toml`.
3. Implement a backend object (`BACKEND`) with `id == entrypoint name`.
4. Install your package and validate with `takopi plugins --load`.
## Entrypoint groups
Takopi uses three entrypoint groups:
```toml
[project.entry-points."takopi.engine_backends"]
myengine = "myengine.backend:BACKEND"
[project.entry-points."takopi.transport_backends"]
mytransport = "mytransport.backend:BACKEND"
[project.entry-points."takopi.command_backends"]
mycommand = "mycommand.backend:BACKEND"
```
## Engine backend plugin (runner)
Minimal example:
```py
# myengine/backend.py
from __future__ import annotations
from pathlib import Path
from takopi.api import EngineBackend, EngineConfig, Runner
def build_runner(config: EngineConfig, config_path: Path) -> Runner:
_ = config_path
return MyEngineRunner(config)
BACKEND = EngineBackend(
id="myengine",
build_runner=build_runner,
cli_cmd="myengine",
install_cmd="pip install myengine",
)
```
Engine config is a raw table in `takopi.toml`:
```toml
[myengine]
model = "..."
```
## Transport backend plugin
Transport plugins connect Takopi to other messaging systems (Slack, Discord, …).
For most transports, delegate message handling to `handle_message()` from `takopi.api`.
## Command backend plugin
Command plugins add custom `/command` handlers. They only run when the message starts
with `/<id>` and the id does not collide with engine ids, project aliases, or reserved names.
Minimal example:
```py
# mycommand/backend.py
from __future__ import annotations
from takopi.api import CommandContext, CommandResult
class MyCommand:
id = "hello"
description = "say hello"
async def handle(self, ctx: CommandContext) -> CommandResult | None:
_ = ctx
return CommandResult(text="hello")
BACKEND = MyCommand()
```
### Command plugin configuration
Configure under `[plugins.<id>]`:
```toml
[plugins.hello]
greeting = "hello"
```
The parsed dict is available as `ctx.plugin_config` in `handle()`.
## Enable/disable installed plugins
```toml
[plugins]
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
```
- `enabled = []` (default) means “load all installed plugins”.
- If non-empty, only distributions with matching names are visible.
## Validate discovery and loading
```sh
takopi plugins
takopi plugins --load
```
## Related
- [Plugin system (design)](../explanation/plugin-system.md)
- [Plugin API reference](../reference/plugin-api.md)
+90 -19
View File
@@ -1,31 +1,102 @@
# Takopi # Takopi documentation
Takopi connects agent CLIs to Telegram so you can run, monitor, and reply to long-running tasks from chat. > Telegram bridge for coding agents (Codex, Claude Code, OpenCode, Pi).
It supports multiple runner backends and a pluggable transport layer, with a stable public API for extensions.
## What this site covers Takopi lets you run an engine CLI in a local repo while controlling it from Telegram: send a task, stream updates, and continue safely (reply-to-continue, topics, or sessions).
- How to get Takopi running end-to-end ## Choose your path
- Project aliases and worktree-aware workflows
- The plugin system and stable public API surface - **Im new / I want to get it running**
- Architectural details and behavioral guarantees - Start with **[Tutorials](tutorials/index.md)**:
- [Install & onboard](tutorials/install-and-onboard.md)
- [First run](tutorials/first-run.md)
- **I know what I want to do (enable a feature / fix a workflow)**
- Use **[How-to guides](how-to/index.md)**:
- [Projects](how-to/projects.md) and [Worktrees](how-to/worktrees.md)
- [Topics](how-to/topics.md) and [Route by chat](how-to/route-by-chat.md)
- [File transfer](how-to/file-transfer.md) and [Voice notes](how-to/voice-notes.md)
- **I need exact knobs, defaults, and contracts**
- Go straight to **[Reference](reference/index.md)**:
- [Commands & directives](reference/commands-and-directives.md)
- [Configuration](reference/config.md)
- [Specification](reference/specification.md) (normative behavior)
- **Im trying to understand the design**
- Read **[Explanation](explanation/index.md)**:
- [Architecture](explanation/architecture.md)
- [Routing & sessions](explanation/routing-and-sessions.md)
- [Plugin system](explanation/plugin-system.md)
## Quick start ## Quick start
If you just want to see it work end-to-end:
```bash ```bash
uv run takopi --help # Install
uv tool install -U takopi
# Configure Telegram + defaults
takopi --onboard
# Run in a repo
cd /path/to/your/repo
takopi
``` ```
## Documentation map Then open Telegram and send a task to your bot.
- Start here: [User guide](user-guide.md) ## Core concepts
- Projects and worktrees: [Projects](projects.md)
- Plugin development: [Plugins](plugins.md) and [Public API](public-api.md)
- System behavior: [Architecture](architecture.md) and [Specification](specification.md)
- Transport details: [Telegram](transports/telegram.md)
- Contributor notes: [Developing](developing.md)
## LLM entrypoints * **Engine**: the CLI that actually does the work (e.g. `codex`, `claude`, `opencode`, `pi`).
* **Project**: a named alias for a repo path (so you can run from anywhere).
* **Worktree / branch selection**: pick where work should happen (`@branch`).
* **Continuation**: how Takopi safely “continues” a run:
- `/llms.txt` lists the key pages and links to their Markdown mirrors. * reply-to-continue (always available)
- `/llms-full.txt` contains the full expanded content of those pages. * forum topics (thread-bound continuation)
* chat sessions (auto-resume)
* **Contract**: the stable rules (resume lines, event ordering, rendering expectations) in the
[Specification](reference/specification.md) and runner contract tests.
## For plugin authors
Start here:
* [Plugin API](reference/plugin-api.md) — **stable** `takopi.api` surface for plugins
* [Write a plugin](how-to/write-a-plugin.md)
* [Add a runner](how-to/add-a-runner.md)
If youre contributing to core:
* [Dev setup](how-to/dev-setup.md)
* [Module map](explanation/module-map.md)
## For LLM agents
Takopi publishes an LLM-oriented index at:
* `/llms.txt` — curated entrypoints + links to Markdown mirrors
* `/llms-full.txt` — expanded “single file” context of the most important pages
In the docs, start here:
* [Reference: For agents](reference/agents/index.md)
* [Repo map](reference/agents/repo-map.md)
* [Invariants](reference/agents/invariants.md)
## Where to look when something feels “off”
* “Why didnt it route to the right repo/branch?” → [Context resolution](reference/context-resolution.md)
* “Why didnt it continue where I left off?” → [Commands & directives](reference/commands-and-directives.md) and [Specification](reference/specification.md)
* “Why did Telegram messages behave weirdly?” → [Telegram transport](reference/transports/telegram.md)
* “Why is it built this way?” → [Architecture](explanation/architecture.md)
## Legacy portals
These pages remain as curated pointers to preserve old links:
- [User guide](user-guide.md)
- [Plugins](plugins.md)
- [Developing](developing.md)
+11 -296
View File
@@ -1,305 +1,20 @@
# Plugins # Plugins (moved)
Takopi supports **entrypoint-based plugins** for: This page was split into smaller Diátaxis pages.
- **Engine backends** (new runner implementations) ## Start here
- **Transport backends** (new chat/command transports)
- **Command backends** (custom `/command` handlers)
Plugins are **discovered lazily**: Takopi lists IDs without importing plugin code, - [Write a plugin](how-to/write-a-plugin.md)
and loads a plugin only when it is needed (or when you explicitly request it).
This keeps `takopi --help` fast and prevents broken plugins from bricking the CLI. ## Design
See `public-api.md` for the stable API surface you should depend on. - [Plugin system](explanation/plugin-system.md)
--- ## Reference
## Entrypoint groups - [Plugin API](reference/plugin-api.md)
Takopi uses three Python entrypoint groups: ## Diagnostics
```toml - `takopi plugins` lists discovered entrypoints without loading them.
[project.entry-points."takopi.engine_backends"] - `takopi plugins --load` loads each plugin to surface import errors.
myengine = "myengine.backend:BACKEND"
[project.entry-points."takopi.transport_backends"]
mytransport = "mytransport.backend:BACKEND"
[project.entry-points."takopi.command_backends"]
mycommand = "mycommand.backend:BACKEND"
```
**Rules:**
- The entrypoint **name** is the plugin ID.
- The entrypoint value must resolve to a **backend object**:
- Engine backend -> `EngineBackend`
- Transport backend -> `TransportBackend`
- Command backend -> `CommandBackend`
- The backend object **must** have `id == entrypoint name`.
Takopi validates this at load time and will report errors via `takopi plugins --load`.
---
## ID rules
Plugin IDs are used in the CLI and (for engines/projects) in Telegram commands.
They must match:
```
^[a-z0-9_]{1,32}$
```
If an ID does not match, it is skipped and reported as an error.
**Reserved IDs (engines):**
- `cancel` (core chat command)
- `init`, `plugins` (CLI commands)
Engines using these IDs are skipped and reported as errors.
**Reserved IDs (commands):**
- `cancel`, `init`, `plugins`
- Any engine id or project alias (checked at runtime)
Command backends using reserved IDs are skipped and reported as errors.
---
## Enabling plugins
Takopi supports a simple enabled list to control which plugins are visible.
```toml
[plugins]
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
```
- `enabled = []` (default) -> load all installed plugins.
- If `enabled` is non-empty, **only distributions with matching names** are visible.
- Distribution names are taken from package metadata (case-insensitive).
- If a plugin has no resolvable distribution name and an enabled list is set, it is hidden.
This enabled list affects:
- Engine subcommands registered in the CLI
- `takopi plugins` output
- Runtime resolution of engines/transports/commands
---
## Discovering plugins
Use the CLI to inspect plugins:
```sh
takopi plugins
takopi plugins --load
```
Behavior:
- `takopi plugins` lists discovered entrypoints **without loading them**.
- `--load` loads each plugin to validate type and surface import errors.
- Errors are shown at the end, grouped by engine/transport and distribution.
- If `[plugins] enabled` is set, entries are still listed but marked `enabled`/`disabled`.
---
## Engine backend plugins
Engine plugins implement a runner for a new engine CLI and expose
an `EngineBackend` object.
Minimal example:
```py
# myengine/backend.py
from __future__ import annotations
from pathlib import Path
from takopi.api import EngineBackend, EngineConfig, Runner
def build_runner(config: EngineConfig, config_path: Path) -> Runner:
_ = config_path
# Parse config if needed; raise ConfigError for invalid config.
return MyEngineRunner(config)
BACKEND = EngineBackend(
id="myengine",
build_runner=build_runner,
cli_cmd="myengine",
install_cmd="pip install myengine",
)
```
`EngineConfig` is the raw config table (dict) from `takopi.toml`:
```toml
[myengine]
model = "..."
```
Read it with `settings.engine_config("myengine", config_path=...)` in Takopi,
or just consume the dict directly in your runner builder.
See `public-api.md` for the runner contract and helper classes like
`JsonlSubprocessRunner` and `EventFactory`.
---
## Transport backend plugins
Transport plugins connect Takopi to new messaging systems (Slack, Discord, etc).
You must provide a `TransportBackend` object with:
- `id` and `description`
- `check_setup()` -> returns `SetupResult` (issues + config path)
- `interactive_setup()` -> optional interactive setup flow
- `lock_token()` -> token fingerprinting for config locks
- `build_and_run()` -> build transport and start the main loop
Minimal skeleton:
```py
# mytransport/backend.py
from __future__ import annotations
from pathlib import Path
from takopi.api import (
EngineBackend,
SetupResult,
TransportBackend,
TransportRuntime,
)
class MyTransportBackend:
id = "mytransport"
description = "MyTransport bot"
def check_setup(
self, engine_backend: EngineBackend, *, transport_override: str | None = None
) -> SetupResult:
_ = engine_backend, transport_override
return SetupResult(issues=[], config_path=Path("takopi.toml"))
def interactive_setup(self, *, force: bool) -> bool:
_ = force
return True
def lock_token(
self, *, transport_config: dict[str, object], config_path: Path
) -> str | None:
_ = transport_config, config_path
return None
def build_and_run(
self,
*,
transport_config: dict[str, object],
config_path: Path,
runtime: TransportRuntime,
final_notify: bool,
default_engine_override: str | None,
) -> None:
_ = (
transport_config,
config_path,
runtime,
final_notify,
default_engine_override,
)
raise NotImplementedError
BACKEND = MyTransportBackend()
```
For most transports, you will want to call `handle_message()` from `takopi.api`
inside your message loop. That function implements progress updates, resume handling,
and cancellation semantics.
---
## Command backend plugins
Command plugins add custom `/command` handlers. A command only runs when the
message starts with `/command` and does **not** collide with engine ids,
project aliases, or reserved command names.
Minimal example:
```py
# mycommand/backend.py
from __future__ import annotations
from takopi.api import CommandContext, CommandResult, RunRequest
class MultiCommand:
id = "multi"
description = "run the prompt on every engine"
async def handle(self, ctx: CommandContext) -> CommandResult | None:
prompt = ctx.args_text.strip()
if not prompt:
return CommandResult(text="usage: /multi <prompt>")
requests = [
RunRequest(prompt=prompt, engine=engine)
for engine in ctx.runtime.available_engine_ids()
]
results = await ctx.executor.run_many(
requests,
mode="capture",
parallel=True,
)
blocks = []
for result in results:
text = result.message.text if result.message else "no output"
blocks.append(f"## {result.engine}\n{text}")
return CommandResult(text="\n\n".join(blocks))
BACKEND = MultiCommand()
```
### Command plugin configuration
Configure command plugins under `[plugins.<id>]`:
```toml
[plugins.multi]
engines = ["codex", "claude"]
```
The parsed dict is available as `ctx.plugin_config` inside `handle()`.
---
## Versioning & compatibility
Takopi exposes a **stable plugin API** via `takopi.api`.
- `TAKOPI_PLUGIN_API_VERSION = 1` is the current API version.
- Depend on a compatible Takopi version range, for example:
```toml
dependencies = ["takopi>=0.14,<0.15"]
```
When the plugin API changes, Takopi will bump the API version and document
any compatibility guidance.
---
## Troubleshooting
Common issues:
- **Plugin missing from CLI**: check the enabled list in `[plugins] enabled`.
- **Plugin not listed**: verify entrypoint group and ID regex.
- **Load failures**: run `takopi plugins --load` and inspect errors.
- **ID mismatch**: ensure `BACKEND.id == entrypoint name`.
+7
View File
@@ -0,0 +1,7 @@
# For agents
These pages are **high-signal reference** for LLM agents (and humans acting like one).
- [Repo map](repo-map.md)
- [Invariants](invariants.md)
+35
View File
@@ -0,0 +1,35 @@
# Invariants
These are the “dont break this” rules that keep Takopi reliable.
## Runner contract
The runner contract is enforced by `tests/test_runner_contract.py`:
- Exactly one `StartedEvent`
- Exactly one `CompletedEvent`
- `CompletedEvent` is last
- `CompletedEvent.resume == StartedEvent.resume`
See also the [Plugin API](../plugin-api.md) runner contract section.
## Per-thread serialization
At most one active run may operate on the same thread/session at a time.
This is enforced both by scheduling and by per-resume-token runner locks.
Normative details live in the [Specification](../specification.md) (§5.2).
## Resume lines
Resume lines embedded in chat are the engines canonical resume command (e.g. `claude --resume <id>`).
- The runner is authoritative for formatting and extraction.
- Transports/rendering must preserve the resume line reliably (even when trimming/splitting).
Normative details live in the [Specification](../specification.md) (§3).
## Local contribution hygiene
- Run `just check` before code commits.
+40
View File
@@ -0,0 +1,40 @@
# Repo map
Quick pointers for navigating the Takopi codebase.
## Where things start
- CLI entry point: `src/takopi/cli.py`
- Telegram backend entry point: `src/takopi/telegram/backend.py`
- Telegram bridge loop: `src/takopi/telegram/bridge.py`
- Transport-agnostic handler: `src/takopi/runner_bridge.py`
## Core concepts
- Domain types (resume tokens, events, actions): `src/takopi/model.py`
- Runner protocol: `src/takopi/runner.py`
- Router selection and resume polling: `src/takopi/router.py`
- Per-thread scheduling: `src/takopi/scheduler.py`
- Progress reduction and rendering: `src/takopi/progress.py`, `src/takopi/markdown.py`
## Engines and streaming
- Runner implementations: `src/takopi/runners/*`
- JSONL decoding schemas: `src/takopi/schemas/*`
## Plugins
- Public API boundary (`takopi.api`): `src/takopi/api.py`
- Entrypoint discovery + lazy loading: `src/takopi/plugins.py`
- Engine/transport/command backend loading: `src/takopi/engines.py`, `src/takopi/transports.py`, `src/takopi/commands.py`
## Configuration
- Settings model + TOML/env loading: `src/takopi/settings.py`
- Config migrations: `src/takopi/config_migrations.py`
## Docs and contracts
- Normative behavior: [Specification](../specification.md)
- Runner invariants: `tests/test_runner_contract.py`
+71
View File
@@ -0,0 +1,71 @@
# Commands & directives
This page documents Takopis user-visible command surface: message directives, in-chat commands, and the CLI.
## Message directives
Takopi parses the first non-empty line of a message for a directive prefix.
| Directive | Example | Effect |
|----------|---------|--------|
| `/engine` | `/codex fix flaky test` | Select an engine for this message. |
| `/project` | `/happy-gadgets add escape-pod` | Select a project alias. |
| `@branch` | `@feat/happy-camera rewind to checkpoint` | Run in a worktree for the branch. |
| Combined | `/happy-gadgets @feat/flower-pin observe unseen` | Project + branch. |
Notes:
- Directives are only parsed at the start of the first non-empty line.
- Parsing stops at the first non-directive token.
- If a reply contains a `ctx:` line, Takopi ignores new directives and uses the reply context.
See [Context resolution](context-resolution.md) for the full rules.
## Context footer (`ctx:`)
When a run has project context, Takopi appends a footer line rendered as inline code:
- With branch: `` `ctx: <project> @<branch>` ``
- Without branch: `` `ctx: <project>` ``
This line is parsed from replies and takes precedence over new directives.
## Telegram in-chat commands
| Command | Description |
|---------|-------------|
| `/cancel` | Reply to the progress message to stop the current run. |
| `/agent` | Show/set the default agent for the current scope. |
| `/file put <path>` | Upload a document into the repo/worktree (requires file transfer enabled). |
| `/file get <path>` | Fetch a file or directory back into Telegram. |
| `/topic <project> @branch` | Create/bind a topic (topics enabled). |
| `/ctx` | Show topic context binding (topics enabled). |
| `/ctx set <project> @branch` | Update topic context binding. |
| `/ctx clear` | Remove topic context binding. |
| `/new` | Clear stored sessions for the current scope (topic/chat). |
## CLI
Takopis CLI is an auto-router by default; engine subcommands override the default engine.
### Commands
| Command | Description |
|---------|-------------|
| `takopi` | Start Takopi (runs onboarding if setup/config is missing and youre in a TTY). |
| `takopi <engine>` | Run with a specific engine (e.g. `takopi codex`). |
| `takopi init <alias>` | Register the current repo as a project. |
| `takopi chat-id` | Capture the current chat id. |
| `takopi chat-id --project <alias>` | Save the captured chat id to a project. |
| `takopi plugins` | List discovered plugins without loading them. |
| `takopi plugins --load` | Load each plugin to validate types and surface import errors. |
### Common flags
| Flag | Description |
|------|-------------|
| `--onboard` | Force the interactive setup wizard before starting. |
| `--transport <id>` | Override the configured transport backend id. |
| `--debug` | Write debug logs to `debug.log`. |
| `--final-notify/--no-final-notify` | Send the final response as a new message vs an edit. |
+109
View File
@@ -0,0 +1,109 @@
# Configuration
Takopi reads configuration from `~/.takopi/takopi.toml`.
If you expect to edit config while Takopi is running, set:
```toml
watch_config = true
```
## Top-level keys
| Key | Type | Default | Notes |
|-----|------|---------|-------|
| `watch_config` | bool | `false` | Hot-reload config changes (transport excluded). |
| `default_engine` | string | `"codex"` | Default engine id for new threads. |
| `default_project` | string\|null | `null` | Default project alias. |
| `transport` | string | `"telegram"` | Transport backend id. |
## `transports.telegram`
```toml
[transports.telegram]
bot_token = "..."
chat_id = 123
```
| Key | Type | Default | Notes |
|-----|------|---------|-------|
| `bot_token` | string | (required) | Telegram bot token from @BotFather. |
| `chat_id` | int | (required) | Default chat id. |
| `message_overflow` | `"trim"`\|`"split"` | `"trim"` | How to handle long final responses. |
| `voice_transcription` | bool | `false` | Enable voice note transcription. |
| `voice_max_bytes` | int | `10485760` | Max voice note size (bytes). |
| `voice_transcription_model` | string | `"gpt-4o-mini-transcribe"` | OpenAI transcription model name. |
| `session_mode` | `"stateless"`\|`"chat"` | `"stateless"` | Auto-resume mode. |
| `show_resume_line` | bool | `true` | Show resume line in message footer. |
### `transports.telegram.topics`
| Key | Type | Default | Notes |
|-----|------|---------|-------|
| `enabled` | bool | `false` | Enable forum-topic features. |
| `scope` | `"auto"`\|`"main"`\|`"projects"`\|`"all"` | `"auto"` | Where topics are managed. |
### `transports.telegram.files`
| Key | Type | Default | Notes |
|-----|------|---------|-------|
| `enabled` | bool | `false` | Enable `/file put` and `/file get`. |
| `auto_put` | bool | `true` | Auto-save uploads. |
| `auto_put_mode` | `"upload"`\|`"prompt"` | `"upload"` | Whether uploads also start a run. |
| `uploads_dir` | string | `"incoming"` | Relative path inside the repo/worktree. |
| `allowed_user_ids` | int[] | `[]` | Allowed senders; empty allows private chats (group usage requires admin). |
| `deny_globs` | string[] | (defaults) | Glob denylist (e.g. `.git/**`, `**/*.pem`). |
File size limits (not configurable):
- uploads: 20 MiB
- downloads: 50 MiB
## `projects.<alias>`
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
worktrees_dir = ".worktrees"
default_engine = "claude"
worktree_base = "master"
chat_id = -1001234567890
```
| Key | Type | Default | Notes |
|-----|------|---------|-------|
| `path` | string | (required) | Repo root (expands `~`). Relative paths are resolved against the config directory. |
| `worktrees_dir` | string | `".worktrees"` | Worktree root (relative to `path` unless absolute). |
| `default_engine` | string\|null | `null` | Per-project default engine. |
| `worktree_base` | string\|null | `null` | Base branch for new worktrees. |
| `chat_id` | int\|null | `null` | Bind a Telegram chat to this project. |
Legacy config note: top-level `bot_token` / `chat_id` are auto-migrated into `[transports.telegram]` on startup.
## Plugins
### `plugins.enabled`
```toml
[plugins]
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
```
- `enabled = []` (default) means “load all installed plugins”.
- If non-empty, only distributions with matching names are visible (case-insensitive).
### `plugins.<id>`
Plugin-specific configuration lives under `[plugins.<id>]` and is passed to command plugins as `ctx.plugin_config`.
## Engine-specific config tables
Engines can have top-level config tables keyed by engine id, for example:
```toml
[codex]
model = "..."
```
The shape is engine-defined.
@@ -1,7 +1,7 @@
# Projects and Worktrees # Context resolution
This doc covers project aliases, worktree behavior, and how Takopi resolves run This page documents how Takopi resolves **run context** (project, worktree/branch, engine) from messages.
context from messages. For step-by-step usage, see [Projects](../how-to/projects.md) and [Worktrees](../how-to/worktrees.md).
## Overview ## Overview
@@ -13,9 +13,10 @@ worktree-based runs via `@branch`.
the task in that worktree. the task in that worktree.
- Progress/final messages include a `ctx:` footer when project context is active. - Progress/final messages include a `ctx:` footer when project context is active.
## Config schema ## Config schema (relevant subset)
All config lives in `~/.takopi/takopi.toml`. All config lives in `~/.takopi/takopi.toml`.
See [Config](config.md) for the full reference.
```toml ```toml
default_engine = "codex" # optional default_engine = "codex" # optional
+26
View File
@@ -0,0 +1,26 @@
# Environment variables
Takopi supports a small set of environment variables for logging and runtime behavior.
## Logging
| Variable | Description |
|----------|-------------|
| `TAKOPI_LOG_LEVEL` | Minimum log level (default `info`; `--debug` forces `debug`). |
| `TAKOPI_LOG_FORMAT` | `console` (default) or `json`. |
| `TAKOPI_LOG_COLOR` | Force color on/off (`1/true/yes/on` or `0/false/no/off`). |
| `TAKOPI_LOG_FILE` | Append JSON lines to a file. `--debug` defaults this to `debug.log`. |
| `TAKOPI_TRACE_PIPELINE` | Log pipeline events at `info` instead of `debug`. |
## CLI behavior
| Variable | Description |
|----------|-------------|
| `TAKOPI_NO_INTERACTIVE` | Disable interactive prompts (useful for CI / non-TTY). |
## Engine-specific
| Variable | Description |
|----------|-------------|
| `PI_CODING_AGENT_DIR` | Override Pi agent session directory base path. |
+65
View File
@@ -0,0 +1,65 @@
# Reference
Reference docs are **authoritative and exact**. Use these when you need stable facts, schemas, and contracts.
If youre trying to achieve a goal (“enable topics”, “fetch a file”), use **[How-to](../how-to/index.md)**.
If youre trying to understand the *why*, use **[Explanation](../explanation/index.md)**.
## Most-used reference pages
- [Commands & directives](commands-and-directives.md)
- Message prefixes like `/engine`, `/project`, and `@branch`
- In-chat commands like `/cancel`, `/new`, `/ctx`, `/file …`, `/topic …`
- [Configuration](config.md)
- `takopi.toml` options and defaults
- Telegram transport options (sessions, topics, files, voice transcription)
## Normative behavior
- [Specification](specification.md)
The normative (“MUST/SHOULD/MAY”) contract for:
- resume tokens + resume lines
- event model
- progress/final message semantics
- per-thread serialization rules
## Plugins and extension contracts
- [Plugin API](plugin-api.md)
The **only** supported import surface for plugins: `takopi.api`
- [Context resolution](context-resolution.md)
How Takopi resolves project + worktree context from directives, replies, and chat ids.
## Transport reference
- [Telegram transport](transports/telegram.md)
Rate limits, outbox behavior, retries, message editing rules.
## Runner reference
These are “engine adapter” implementation details: JSONL formats, mapping rules, and emitted events.
- [Runners overview](runners/index.md)
- Claude:
- [runner.md](runners/claude/runner.md)
- [stream-json-cheatsheet.md](runners/claude/stream-json-cheatsheet.md)
- [takopi-events.md](runners/claude/takopi-events.md)
- Codex:
- [exec-json-cheatsheet.md](runners/codex/exec-json-cheatsheet.md)
- [takopi-events.md](runners/codex/takopi-events.md)
- OpenCode:
- [runner.md](runners/opencode/runner.md)
- [stream-json-cheatsheet.md](runners/opencode/stream-json-cheatsheet.md)
- [takopi-events.md](runners/opencode/takopi-events.md)
- Pi:
- [runner.md](runners/pi/runner.md)
- [stream-json-cheatsheet.md](runners/pi/stream-json-cheatsheet.md)
- [takopi-events.md](runners/pi/takopi-events.md)
## For LLM agents
If youre an LLM agent contributing to Takopi, start here:
- [Agent entrypoint](agents/index.md)
- [Repo map](agents/repo-map.md)
- [Invariants](agents/invariants.md) (runner contract, resume handling, “dont break this” rules)
@@ -1,6 +1,6 @@
# Public Plugin API # Plugin API
Takopi's **public plugin API** is exported from: Takopis **public plugin API** is exported from:
``` ```
takopi.api takopi.api
@@ -199,7 +199,7 @@ Claude runner implementation summary (no Takopi domain model changes):
2. [x] Define `BACKEND` in `takopi/runners/claude.py`: 2. [x] Define `BACKEND` in `takopi/runners/claude.py`:
- `install_cmd`: install command for the `claude` binary - `install_cmd`: install command for the `claude` binary
- `build_runner`: read `[claude]` config + construct runner - `build_runner`: read `[claude]` config + construct runner
3. [x] Add new docs (this file + `claude-stream-json-cheatsheet.md`). 3. [x] Add new docs (this file + `stream-json-cheatsheet.md`).
4. [x] Add fixtures in `tests/fixtures/` (see below). 4. [x] Add fixtures in `tests/fixtures/` (see below).
5. [x] Add unit tests mirroring `tests/test_codex_*` but for Claude translation 5. [x] Add unit tests mirroring `tests/test_codex_*` but for Claude translation
and resume parsing (recommended, not required for initial handoff). and resume parsing (recommended, not required for initial handoff).
+9
View File
@@ -0,0 +1,9 @@
# Runners
Runner docs describe the **engine-specific** behavior: event shapes, JSON streaming, and integration notes.
- Claude: [Runner](claude/runner.md), [Stream JSON cheatsheet](claude/stream-json-cheatsheet.md), [Takopi events](claude/takopi-events.md)
- Codex: [Exec JSON cheatsheet](codex/exec-json-cheatsheet.md), [Takopi events](codex/takopi-events.md)
- OpenCode: [Runner](opencode/runner.md), [Stream JSON cheatsheet](opencode/stream-json-cheatsheet.md), [Takopi events](opencode/takopi-events.md)
- Pi: [Runner](pi/runner.md), [Stream JSON cheatsheet](pi/stream-json-cheatsheet.md), [Takopi events](pi/takopi-events.md)
@@ -44,4 +44,4 @@ OpenCode outputs JSON events with the following types:
| `step_finish` | End of a step (reason: "stop" or "tool-calls" when present) | | `step_finish` | End of a step (reason: "stop" or "tool-calls" when present) |
| `error` | Error event | | `error` | Error event |
See [opencode-stream-json-cheatsheet.md](./opencode-stream-json-cheatsheet.md) for detailed event format documentation. See [stream-json-cheatsheet.md](./stream-json-cheatsheet.md) for detailed event format documentation.
+32
View File
@@ -0,0 +1,32 @@
# First run
Youll run Takopi in a repo, send a task from Telegram, and learn the basic controls.
## 1) Start Takopi in a repo
```sh
cd ~/dev/your-repo
takopi
```
## 2) Send a message to your bot
Takopi streams progress in chat and posts a final message when the engine finishes.
If you want to override the default engine for a single message, prefix the first line:
```
/codex explain this code
/claude refactor this module
```
## 3) Continue or cancel
- Continue the same thread by **replying** to any bot message that contains a resume line in the footer.
- Cancel an in-flight run by clicking the cancel button or replying to the progress message with `/cancel`.
## Next
- [Projects](../how-to/projects.md) and [Worktrees](../how-to/worktrees.md)
- [Commands & directives](../reference/commands-and-directives.md)
+41
View File
@@ -0,0 +1,41 @@
# Tutorials
Tutorials are **step-by-step learning paths**. If youre new, start here and follow them in order.
If you already know what you want to do (e.g. “enable topics” or “fetch a file”), jump to **How-to**.
## Before you start
Youll need:
- A Telegram account + a bot token
- Python **3.14+** and `uv`
- At least one engine CLI on your `PATH`: `codex`, `claude`, `opencode`, or `pi`
## 1) Install and onboard
Get Takopi installed, create a bot token, capture your chat id, and pick a default engine.
- [Install & onboard](install-and-onboard.md)
## 2) Your first run
Run Takopi in a repo, send your first task from Telegram, and learn the two core controls:
- reply-to-continue (resume)
- cancel a run
- [First run](first-run.md)
## What to do next
Once you can run tasks end-to-end, youll usually want:
- Register a repo as a **project alias**: [How-to: Projects](../how-to/projects.md)
- Run work on a branch in a dedicated **worktree**: [How-to: Worktrees](../how-to/worktrees.md)
- Keep long-running work organized in threads: [How-to: Topics](../how-to/topics.md)
When you need exact knobs and defaults:
- [Reference: Configuration](../reference/config.md)
- [Reference: Commands & directives](../reference/commands-and-directives.md)
+56
View File
@@ -0,0 +1,56 @@
# Install & onboard
Youll install Takopi, connect it to Telegram, and generate a working `takopi.toml`.
## Prerequisites
- A Telegram account
- Python 3.14+ and `uv`
- At least one supported engine CLI on your `PATH` (`codex`, `claude`, `opencode`, or `pi`)
## 1) Install Takopi
```sh
uv tool install -U takopi
```
## 2) Run onboarding
Start Takopi:
```sh
takopi
```
If you want to re-run onboarding later:
```sh
takopi --onboard
```
The wizard walks you through:
1. Creating a bot token via [@BotFather](https://t.me/BotFather)
2. Capturing your `chat_id` (it listens for a message from you)
3. Choosing a default engine
Your configuration lives at `~/.takopi/takopi.toml`.
## 3) Verify minimal config
After onboarding you should have something like:
```toml
default_engine = "codex"
transport = "telegram"
[transports.telegram]
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
chat_id = 123456789
```
## Next
- [First run](first-run.md)
- [Config reference](../reference/config.md)
+19 -539
View File
@@ -1,546 +1,26 @@
# Takopi User Guide # Takopi User Guide (moved)
Takopi is a command-line tool that lets you control coding agents—like Codex, Claude, and others—through Telegram. Send a message, and takopi runs the agent in your repo, streaming progress back to your chat. It supports multi-repo workflows, git worktrees, and per-project routing. This guide has been reorganized into smaller Diátaxis pages.
This guide starts simple and layers on features as you go. Jump to any section or read straight through. ## Start here
## Prerequisites - [Install & onboard](tutorials/install-and-onboard.md)
- [First run](tutorials/first-run.md)
Before you begin, make sure you have: ## Common tasks
- A Telegram account - [Switch engines](how-to/switch-engines.md)
- Python 3.14+ and `uv` installed - [Projects](how-to/projects.md)
- At least one supported agent CLI installed and on your `PATH` (codex, claude, opencode, pi) - [Worktrees](how-to/worktrees.md)
- Basic familiarity with git (especially if you plan to use worktrees) - [Route by chat](how-to/route-by-chat.md)
- [Topics](how-to/topics.md)
- [Voice notes](how-to/voice-notes.md)
- [File transfer](how-to/file-transfer.md)
- [Schedule tasks](how-to/schedule-tasks.md)
- [Troubleshooting](how-to/troubleshooting.md)
## Key concepts ## Reference
A few terms you'll see throughout: - [Configuration](reference/config.md)
- [Commands & directives](reference/commands-and-directives.md)
| Term | Meaning | - [Context resolution](reference/context-resolution.md)
|------|---------|
| **Engine** | A coding agent backend (Codex, Claude, opencode, pi) |
| **Project** | A repo/workdir alias used for routing (can set default engine, worktrees, chat ID) |
| **Worktree** | A git feature that lets you check out multiple branches simultaneously in separate directories |
| **Topic** | A Telegram forum thread bound to a project/branch; stores per-topic resume tokens |
| **Resume token** | State that allows an engine to continue from where it left off |
---
## How conversations work
Takopi is **stateless by default**. Each message starts a new engine session unless a resume token is present.
To continue a session:
- **Reply** to a bot message. Takopi reads the resume token from the footer and resumes that session.
- **Forum topics** (optional) store resume tokens per topic and auto-resume for new messages in that topic.
Reset with `/new`.
- **Chat sessions** (optional) store one resume token per chat (per sender in groups) so new messages
auto-resume without replying. Enable with `session_mode = "chat"` and reset with `/new`.
State is stored in `telegram_chat_sessions_state.json`.
You can hide resume lines by setting `[transports.telegram].show_resume_line = false`
when auto-resume is available and a project context is resolved.
Reply-to-continue always works, even if chat sessions or topics are enabled.
---
## 1. Installation and setup
Install takopi with:
```sh
uv tool install -U takopi
```
Run it once to start the onboarding wizard:
```sh
takopi
```
The wizard walks you through:
1. Creating a Telegram bot token via [@BotFather](https://t.me/BotFather)
2. Capturing your `chat_id` (the wizard listens for a message from you)
3. Choosing a default engine
To re-run onboarding later, use `takopi --onboard`.
Your configuration is stored at `~/.takopi/takopi.toml`.
### Minimal configuration
After onboarding, your config looks something like this:
```toml
default_engine = "codex"
transport = "telegram"
[transports.telegram]
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
chat_id = 123456789
```
Optional: split long final responses instead of trimming them:
```toml
[transports.telegram]
message_overflow = "split" # trim | split
```
---
## 2. Your first handoff
The simplest workflow:
1. `cd` into any git repository
2. Run `takopi`
3. Send a message to your bot
Takopi streams progress in the chat and sends a final response when the agent finishes.
### Basic controls
- **Reply** to a bot message to continue the session (takopi reads the resume token in the footer)
- **Cancel** a run by clicking the cancel button or replying to the progress message with `/cancel`
---
## 3. Switching engines
Prefix your message with an engine directive to override the default:
```
/codex hard reset the timeline
/claude shrink and store artifacts forever
/opencode hide their paper until they reply
/pi render a diorama of this timeline
```
Directives are only parsed at the start of the first non-empty line.
### Default agent per chat or topic
Use `/agent` to view or set a persistent default for the current scope:
```
/agent
/agent set claude
/agent clear
```
- Inside a forum topic, `/agent set` affects that topic.
- In normal chats, it affects the whole chat.
- In group chats, only admins can change defaults.
Precedence (highest to lowest): resume token → `/engine` directive → topic default → chat default → project default → global default.
### Setting up engines
Takopi shells out to the agent CLIs. Install them and make sure they're on your `PATH`
(codex, claude, opencode, pi). Authentication is handled by each CLI (login,
config files, or environment variables).
---
## 4. Projects
For repos you work with often, register them as projects:
```sh
cd ~/dev/happy-gadgets
takopi init happy-gadgets
```
This adds a project entry to your config (for example):
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
```
Now you can target it from anywhere using the `/project` directive:
```
/happy-gadgets pinky-link two threads
```
If you expect to add or edit projects while takopi is running, enable config
watching so changes are picked up automatically:
```toml
watch_config = true
```
### Project-specific settings
Projects can override global defaults:
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
default_engine = "claude"
worktrees_dir = ".worktrees"
worktree_base = "master"
```
### Setting a default project
If you mostly work in one repo:
```toml
default_project = "happy-gadgets"
```
---
## 5. Worktrees
Worktrees let you work on multiple branches without switching back and forth. Use `@branch` to run a task in a dedicated worktree:
```
/happy-gadgets @feat/memory-box freeze artifacts forever
```
Takopi creates (or reuses) a worktree at:
```
<worktrees_root>/<branch>
```
`worktrees_root` is `<project.path>/<worktrees_dir>` unless `worktrees_dir` is an
absolute path. If the branch matches the repo's current branch, Takopi runs in the
main repo instead of creating a new worktree.
### Worktree configuration
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
worktrees_dir = ".worktrees" # relative to project path
worktree_base = "master" # base branch for new worktrees
```
To avoid `.worktrees/` showing up as untracked, add it to your global gitignore:
```sh
git config --global core.excludesfile ~/.config/git/ignore
echo ".worktrees/" >> ~/.config/git/ignore
```
### Context persistence
Takopi adds a `ctx:` footer to messages with project and branch info. When you reply, this context carries forward—no need to repeat `/project @branch` each time.
---
## 6. Per-project chat routing
Give each project its own Telegram chat:
```sh
takopi chat-id --project happy-gadgets
```
Send any message in the target chat. Takopi captures the `chat_id` and updates your config:
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
chat_id = -1001234567890
```
Messages from that chat automatically route to the project.
### Rules for chat IDs
- Each `projects.*.chat_id` must be unique
- Project chat IDs must not match `transports.telegram.chat_id`
- Telegram uses positive IDs for private chats and negative IDs for groups/supergroups
### Capture a chat ID without saving
To see a chat ID without writing to config:
```sh
takopi chat-id
```
---
## 7. Topics
Topics bind Telegram forum threads to specific project/branch contexts. They also preserve resume tokens and can store a default agent per topic.
### Enabling topics
```toml
[transports.telegram.topics]
enabled = true
```
Your bot needs **Manage Topics** permission in the group.
If any `projects.<alias>.chat_id` are configured, topics are managed in those
project chats; otherwise topics are managed in the main chat.
### Topic behavior
```
┌────────────────────────────┐
│ takopi projects │
├────────────────────────────┤
│ takopi @master │
│ takopi @feat/topics │
│ happy-gadgets @master │
│ happy-gadgets @feat/camera │
└────────────────────────────┘
```
Each project can have its own forum-enabled supergroup. Topics still
include the project name for consistency, but the project is inferred from the
chat. Regular messages in that chat also infer the project, so `/project` is
usually optional.
```
┌────────────────────────────────┐ ┌───────────────────────────────────┐
│ takopi │ │ happy-gadgets │
├────────────────────────────────┤ ├───────────────────────────────────┤
│ takopi @master │ │ happy-gadgets @master │
│ takopi @feat/topics │ │ happy-gadgets @feat/happy-camera │
│ takopi @feat/voice │ │ happy-gadgets @feat/memory-box │
└────────────────────────────────┘ └───────────────────────────────────┘
```
### Topic commands
Run these inside a topic thread:
| Command | Description |
|---------|-------------|
| `/topic <project> @branch` | Create a new topic bound to context |
| `/ctx` | Show the current binding |
| `/ctx set <project> @branch` | Update the binding |
| `/ctx clear` | Remove the binding |
| `/new` | Clear resume tokens for this topic |
In project chats, omit the project: `/topic @branch` or `/ctx set @branch`.
### Configuration examples
**Main chat only:**
```toml
[transports.telegram]
chat_id = -1001234567890
# show_resume_line = false
[transports.telegram.topics]
enabled = true
```
**Project chats:**
```toml
[transports.telegram]
chat_id = 123456789 # main chat (private, for non-project messages)
# show_resume_line = false
[transports.telegram.topics]
enabled = true
[projects.takopi]
path = "~/dev/takopi"
chat_id = -1001111111111 # forum-enabled group
```
Topic state is stored in `telegram_topics_state.json` next to your config file. Chat defaults live in `telegram_chat_prefs_state.json`.
---
## 8. Voice notes
Dictate tasks instead of typing:
```toml
[transports.telegram]
voice_transcription = true
voice_transcription_model = "gpt-4o-mini-transcribe" # optional
```
Set `OPENAI_API_KEY` in your environment (uses OpenAI's transcription API with the
`gpt-4o-mini-transcribe` model by default). To use a local OpenAI-compatible
Whisper server, also set `OPENAI_BASE_URL` (for example, `http://localhost:8000/v1`)
and a dummy `OPENAI_API_KEY` if your server ignores it. If your server requires a
specific model name, set `voice_transcription_model` accordingly (for example,
`whisper-1`).
When you send a voice note, takopi transcribes it and runs the result as a normal text message. If transcription fails, you'll get an error message and the run is skipped.
---
---
## 9. File transfer
Upload files into the active repo/worktree or fetch files back into Telegram.
### Upload a file
Send a document with a caption:
```
/file put <path>
```
Examples:
```
/file put docs/spec.pdf
/file put /happy-gadgets @feat/camera assets/logo.png
```
If you send a file **without a caption**, takopi saves it to:
```
incoming/<original_filename>
```
If you set `auto_put_mode = "prompt"`, a caption/question will start a run
immediately after upload, with the prompt annotated with the uploaded path.
Use `--force` to overwrite an existing file:
```
/file put --force docs/spec.pdf
```
### Fetch a file
Send:
```
/file get <path>
```
Directories are zipped automatically.
### File transfer config
```toml
[transports.telegram.files]
enabled = true
auto_put = true
auto_put_mode = "upload"
uploads_dir = "incoming"
allowed_user_ids = [123456789]
deny_globs = [".git/**", ".env", ".envrc", "**/*.pem", "**/.ssh/**"]
```
Notes:
- File transfer is **disabled by default**.
- If `allowed_user_ids` is empty, private chats are allowed and group usage requires admin privileges.
---
## 10. Configuration reference
Full example with all options:
```toml
# Global defaults
default_engine = "codex"
default_project = "takopi"
transport = "telegram"
watch_config = true # hot-reload on config changes (except transport)
[transports.telegram]
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
chat_id = 123456789
voice_transcription = true
voice_transcription_model = "gpt-4o-mini-transcribe"
session_mode = "stateless" # or "chat" for auto-resume per chat
show_resume_line = true
[transports.telegram.files]
enabled = true
auto_put = true
auto_put_mode = "upload"
uploads_dir = "incoming"
allowed_user_ids = [123456789]
deny_globs = [".git/**", ".env", ".envrc", "**/*.pem", "**/.ssh/**"]
[transports.telegram.topics]
enabled = true
scope = "auto"
# Project definitions
[projects.takopi]
path = "~/dev/takopi"
default_engine = "codex"
worktrees_dir = ".worktrees"
worktree_base = "master"
# chat_id = -1001234567890 # optional: dedicated chat
[projects.happy-planet]
path = "~/dev/happy-planet"
default_engine = "claude"
worktrees_dir = "~/.takopi/worktrees/happy-planet"
worktree_base = "develop"
```
---
## 11. Command cheatsheet
### Message directives
| Directive | Example | Description |
|-----------|---------|-------------|
| `/engine` | `/codex make threads resolve their differences` | Use a specific engine |
| `/project` | `/happy-gadgets add escape-pod` | Target a project |
| `@branch` | `@feat/happy-camera rewind to checkpoint` | Run in a worktree |
| Combined | `/happy-gadgets @feat/flower-pin observe unseen` | Project + branch |
### In-chat commands
| Command | Description |
|---------|-------------|
| `/cancel` | Reply to the progress message to stop the current run |
| `/file put <path>` | Upload a document into the repo/worktree |
| `/file get <path>` | Fetch a file (directories are zipped) |
| `/agent` | Show/set the default agent for the current scope |
| `/topic <project> @branch` | Create/bind a topic |
| `/ctx` | Show current context |
| `/ctx set <project> @branch` | Update context binding |
| `/ctx clear` | Remove context binding |
| `/new` | Clear stored resume tokens (topic or chat session) |
### CLI commands
| Command | Description |
|---------|-------------|
| `takopi` | Start the bot (runs onboarding if first time) |
| `takopi --onboard` | Re-run onboarding wizard |
| `takopi init <alias>` | Register current directory as a project |
| `takopi chat-id` | Capture a chat ID |
| `takopi chat-id --project <alias>` | Set a project's chat ID |
| `takopi --debug` | Write debug logs to `debug.log` |
---
## 12. Tips
### Schedule tasks with Telegram
Telegram has a native message scheduling feature that works seamlessly with takopi. Long-press the send button and choose "Schedule Message" to run tasks at a specific time. You can also set up recurring schedules (daily, weekly, etc.) for automated workflows.
---
## 13. Troubleshooting
If something isn't working, rerun with `takopi --debug` and check `debug.log`
for errors. Include it when reporting issues.
+99 -27
View File
@@ -34,6 +34,25 @@ markdown_extensions:
plugins: plugins:
- search - search
- redirects:
redirect_maps:
architecture.md: explanation/architecture.md
specification.md: reference/specification.md
public-api.md: reference/plugin-api.md
transports/telegram.md: reference/transports/telegram.md
adding-a-runner.md: how-to/add-a-runner.md
projects.md: reference/context-resolution.md
runner/claude/claude-runner.md: reference/runners/claude/runner.md
runner/claude/claude-stream-json-cheatsheet.md: reference/runners/claude/stream-json-cheatsheet.md
runner/claude/claude-takopi-events.md: reference/runners/claude/takopi-events.md
runner/codex/exec-json-cheatsheet.md: reference/runners/codex/exec-json-cheatsheet.md
runner/codex/codex-takopi-events.md: reference/runners/codex/takopi-events.md
runner/opencode/opencode-runner.md: reference/runners/opencode/runner.md
runner/opencode/opencode-stream-json-cheatsheet.md: reference/runners/opencode/stream-json-cheatsheet.md
runner/opencode/opencode-takopi-events.md: reference/runners/opencode/takopi-events.md
runner/pi/pi-runner.md: reference/runners/pi/runner.md
runner/pi/pi-stream-json-cheatsheet.md: reference/runners/pi/stream-json-cheatsheet.md
runner/pi/pi-takopi-events.md: reference/runners/pi/takopi-events.md
- mkdocstrings: - mkdocstrings:
handlers: handlers:
python: python:
@@ -42,36 +61,89 @@ plugins:
show_source: false show_source: false
- llmstxt: - llmstxt:
markdown_description: | markdown_description: |
Takopi is a Telegram bridge for coding agents. Use this index to find the most relevant docs pages. Takopi is a Telegram bridge for coding agents.
For LLM ingestion, prefer the linked `.md` pages. For LLM ingestion, prefer the linked `.md` pages (Diátaxis: Tutorials / How-to / Reference / Explanation).
full_output: llms-full.txt full_output: llms-full.txt
sections: sections:
Getting started: Start here:
- index.md: Overview + quick start - index.md: Overview + doc map
- user-guide.md: How to use Takopi end-to-end - tutorials/install-and-onboard.md: Install Takopi + onboarding
- projects.md: Project aliases + worktrees - tutorials/first-run.md: First run
Plugin development: - how-to/projects.md: Projects (routing)
- plugins.md: Plugin system overview - how-to/worktrees.md: Worktrees (@branch)
- public-api.md: Stable plugin API surface - how-to/topics.md: Telegram topics
- adding-a-runner.md: Engine runner integration - how-to/file-transfer.md: File transfer
Internals: Reference:
- architecture.md: Core architecture - reference/commands-and-directives.md: Commands, directives, and CLI flags
- specification.md: Behavior spec - reference/config.md: Config schema and defaults
- developing.md: Developer guide - reference/specification.md: Normative behavior spec
- reference/plugin-api.md: Stable plugin API surface
- reference/context-resolution.md: Directive parsing + worktree rules
- reference/transports/telegram.md: Telegram transport details
Contributors & agents:
- how-to/dev-setup.md: Dev setup + checks
- explanation/module-map.md: Module responsibilities
- reference/env-vars.md: Environment variables
- reference/agents/invariants.md: Critical invariants
- reference/agents/repo-map.md: Codebase entrypoints
Optional: Optional:
- transports/telegram.md: Telegram transport details - reference/runners/index.md: Runner docs index
- reference/runners/claude/stream-json-cheatsheet.md: Claude stream-json notes
- reference/runners/codex/exec-json-cheatsheet.md: Codex exec --json notes
nav: nav:
- Home: index.md - Home: index.md
- User guide: user-guide.md - Tutorials:
- Projects: projects.md - Overview: tutorials/index.md
- Plugins: - Install & onboard: tutorials/install-and-onboard.md
- Plugin system: plugins.md - First run: tutorials/first-run.md
- Public API: public-api.md - How-to:
- Adding a runner: adding-a-runner.md - Overview: how-to/index.md
- Internals: - Switch engines: how-to/switch-engines.md
- Architecture: architecture.md - Projects: how-to/projects.md
- Specification: specification.md - Worktrees: how-to/worktrees.md
- Developing: developing.md - Route by chat: how-to/route-by-chat.md
- Transports: - Topics: how-to/topics.md
- Telegram: transports/telegram.md - Voice notes: how-to/voice-notes.md
- File transfer: how-to/file-transfer.md
- Schedule tasks: how-to/schedule-tasks.md
- Troubleshooting: how-to/troubleshooting.md
- Write a plugin: how-to/write-a-plugin.md
- Add a runner: how-to/add-a-runner.md
- Dev setup: how-to/dev-setup.md
- Reference:
- Overview: reference/index.md
- Commands & directives: reference/commands-and-directives.md
- Configuration: reference/config.md
- Environment variables: reference/env-vars.md
- Specification: reference/specification.md
- Plugin API: reference/plugin-api.md
- Context resolution: reference/context-resolution.md
- Telegram transport: reference/transports/telegram.md
- Runners:
- Overview: reference/runners/index.md
- Claude:
- Runner: reference/runners/claude/runner.md
- Stream JSON cheatsheet: reference/runners/claude/stream-json-cheatsheet.md
- Takopi events: reference/runners/claude/takopi-events.md
- Codex:
- Exec JSON cheatsheet: reference/runners/codex/exec-json-cheatsheet.md
- Takopi events: reference/runners/codex/takopi-events.md
- OpenCode:
- Runner: reference/runners/opencode/runner.md
- Stream JSON cheatsheet: reference/runners/opencode/stream-json-cheatsheet.md
- Takopi events: reference/runners/opencode/takopi-events.md
- Pi:
- Runner: reference/runners/pi/runner.md
- Stream JSON cheatsheet: reference/runners/pi/stream-json-cheatsheet.md
- Takopi events: reference/runners/pi/takopi-events.md
- For agents:
- Agent entrypoint: reference/agents/index.md
- Repo map: reference/agents/repo-map.md
- Invariants: reference/agents/invariants.md
- Explanation:
- Overview: explanation/index.md
- Architecture: explanation/architecture.md
- Routing & sessions: explanation/routing-and-sessions.md
- Plugin system: explanation/plugin-system.md
- Module map: explanation/module-map.md
+1
View File
@@ -63,6 +63,7 @@ docs = [
"mkdocs>=1.6", "mkdocs>=1.6",
"mkdocs-llmstxt>=0.5.0", "mkdocs-llmstxt>=0.5.0",
"mkdocs-material>=9.7", "mkdocs-material>=9.7",
"mkdocs-redirects>=1.2.2",
"mkdocstrings[python]>=0.29", "mkdocstrings[python]>=0.29",
] ]
+3 -3
View File
@@ -46,17 +46,17 @@ register a project with `takopi init happy-gadgets`, then target it from anywher
mention a branch to run an agent in a dedicated worktree `/happy-gadgets @feat/memory-box freeze artifacts forever`. mention a branch to run an agent in a dedicated worktree `/happy-gadgets @feat/memory-box freeze artifacts forever`.
see [`docs/user-guide.md`](docs/user-guide.md) for configuration, worktrees, topics, file transfer, and more. see [`docs/index.md`](docs/index.md) (or [`docs/user-guide.md`](docs/user-guide.md)) for configuration, worktrees, topics, file transfer, and more.
## plugins ## plugins
takopi supports entrypoint-based plugins for engines, transports, and commands. takopi supports entrypoint-based plugins for engines, transports, and commands.
see [`docs/plugins.md`](docs/plugins.md) and [`docs/public-api.md`](docs/public-api.md). see [`docs/how-to/write-a-plugin.md`](docs/how-to/write-a-plugin.md) and [`docs/reference/plugin-api.md`](docs/reference/plugin-api.md).
## development ## development
see [`docs/specification.md`](docs/specification.md) and [`docs/developing.md`](docs/developing.md). see [`docs/reference/specification.md`](docs/reference/specification.md) and [`docs/developing.md`](docs/developing.md).
## community ## community
Generated
+14
View File
@@ -544,6 +544,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
] ]
[[package]]
name = "mkdocs-redirects"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/a8/6d44a6cf07e969c7420cb36ab287b0669da636a2044de38a7d2208d5a758/mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095", size = 7162, upload-time = "2024-11-07T14:57:21.109Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c4/ec/38443b1f2a3821bbcb24e46cd8ba979154417794d54baf949fefde1c2146/mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5", size = 6142, upload-time = "2024-11-07T14:57:19.143Z" },
]
[[package]] [[package]]
name = "mkdocstrings" name = "mkdocstrings"
version = "1.0.0" version = "1.0.0"
@@ -1029,6 +1041,7 @@ docs = [
{ name = "mkdocs" }, { name = "mkdocs" },
{ name = "mkdocs-llmstxt" }, { name = "mkdocs-llmstxt" },
{ name = "mkdocs-material" }, { name = "mkdocs-material" },
{ name = "mkdocs-redirects" },
{ name = "mkdocstrings", extra = ["python"] }, { name = "mkdocstrings", extra = ["python"] },
] ]
@@ -1062,6 +1075,7 @@ docs = [
{ name = "mkdocs", specifier = ">=1.6" }, { name = "mkdocs", specifier = ">=1.6" },
{ name = "mkdocs-llmstxt", specifier = ">=0.5.0" }, { name = "mkdocs-llmstxt", specifier = ">=0.5.0" },
{ name = "mkdocs-material", specifier = ">=9.7" }, { name = "mkdocs-material", specifier = ">=9.7" },
{ name = "mkdocs-redirects", specifier = ">=1.2.2" },
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.29" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.29" },
] ]