01 — Current vs Target
📸
État actuel
2026-03-19
Inbound Adapters
TelegramAdapter (aiogram v3) · DiscordAdapter (discord.py v2) · normalize() → InboundMessage
InboundMessage
frozen dataclass · typed · platform-agnostic ✅
GuardChain · Router · SessionManager · Pool
Auth, routing, smart routing, mémoire 5 niveaux ✅
StreamProcessor
N'existe pas — tool_use ignorés, aucune agrégation
LlmProvider
Protocol ✅ · complete() → LlmResult bufferisé ✅
❌ Pas de stream() → tool_use jamais exposés
LLM Adapters
AnthropicSdkDriver (buffered) · ClaudeCliDriver (subprocess) ✅
Outbound
send_streaming(AsyncIterator[str]) — text seulement
❌ Pas de RenderEvent · pas de ToolSummary
Outbound Adapters
telegram_outbound.py · discord_outbound.py ✅
🎯
Architecture cible
Hexagonal
Inbound Adapters
Telegram · Discord · Signal · HTTP · CLI · normalize()
Inbound Port — InboundMessage
frozen · typed · platform-agnostic
GuardChain · Router · SessionManager
Domain core — aucune dépendance framework
★ StreamProcessor
LlmEvent → RenderEvent · seuils config · channel-agnostic · testable isolation
LLM Port
complete() → LlmResult · stream() → AsyncIter[LlmEvent]
LLM Adapters
AnthropicSdkAdapter · ClaudeCliAdapter · OllamaAdapter
Outbound Port + Dispatcher
AsyncIterator[RenderEvent] · throttle · route par platform
Outbound Adapters
Telegram editMessage · Discord embed · CLI print · Signal
Existe ✅
Partiel / incomplet
Manquant ❌
Cible
Pièce centrale manquante
02 — 6 Gaps identifiés
G1
LlmProvider.stream() manquant
Le protocol n'a que complete() qui bufferise tout. Les tool_use events ne sont jamais exposés à l'appelant.
llm/base.py llm/drivers/sdk.py llm/drivers/cli.py
M
G2
LlmEvent types manquants
Aucun type TextLlmEvent, ToolUseLlmEvent, ResultLlmEvent. Requis avant G1 et G3.
llm/events.py ✦ nouveau
S
G3
★ StreamProcessor manquant
Aucune couche domain entre LLM et outbound. Logique de seuils, groupage et throttle absente. Bloque G4 et G5.
core/stream_processor.py ✦ nouveau
M-Lbloquant
G4
RenderEvent types manquants
Aucun TextRenderEvent ni ToolSummaryRenderEvent. Requis avant d'adapter les outbound adapters.
core/render_events.py ✦ nouveau
S
G5
send_streaming() prend str
ChannelAdapter.send_streaming(AsyncIterator[str]) doit devenir AsyncIterator[RenderEvent]. Telegram et Discord à mettre à jour.
core/hub_protocol.py adapters/telegram_outbound.py adapters/discord_outbound.py
M
G6
Config tool_display absente
Les seuils names_threshold=3, group_threshold=3, throttle_ms et les flags show.* n'ont nulle part où vivre.
config.toml.example bootstrap/config.py
S
03 — Stratégie en 5 phases
Sprint 1
Sprint 2
Sprint 3
Sprint 4
Fichiers
Phase 1Types · S
G2 + G4 — types
llm/events.py core/render_events.py
Phase 2LLM drivers · M
G1 — stream()
llm/base.py llm/drivers/sdk.py llm/drivers/cli.py
P1+P2 parallèles
Phase 3StreamProcessor · M-L
G3 — StreamProcessor
core/stream_processor.py
Phase 4Outbound adapters · M
G5 — RenderEvent
hub_protocol.py telegram_outbound.py discord_outbound.py
Phase 5Config + wiring · S
G6 — config
config.toml.example bootstrap/config.py

P1+P2 parallèles (aucune dépendance) · P3 bloqué par P1+P2 · P4 bloqué par P3 · P5 indépendante (peut partir en parallèle)

04 — Zoom StreamProcessor
LlmEvent (input)
💬
TextLlmEventtext: str
🔧
ToolUseLlmEventtool_name · tool_id · input: dict
ResultLlmEventis_error · duration_ms · cost_usd
★ StreamProcessor
État interne files: dict[path → FileEditSummary]
bash_commands: list[str]
silent_counts: SilentCounts
pending_text: str | None
ToolDisplayConfig
names_threshold=3
group_threshold=3
throttle_ms=2000
RenderEvent (output)
📝
TextRenderEventtext · is_final: bool
📊
ToolSummaryRenderEventfiles · bash_commands · silent_counts · is_complete
Règles de transition
TextLlmEvent TextRenderEvent(is_final=False) sauf si suivie d'un tool_use dans le même turn (intro courte) → fusionner dans le ToolSummaryRenderEvent
ToolUseLlmEvent Edit/Write → accumule dans files[path]. Si len(edits) > names_threshold → bascule en mode count. Si len(files) >= group_threshold → groupage. Émet ToolSummaryRenderEvent
ToolUseLlmEvent Bash → si show.bash=true : ajoute command tronquée à bash_max_len. Émet ToolSummaryRenderEvent
ToolUseLlmEvent Read/Grep/Glob silent_counts++ (si show.read=false) · comptés en footer uniquement
ResultLlmEvent ToolSummaryRenderEvent(is_complete=True) puis TextRenderEvent(is_final=True) avec le texte de conclusion