Skip to content

feat: Telegram adapter — session lifecycle, memory compaction & crash recovery#86

Open
henrieopenclaw wants to merge 41 commits intoopenabdev:mainfrom
henrieopenclaw:feat/telegram-topics-idle-cleanup
Open

feat: Telegram adapter — session lifecycle, memory compaction & crash recovery#86
henrieopenclaw wants to merge 41 commits intoopenabdev:mainfrom
henrieopenclaw:feat/telegram-topics-idle-cleanup

Conversation

@henrieopenclaw
Copy link
Copy Markdown

@henrieopenclaw henrieopenclaw commented Apr 6, 2026

Supersedes #60. Full Telegram adapter replacing Discord, plus team/personal chat modes.

Architecture

graph LR
    U(["👤 Telegram User"]) <-->|Bot API| AB["agent-broker (Rust)"]
    AB <-->|"ACP stdio JSON-RPC"| CLI["kiro-cli / claude / codex / gemini"]
Loading

Chat Modes

Personal (default) Team
Any message in #general Auto-creates topic, replies Silent buffer 👀
@mention in #general Auto-creates topic, replies Replies in-place
!kiro in #general Auto-creates topic Creates topic (restricted to topic_creator_id)
Plain message in topic Replies directly Silent buffer 👀
@mention in topic Replies directly Replies directly

Session Lifecycle

flowchart TD
    M([User message]) --> GC[get_or_create]
    GC -->|alive| USE[use existing session]
    GC -->|dead/new| SP[spawn CLI]
    SP --> SN[session/new or session/load]
    SN --> PC{pending_context?}
    PC -->|yes| PP[prepend summary]
    PC -->|no| ST
    PP --> ST[stream_prompt]
    ST --> PD[prompt_done]
    PD --> CI[cleanup_idle every 15min]
    CI --> IT{idle > TTL?}
    IT -->|yes| CO[compact → evict → notify]
    CO --> SS[summary stored, injected on resume]
Loading

Memory Management

Idle Eviction

flowchart TD
    A([User idle for 2 hours]) --> B[cleanup_idle detects idle > TTL]
    B --> C[Send compaction prompt to live kiro process]
    C --> D[Receive summary → store in summaries]
    D --> E[Kill kiro process, notify user ⏱]
    E --> F([User sends next message])
    F --> G[Spawn new kiro → inject summary]
    G --> H[Kiro remembers previous conversation ✅]
Loading

Crash Recovery

flowchart TD
    A([User sends message]) --> B[record_user_message → VecDeque max 20]
    B --> C{... kiro crashes ...}
    C --> D[cleanup_idle detects !alive]
    D --> E[Attempt compaction → fails]
    E --> F[Fallback: VecDeque → Recent messages before crash]
    F --> G[Store in summaries]
    G --> H([User sends next message])
    H --> I[Spawn new kiro → inject crash context]
    I --> J[Kiro recalls recent context ✅ partial memory]
Loading

Key changes

  • Telegram adapter replacing Discord (teloxide)
  • Forum topic threading — one topic per conversation
  • personal vs team mode via config.toml
  • Memory compaction — summarizes session before eviction, injects on resume
  • Crash recovery — stores last 20 user messages as fallback context when kiro crashes before compaction
  • Deadlock fixsilent_prompt/rename_topic no longer hold write lock during stream
  • Security fix (relates to [Security] Empty allowed_channels should not default to allowing all channels #91) — bot refuses to start if allowed_users is empty
  • Resilient streaming — 200ms drain window, 30s alive check, 30min hard timeout
  • Session recovery — on crash/timeout: partial summary stored, session evicted so next message resumes with context
  • Mermaid diagrams replacing ASCII throughout README

Known limitations (marked as WORKAROUND in code)

  • stream_prompt still holds the write lock for its duration — a hung task blocks other messages until timeout fires. Proper fix requires lock-free streaming (same pattern as silent_prompt).
  • Drain window (200ms) and hard timeout (30min) are hardcoded — should be configurable.
  • These are workarounds pending better ACP lifecycle signals (heartbeat, cancellation, sequence numbers).

- Forum supergroups: create a new topic per conversation (session key = chat_id:thread_id)
- Messages already in a topic: reuse the existing topic/session
- Plain DMs/groups: fall back to reply chains (reply_parameters)
- Session isolation: each topic gets its own ACP session
…tion

- Fix compaction drain to use classify_notification() instead of
  msg.result.content[0].text (wrong path; ACP text is in params.update.content.text)
- Fix deadlock in cleanup_idle: release connections write lock before
  awaiting compaction response
- Fix deadlock in get_or_create: replace inline summary injection round-trip
  with pending_context field on AcpConnection; session_prompt prepends it
  to the first real user prompt — no extra kiro-cli call, no lock contention

Alice test passed: kiro correctly recalls user identity and preferences
after session eviction and cold resume.
…from tracking

- README fully rewritten for Telegram (replaces Discord content)
- Added forum supergroup setup guide and Topics enable instructions
- TTL constants documented with src/telegram.rs reference
- Memory compaction design documented with ASCII flow
- PR description updated with compaction design and correct status
- handoff.md added to .gitignore (private dev notes)
- docs/session-management.md and docs/pr-telegram-topics-idle-cleanup.md included
…ntion replies

- Add ChatMode enum (personal/team) to config, default personal
- personal: original behavior — any message creates topic, always reply
- team: !kiro creates topic (topic_creator_id gated), Kiro listens
  silently in topics (👀 reaction), replies only on @mention
- Add topic_creator_id config field to restrict !kiro to one user
- Fetch bot username on startup for @mention detection
- Prefix shared-topic messages with [Name]: for Kiro context
- Error feedback on topic creation failure and pool exhaustion
- Document both modes in config.toml.example
…ck fix

- silent_prompt and rename_topic no longer hold the connection write lock
  while draining the stream (fixes deadlock when @mention arrives during
  a silent prompt)
- plain messages in #general now buffer silently (👀) instead of being dropped
- @mention in #general replies in-place without creating a topic
- get_or_create_thread returns a per-user general session key for non-!kiro
  messages instead of bailing, enabling silent + mention flows
- update config.toml.example and README with full team/personal mode docs
@henrieopenclaw henrieopenclaw changed the title feat: team mode — silent buffering, @mention replies, deadlock fix + Mermaid diagrams feat: Telegram adapter — team/personal modes, session lifecycle, memory compaction Apr 6, 2026
…timeout

- 200ms drain window after end_turn to capture late-arriving text chunks
- tokio::select! with 30s alive check — breaks if agent process dies mid-prompt
- 30min hard timeout safety net for hung tool calls
- fallback message shows tool summary if text response is empty
  (fixes the morning hang where kiro got stuck and froze all sessions)
chaodu-agent added a commit to chaodu-agent/openab that referenced this pull request Apr 7, 2026
Defines ChatAdapter trait, AdapterRouter, and platform-specific
implementations for Discord, Telegram, and Slack.

Covers: trait design, config format, message size handling,
reaction mapping, security, 5 implementation phases.

Relates to openabdev#86 openabdev#93
… and resume

- stream_prompt now returns timed_out/agent_died flags after releasing the lock
- on abnormal exit: partial summary (text_buf + tool_lines) stored in pool.summaries
- session evicted via remove_session so next message spawns fresh with context injected
- pool.store_partial_summary() added for external callers
- works for both personal and team mode (session-key based, mode-agnostic)
- Add crash_history_size config (default 20) to [pool]
- SessionPool records last N user messages per session key
- cleanup_idle falls back to raw history when compaction fails (crashed session)
- History injected as [Recent messages before crash] context on next resume
- remove_session/shutdown clean up history
@henrieopenclaw henrieopenclaw changed the title feat: Telegram adapter — team/personal modes, session lifecycle, memory compaction feat: Telegram adapter — session lifecycle, memory compaction & crash recovery Apr 7, 2026
dogzzdogzz pushed a commit to dogzzdogzz/openab that referenced this pull request Apr 10, 2026
Introduces a platform-agnostic adapter layer (ChatAdapter trait + AdapterRouter)
that decouples session management, streaming, and reactions from any specific
chat platform. Refactors Discord support to implement this trait and adds a
new Slack adapter using Socket Mode (WebSocket) with auto-reconnect.

- Extract ChatAdapter trait with send/edit/thread/react methods + message_limit()
- Extract AdapterRouter with shared session routing, edit-streaming, and reactions
- Decouple StatusReactionController from serenity types (uses Arc<dyn ChatAdapter>)
- Implement DiscordAdapter (ChatAdapter for Discord via serenity)
- Implement SlackAdapter (ChatAdapter for Slack via reqwest + tokio-tungstenite)
- Add [slack] config section (bot_token, app_token, allowed_channels, allowed_users)
- Support running Discord + Slack simultaneously in one process
- Session keys namespaced by platform (discord:xxx, slack:xxx)
- Unicode emoji → Slack short name mapping for reactions

Relates to openabdev#93, openabdev#94, openabdev#86

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… passthrough

- Add ContentBlock enum (Text/Image) matching upstream openabdev/openab openabdev#158
  interface — compatible with ACP wire format, re-exported from crate::acp
- Fix session/new and session/load passing mcpServers: [] (empty) — now
  reads from [agent.mcp_servers] config, enabling markitdown and other MCP
  tools to be available in kiro sessions
- Add McpServerConfig to config.rs with name/command/args/env fields
- Add mcp_servers_json() helper on SessionPool
- Update config.toml.example with MCP server documentation
…image path

- Add src/media.rs with MediaInput enum and resolve() transform layer
- Image: resize to max 1200px (Lanczos3), re-encode JPEG, vision model gate
- GIF passes through unchanged; resize failure falls back to original bytes
- session_prompt() now takes Vec<ContentBlock> instead of Option<(Vec<u8>, String)>
- Model gate (claude/auto only) applied inside stream_prompt via conn.current_model_id
- Add current_model_id field to AcpConnection, populated from session/new response
- Add image crate dependency (jpeg/png/gif/webp, no default features)
- 22/22 tests passing
…sion model warning

- Fix: image-only messages (no caption) were dropped by empty prompt guard
- Add [image] placeholder prompt when caption is empty so kiro knows media arrived
- Warn user with model name when image dropped due to non-vision model
- Image in #General (team mode) now creates topic + replies (not silent)
…ll-out

- Add MediaInput::Document variant to media.rs
- resolve() is now async — markitdown runs as subprocess via tokio::process::Command
- Writes to tempfile (original filename preserved for format detection)
- 60s timeout, 20MB size limit, fallback ContentBlock::Text on any error
- Documents in #General create a topic (same as images)
- [file] placeholder prompt when document sent with no caption
- Add tempfile dependency
- 23/23 tests passing
- rename_topic now receives effective_prompt ([file]/[image] fallback)
  instead of empty prompt string when no caption provided
- New topics from documents get 📄 placeholder, images keep 📷
markitdown CLI required for document pipeline (MediaInput::Document).
Runs as on-demand subprocess — no persistent service, no idle cost.
dogzzdogzz pushed a commit to dogzzdogzz/openab that referenced this pull request Apr 12, 2026
Introduces a platform-agnostic adapter layer (ChatAdapter trait + AdapterRouter)
that decouples session management, streaming, and reactions from any specific
chat platform. Refactors Discord support to implement this trait and adds a
new Slack adapter using Socket Mode (WebSocket) with auto-reconnect.

- Extract ChatAdapter trait with send/edit/thread/react methods + message_limit()
- Extract AdapterRouter with shared session routing, edit-streaming, and reactions
- Decouple StatusReactionController from serenity types (uses Arc<dyn ChatAdapter>)
- Implement DiscordAdapter (ChatAdapter for Discord via serenity)
- Implement SlackAdapter (ChatAdapter for Slack via reqwest + tokio-tungstenite)
- Add [slack] config section (bot_token, app_token, allowed_channels, allowed_users)
- Support running Discord + Slack simultaneously in one process
- Session keys namespaced by platform (discord:xxx, slack:xxx)
- Unicode emoji → Slack short name mapping for reactions

Relates to openabdev#93, openabdev#94, openabdev#86

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dogzzdogzz pushed a commit to dogzzdogzz/openab that referenced this pull request Apr 12, 2026
Introduces a platform-agnostic adapter layer (ChatAdapter trait + AdapterRouter)
that decouples session management, streaming, and reactions from any specific
chat platform. Refactors Discord support to implement this trait and adds a
new Slack adapter using Socket Mode (WebSocket) with auto-reconnect.

- Extract ChatAdapter trait with send/edit/thread/react methods + message_limit()
- Extract AdapterRouter with shared session routing, edit-streaming, and reactions
- Decouple StatusReactionController from serenity types (uses Arc<dyn ChatAdapter>)
- Implement DiscordAdapter (ChatAdapter for Discord via serenity)
- Implement SlackAdapter (ChatAdapter for Slack via reqwest + tokio-tungstenite)
- Add [slack] config section (bot_token, app_token, allowed_channels, allowed_users)
- Support running Discord + Slack simultaneously in one process
- Session keys namespaced by platform (discord:xxx, slack:xxx)
- Unicode emoji → Slack short name mapping for reactions

Relates to openabdev#93, openabdev#94, openabdev#86

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants