From 26df1c8ac0eddc906449bba93efecb7f7ff4e08e Mon Sep 17 00:00:00 2001
From: banteg <4562643+banteg@users.noreply.github.com>
Date: Thu, 8 Jan 2026 00:21:46 +0400
Subject: [PATCH] docs: add architecture and lifecycle diagrams
---
docs/architecture.md | 342 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 342 insertions(+)
create mode 100644 docs/architecture.md
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..9f6fca8
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,342 @@
+# Takopi Architecture & Lifecycle
+
+## Layer Diagram
+
+```mermaid
+flowchart TB
+ subgraph CLI["CLI Layer"]
+ cli[cli.py]
+ cli_desc["Entry point, config loading, lock file"]
+ end
+
+ subgraph Orchestration["Orchestration Layer"]
+ router[AutoRouter
router.py]
+ scheduler[ThreadScheduler
scheduler.py]
+ projects[ProjectsConfig
config.py]
+ end
+
+ subgraph Bridge["Bridge Layer"]
+ tg_bridge[telegram/bridge.py
run_main_loop]
+ runner_bridge[runner_bridge.py
handle_message]
+ end
+
+ subgraph Runner["Runner Layer"]
+ runner_proto[Runner Protocol
runner.py]
+ runners[runners/
claude, codex, opencode, pi]
+ schemas[schemas/
JSONL decoders]
+ end
+
+ subgraph Transport["Transport Layer"]
+ transport[Transport Protocol]
+ presenter[Presenter Protocol]
+ tg_client[telegram/client.py]
+ tg_render[telegram/render.py]
+ markdown[markdown.py]
+ end
+
+ subgraph External["External"]
+ agent_clis[Agent CLIs
claude, codex, pi]
+ telegram_api[Telegram Bot API]
+ end
+
+ cli --> router
+ cli --> scheduler
+ cli --> projects
+ router --> tg_bridge
+ scheduler --> tg_bridge
+ tg_bridge --> runner_bridge
+ runner_bridge --> runner_proto
+ runner_proto --> runners
+ runners --> schemas
+ runners --> agent_clis
+ runner_bridge --> transport
+ runner_bridge --> presenter
+ transport --> tg_client
+ presenter --> tg_render
+ presenter --> markdown
+ tg_client --> telegram_api
+```
+
+---
+
+## Domain Model
+
+```mermaid
+classDiagram
+ class ResumeToken {
+ +engine: EngineId
+ +value: str
+ }
+
+ class Action {
+ +id: str
+ +kind: ActionKind
+ +title: str
+ +detail: dict
+ }
+
+ class StartedEvent {
+ +type: "started"
+ +engine: EngineId
+ +resume: ResumeToken
+ +title: str?
+ }
+
+ class ActionEvent {
+ +type: "action"
+ +engine: EngineId
+ +action: Action
+ +phase: started|updated|completed
+ +ok: bool?
+ +message: str?
+ }
+
+ class CompletedEvent {
+ +type: "completed"
+ +engine: EngineId
+ +ok: bool
+ +answer: str
+ +resume: ResumeToken?
+ +usage: dict?
+ }
+
+ StartedEvent --> ResumeToken
+ ActionEvent --> Action
+ CompletedEvent --> ResumeToken
+
+ note for Action "ActionKind: command | tool | file_change |\nweb_search | subagent | note | turn | warning"
+```
+
+---
+
+## Message Lifecycle
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Telegram
+ participant Bridge as telegram/bridge.py
+ participant Scheduler as ThreadScheduler
+ participant RunnerBridge as runner_bridge.py
+ participant Runner
+ participant AgentCLI as Agent CLI
+
+ User->>Telegram: Send message
+ Telegram->>Bridge: poll_incoming()
+
+ Bridge->>Bridge: Parse directives
(/engine, /project, @branch)
+ Bridge->>Bridge: Extract resume token
from reply
+ Bridge->>Bridge: Resolve worktree
(if @branch)
+
+ Bridge->>Scheduler: enqueue(ThreadJob)
+ Scheduler->>RunnerBridge: handle_message()
+
+ RunnerBridge->>Telegram: Send progress message
+ RunnerBridge->>Runner: run(prompt, resume)
+
+ Runner->>AgentCLI: Spawn subprocess
+
+ loop JSONL Stream
+ AgentCLI-->>Runner: JSONL event
+ Runner-->>RunnerBridge: TakopiEvent
+ RunnerBridge->>Telegram: Edit progress message
+ end
+
+ AgentCLI-->>Runner: Completed
+ Runner-->>RunnerBridge: CompletedEvent
+ RunnerBridge->>Telegram: Send final answer
+ RunnerBridge->>Telegram: Delete progress message
+```
+
+---
+
+## Runner Execution Flow
+
+```mermaid
+flowchart TD
+ A[runner.run\nprompt, resume_token] --> B[Acquire Session Lock
SessionLockMixin]
+
+ B --> C[Build Command]
+
+ C --> D{Engine?}
+ D -->|Claude| D1["claude --print --output-format stream-json
[--resume id] prompt"]
+ D -->|Codex| D2["codex exec --output jsonl
[--reconnect id] prompt"]
+ D -->|Pi| D3["pi --output jsonl
[--session id] prompt"]
+ D -->|OpenCode| D4["opencode --output jsonl
[--session id] prompt"]
+
+ D1 --> E[Spawn Subprocess
anyio.open_process]
+ D2 --> E
+ D3 --> E
+ D4 --> E
+
+ E --> F[Stream JSONL from stdout]
+
+ F --> G[Decode with msgspec]
+ G --> H[Translate to TakopiEvent]
+ H --> I[yield event]
+ I --> F
+
+ F -->|EOF| J[Return]
+```
+
+---
+
+## Resume Token Flow
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Bridge
+ participant Runner
+ participant CLI as Agent CLI
+
+ Note over User,CLI: New Conversation
+ User->>Bridge: "fix the bug"
+ Bridge->>Runner: run(prompt, None)
+ Runner->>CLI: claude "fix the bug"
+ CLI-->>Runner: StartedEvent(resume=abc123)
+ Runner-->>Bridge: Stream events
+ Bridge->>User: Final message with:
claude --resume abc123
ctx: project @ branch
+
+ Note over User,CLI: Resume Conversation
+ User->>Bridge: Reply: "now add tests"
+ Bridge->>Bridge: extract_resume(reply_text)
→ ResumeToken(claude, abc123)
+ Bridge->>Bridge: parse_ctx_line()
→ project, branch
+ Bridge->>Runner: run("now add tests", token)
+ Runner->>CLI: claude --resume abc123 "now add tests"
+ CLI-->>Runner: Continues session
+ Runner-->>Bridge: Stream events
+ Bridge->>User: Final message
+```
+
+---
+
+## Component Dependencies
+
+```mermaid
+flowchart TD
+ cli[cli.py] --> config[config.py]
+ cli --> engines[engines.py]
+ cli --> lockfile[lockfile.py]
+
+ engines --> backends[backends.py]
+
+ backends --> runners[runners/]
+ backends --> runner[runner.py]
+
+ subgraph runners[runners/]
+ claude[claude.py]
+ codex[codex.py]
+ opencode[opencode.py]
+ pi[pi.py]
+ end
+
+ subgraph schemas[schemas/]
+ claude_s[claude.py]
+ codex_s[codex.py]
+ opencode_s[opencode.py]
+ pi_s[pi.py]
+ end
+
+ claude --> claude_s
+ codex --> codex_s
+ opencode --> opencode_s
+ pi --> pi_s
+
+ cli --> router[router.py]
+ router --> tg_bridge[telegram/bridge.py]
+
+ runner --> runner_bridge[runner_bridge.py]
+ runner_bridge --> tg_bridge
+
+ tg_bridge --> client[telegram/client.py]
+ tg_bridge --> render[telegram/render.py]
+
+ client --> transport[transport.py]
+
+ runner_bridge --> progress[progress.py]
+ runner_bridge --> events[events.py]
+
+ render --> presenter[presenter.py]
+ presenter --> markdown[markdown.py]
+```
+
+---
+
+## Configuration Structure
+
+```mermaid
+flowchart LR
+ subgraph Config["~/.takopi/"]
+ toml[takopi.toml]
+ lock[.takopi.lock]
+ end
+
+ subgraph toml_contents["takopi.toml"]
+ direction TB
+ global["bot_token
chat_id
default_engine"]
+ claude_cfg["[claude]
model = ..."]
+ codex_cfg["[codex]
model = ..."]
+ projects_cfg["[projects.alias]
path = ...
worktrees_dir = ...
default_engine = ..."]
+ default_proj["[projects]
default = ..."]
+ end
+
+ toml --> toml_contents
+```
+
+---
+
+## Thread Scheduling
+
+```mermaid
+flowchart TD
+ subgraph Incoming[Incoming Messages]
+ m1[Message 1
new thread]
+ m2[Message 2
reply to thread A]
+ m3[Message 3
reply to thread A]
+ m4[Message 4
new thread]
+ end
+
+ subgraph Scheduler[ThreadScheduler]
+ direction TB
+ q1[Thread A Queue]
+ q2[Thread B Queue]
+ q3[Thread C Queue]
+ end
+
+ subgraph Workers[Worker Tasks]
+ w1[Worker A]
+ w2[Worker B]
+ w3[Worker C]
+ end
+
+ m1 --> q2
+ m2 --> q1
+ m3 --> q1
+ m4 --> q3
+
+ q1 --> w1
+ q2 --> w2
+ q3 --> w3
+
+ w1 --> runner1[Runner.run]
+ w2 --> runner2[Runner.run]
+ w3 --> runner3[Runner.run]
+
+ note1[Jobs in same thread
execute sequentially]
+ note2[Different threads
execute in parallel]
+```
+
+---
+
+## Summary
+
+| Layer | Components | Responsibility |
+|-------|------------|----------------|
+| **CLI** | `cli.py` | Entry point, config, lock |
+| **Orchestration** | `router.py`, `scheduler.py`, `config.py` | Engine selection, job queuing, project config |
+| **Bridge** | `telegram/bridge.py`, `runner_bridge.py` | Message handling, execution coordination |
+| **Runner** | `runner.py`, `runners/*.py`, `schemas/*.py` | Agent CLI subprocess, JSONL parsing, event translation |
+| **Transport** | `transport.py`, `presenter.py`, `telegram/client.py` | Telegram API, message rendering |
+| **Domain** | `model.py`, `progress.py`, `events.py` | Event types, action tracking |
+| **Utils** | `worktrees.py`, `utils/*.py`, `markdown.py` | Git worktrees, formatting, paths |