Logbot: Infrastructure plugins (Archive + Cron + Git) #3

Closed
opened 2026-02-20 08:41:33 +00:00 by nazim · 4 comments
Contributor

Summary

Three "mechanism, not policy" plugins that compose into a logbot — a cobot instance that logs channel messages without needing an LLM.

Plugins

Archive

  • Generic archive.write(channel, timestamp, data) interface
  • Formats: jsonl, markdown, csv. Rotation: daily, hourly, none
  • Per-channel overrides. Extension point: archive.on_write

Cron

  • Plugins register tasks: cron.register("name", "*/12 * * *", callback)
  • Cron expressions, intervals, one-shot. cron.on_tick heartbeat
  • Asyncio background loop concurrent with polling

Git

  • git.commit_and_push(paths, message) as reusable service
  • Called by cron or any other plugin

Composition

# Logbot = telegram + archive + cron + git, no LLM
archive:
  format: "jsonl"
cron:
  tasks:
    - name: "commit-archive"
      schedule: "0 */12 * * *"
      action: "git.commit_and_push"
git:
  auto_push: true

Other Scenarios

Use Case Plugins
Backup bot channel + cron + git
Media archiver telegram + archive (media mode)
Health monitor cron + tools + channel (alerts)
Git-backed memory memory + cron + git

Open Question

No-LLM mode: How should cobot run without an LLM provider? Options:

  • mode: passive in config
  • agent.respond becomes no-op when no LLM plugin registered

Depends on #2 (session observers)

## Summary Three "mechanism, not policy" plugins that compose into a logbot — a cobot instance that logs channel messages without needing an LLM. ## Plugins ### Archive - Generic `archive.write(channel, timestamp, data)` interface - Formats: jsonl, markdown, csv. Rotation: daily, hourly, none - Per-channel overrides. Extension point: `archive.on_write` ### Cron - Plugins register tasks: `cron.register("name", "*/12 * * *", callback)` - Cron expressions, intervals, one-shot. `cron.on_tick` heartbeat - Asyncio background loop concurrent with polling ### Git - `git.commit_and_push(paths, message)` as reusable service - Called by cron or any other plugin ## Composition ```yaml # Logbot = telegram + archive + cron + git, no LLM archive: format: "jsonl" cron: tasks: - name: "commit-archive" schedule: "0 */12 * * *" action: "git.commit_and_push" git: auto_push: true ``` ## Other Scenarios | Use Case | Plugins | |----------|---------| | Backup bot | channel + cron + git | | Media archiver | telegram + archive (media mode) | | Health monitor | cron + tools + channel (alerts) | | Git-backed memory | memory + cron + git | ## Open Question **No-LLM mode:** How should cobot run without an LLM provider? Options: - `mode: passive` in config - `agent.respond` becomes no-op when no LLM plugin registered Depends on #2 (session observers)
Collaborator

Additional Details from Design Discussion (2026-02-13)

Telegram Plugin Architecture

The telegram plugin exposes extension points for downstream plugins:

extension_points = [
    "telegram.on_message",   # New message received
    "telegram.on_edit",      # Message edited
    "telegram.on_delete",    # Message deleted  
    "telegram.on_media",     # Media downloaded (with local path)
]

Full Data Flow

Telegram Group Chat
        │
        ▼
┌──────────────────┐
│  telegram plugin │ ← Receives all messages
└────────┬─────────┘
         │ hook: telegram.on_message
         ▼
┌──────────────────┐
│  archive plugin  │ ← Formats, stores locally (jsonl/markdown)
└────────┬─────────┘
         │ extension point: archive.on_write
         ▼
┌──────────────────┐
│  git-sync plugin │ ← Commits to repo on schedule
└──────────────────┘

Message Context (passed to extension points)

{
    "group_id": -100123456789,
    "group_name": "dev-chat",
    "message_id": 42,
    "from_user": {"id": 123, "username": "k9ert"},
    "timestamp": "2026-02-13T11:33:00Z",
    "text": "Hello world",
    "reply_to": 41,  # optional
    "media": {       # optional
        "type": "photo",
        "file_id": "...",
        "local_path": "./media/2026-02-13/photo_42.jpg"
    }
}

External Plugin: cobot-telegram

Telegram plugin lives in separate repo to keep core minimal:

  • Repo: bridio/cobot-telegram
  • Install: pip install git+https://github.com/bridio/cobot-telegram
  • Uses python-telegram-bot library
  • Supports multiple groups, media download, edits/deletes

Wizard Integration

Plugins can contribute to cobot wizard init via:

  • wizard_section() - describes the plugin for the wizard
  • wizard_configure(config) - interactive setup returning config dict

Details from Telegram discussion 2026-02-13, filed by Doxios 🦊

## Additional Details from Design Discussion (2026-02-13) ### Telegram Plugin Architecture The telegram plugin exposes extension points for downstream plugins: ```python extension_points = [ "telegram.on_message", # New message received "telegram.on_edit", # Message edited "telegram.on_delete", # Message deleted "telegram.on_media", # Media downloaded (with local path) ] ``` ### Full Data Flow ``` Telegram Group Chat │ ▼ ┌──────────────────┐ │ telegram plugin │ ← Receives all messages └────────┬─────────┘ │ hook: telegram.on_message ▼ ┌──────────────────┐ │ archive plugin │ ← Formats, stores locally (jsonl/markdown) └────────┬─────────┘ │ extension point: archive.on_write ▼ ┌──────────────────┐ │ git-sync plugin │ ← Commits to repo on schedule └──────────────────┘ ``` ### Message Context (passed to extension points) ```python { "group_id": -100123456789, "group_name": "dev-chat", "message_id": 42, "from_user": {"id": 123, "username": "k9ert"}, "timestamp": "2026-02-13T11:33:00Z", "text": "Hello world", "reply_to": 41, # optional "media": { # optional "type": "photo", "file_id": "...", "local_path": "./media/2026-02-13/photo_42.jpg" } } ``` ### External Plugin: cobot-telegram Telegram plugin lives in separate repo to keep core minimal: - **Repo:** `bridio/cobot-telegram` - **Install:** `pip install git+https://github.com/bridio/cobot-telegram` - Uses `python-telegram-bot` library - Supports multiple groups, media download, edits/deletes ### Wizard Integration Plugins can contribute to `cobot wizard init` via: - `wizard_section()` - describes the plugin for the wizard - `wizard_configure(config)` - interactive setup returning config dict --- *Details from Telegram discussion 2026-02-13, filed by Doxios 🦊*
Collaborator

Architecture Review: Logbot Infrastructure Plugins

Verdict: APPROVE (with minor revisions)

This proposal aligns well with Cobot's plugin architecture. The "mechanism, not policy" approach is exactly right.

What Works Well

  1. Clean separation of concerns — Archive, Cron, Git are each focused on one thing
  2. Extension point designarchive.on_write follows the pattern established by filedrop.before_write
  3. Composability — The logbot configuration example shows elegant composition
  4. No existing conflicts — None of these overlap with current plugins (persistence is conversation-specific)

📋 Suggested Plugin Structure

cobot/plugins/
├── archive/
│   ├── __init__.py
│   └── plugin.py        # ArchivePlugin, capabilities=["archive"]
├── cron/
│   ├── __init__.py
│   └── plugin.py        # CronPlugin, capabilities=["cron"]
└── git/
    ├── __init__.py
    └── plugin.py        # GitPlugin, capabilities=["git"]

🔌 Interface Proposal

Add to interfaces.py:

class ArchiveProvider(ABC):
    @abstractmethod
    def write(self, channel: str, timestamp: int, data: dict) -> str:
        """Write data to archive, returns file path."""
        pass
    
    @abstractmethod
    def get_archive_path(self, channel: str, date: str) -> Path:
        """Get archive file path for channel and date."""
        pass

class CronProvider(ABC):
    @abstractmethod
    def register(self, name: str, schedule: str, callback: Callable) -> None:
        """Register a scheduled task."""
        pass
    
    @abstractmethod
    def unregister(self, name: str) -> bool:
        """Unregister a task."""
        pass

class GitProvider(ABC):
    @abstractmethod
    def commit_and_push(self, paths: list[str], message: str) -> dict:
        """Commit paths and push, returns {success, commit_hash}."""
        pass

🛠 Implementation Notes

Archive Plugin:

class ArchivePlugin(Plugin, ArchiveProvider):
    meta = PluginMeta(
        id="archive",
        version="1.0.0",
        capabilities=["archive"],
        dependencies=["config"],
        priority=20,
        extension_points=["archive.on_write"],
    )

Config schema: format (jsonl|markdown|csv), rotation (daily|hourly|none), base_path

Cron Plugin:

class CronPlugin(Plugin, CronProvider):
    meta = PluginMeta(
        id="cron",
        version="1.0.0",
        capabilities=["cron"],
        dependencies=["config"],
        priority=15,  # Early - other plugins may register tasks in start()
        extension_points=["cron.on_tick"],
    )

Use croniter library for cron expression parsing. Run background asyncio task.

Git Plugin:

class GitPlugin(Plugin, GitProvider):
    meta = PluginMeta(
        id="git",
        version="1.0.0",
        capabilities=["git"],
        dependencies=["config"],
        priority=25,
    )

Config: repo_path, auto_push, remote (default: origin)

Open Questions

  1. No-LLM mode: I recommend mode: passive in config. The agent loop should check for LLM capability and skip respond() if none exists. Simpler than special-casing everywhere.
agent:
  mode: passive  # or: active (default)
  1. Cron asyncio integration: Needs careful design. Suggestion: CronPlugin.start() spawns a background task, but uses asyncio.create_task() or threading depending on whether agent runs async.

  2. Dependency on #2: Session observers aren't strictly required for v1. Archive can hook on_message_received directly. Session observers would be a nice-to-have for cleaner separation.

📦 External Telegram Plugin

Agreed that Telegram should be in a separate repo (bridio/cobot-telegram). The extension points design from @doxios's comment is solid — telegram.on_message, telegram.on_media etc. follow the established pattern.

Summary

Criteria Status
Follows BasePlugin pattern
No overlap with existing plugins
Scope appropriate
Aligns with minimal philosophy
Interface definitions needed ⚠️ Add to interfaces.py
No-LLM mode design ⚠️ Recommend mode: passive

Ready to implement once interfaces are finalized.


Reviewed by Cobot Plugin Architecture Reviewer 🔌

## Architecture Review: Logbot Infrastructure Plugins **Verdict: APPROVE (with minor revisions)** This proposal aligns well with Cobot's plugin architecture. The "mechanism, not policy" approach is exactly right. ### ✅ What Works Well 1. **Clean separation of concerns** — Archive, Cron, Git are each focused on one thing 2. **Extension point design** — `archive.on_write` follows the pattern established by `filedrop.before_write` 3. **Composability** — The logbot configuration example shows elegant composition 4. **No existing conflicts** — None of these overlap with current plugins (persistence is conversation-specific) ### 📋 Suggested Plugin Structure ``` cobot/plugins/ ├── archive/ │ ├── __init__.py │ └── plugin.py # ArchivePlugin, capabilities=["archive"] ├── cron/ │ ├── __init__.py │ └── plugin.py # CronPlugin, capabilities=["cron"] └── git/ ├── __init__.py └── plugin.py # GitPlugin, capabilities=["git"] ``` ### 🔌 Interface Proposal Add to `interfaces.py`: ```python class ArchiveProvider(ABC): @abstractmethod def write(self, channel: str, timestamp: int, data: dict) -> str: """Write data to archive, returns file path.""" pass @abstractmethod def get_archive_path(self, channel: str, date: str) -> Path: """Get archive file path for channel and date.""" pass class CronProvider(ABC): @abstractmethod def register(self, name: str, schedule: str, callback: Callable) -> None: """Register a scheduled task.""" pass @abstractmethod def unregister(self, name: str) -> bool: """Unregister a task.""" pass class GitProvider(ABC): @abstractmethod def commit_and_push(self, paths: list[str], message: str) -> dict: """Commit paths and push, returns {success, commit_hash}.""" pass ``` ### 🛠 Implementation Notes **Archive Plugin:** ```python class ArchivePlugin(Plugin, ArchiveProvider): meta = PluginMeta( id="archive", version="1.0.0", capabilities=["archive"], dependencies=["config"], priority=20, extension_points=["archive.on_write"], ) ``` Config schema: `format` (jsonl|markdown|csv), `rotation` (daily|hourly|none), `base_path` **Cron Plugin:** ```python class CronPlugin(Plugin, CronProvider): meta = PluginMeta( id="cron", version="1.0.0", capabilities=["cron"], dependencies=["config"], priority=15, # Early - other plugins may register tasks in start() extension_points=["cron.on_tick"], ) ``` Use `croniter` library for cron expression parsing. Run background asyncio task. **Git Plugin:** ```python class GitPlugin(Plugin, GitProvider): meta = PluginMeta( id="git", version="1.0.0", capabilities=["git"], dependencies=["config"], priority=25, ) ``` Config: `repo_path`, `auto_push`, `remote` (default: origin) ### ❓ Open Questions 1. **No-LLM mode:** I recommend `mode: passive` in config. The agent loop should check for LLM capability and skip `respond()` if none exists. Simpler than special-casing everywhere. ```yaml agent: mode: passive # or: active (default) ``` 2. **Cron asyncio integration:** Needs careful design. Suggestion: `CronPlugin.start()` spawns a background task, but uses `asyncio.create_task()` or threading depending on whether agent runs async. 3. **Dependency on #2:** Session observers aren't strictly required for v1. Archive can hook `on_message_received` directly. Session observers would be a nice-to-have for cleaner separation. ### 📦 External Telegram Plugin Agreed that Telegram should be in a separate repo (`bridio/cobot-telegram`). The extension points design from @doxios's comment is solid — `telegram.on_message`, `telegram.on_media` etc. follow the established pattern. ### Summary | Criteria | Status | |----------|--------| | Follows BasePlugin pattern | ✅ | | No overlap with existing plugins | ✅ | | Scope appropriate | ✅ | | Aligns with minimal philosophy | ✅ | | Interface definitions needed | ⚠️ Add to interfaces.py | | No-LLM mode design | ⚠️ Recommend `mode: passive` | **Ready to implement once interfaces are finalized.** --- *Reviewed by Cobot Plugin Architecture Reviewer* 🔌
Collaborator

🔄 Status Update: Cron Plugin Merged

The cron plugin has been implemented and merged via #35 and #42. Here's how it relates to this issue:


Cron Plugin — DONE

Implemented in cobot/plugins/cron/:

What was proposed:

cron.register("name", "*/12 * * *", callback)

What was implemented:

cron:
  jobs:
    - name: commit-archive
      schedule: "0 */12 * * *"
      mode: isolated        # or main_session
      prompt: "Commit archive files"

Key differences:

  • Config-driven rather than programmatic registration (but add_job() API exists for dynamic registration)
  • Two execution modes: isolated (spawns subagent) and main_session (injects into main loop)
  • Jobs are prompts to the LLM, not raw callbacks

For the logbot use case, we'd need to extend cron to support:

- name: commit-archive
  schedule: "0 */12 * * *"
  action: "git.commit_and_push"  # Direct plugin method call, no LLM

This is a small addition — I can add mode: direct that calls a plugin method directly.


🔲 Archive Plugin — NOT YET IMPLEMENTED

Still needed:

class ArchiveProvider(ABC):
    def write(self, channel: str, timestamp: int, data: dict) -> str
    def get_archive_path(self, channel: str, date: str) -> Path

Config:

archive:
  format: jsonl  # or markdown, csv
  rotation: daily  # or hourly, none
  base_path: ./archive

🔲 Git Plugin — NOT YET IMPLEMENTED

Still needed:

class GitProvider(ABC):
    def commit_and_push(self, paths: list[str], message: str) -> dict

Config:

git:
  repo_path: .
  auto_push: true
  remote: origin

🔲 No-LLM Mode — NOT YET IMPLEMENTED

For logbot to work without an LLM, we need:

agent:
  mode: passive  # Skip LLM calls, just run plugins

Proposal: Split This Issue

This issue covers 4 things. I suggest:

  1. Close this issue as "cron done, others tracked separately"
  2. Create new issues:
    • Archive Plugin (#XX)
    • Git Plugin (#XX)
    • Passive/No-LLM Mode (#XX)
    • Cron: Add mode: direct for callback-style jobs (#XX)

Or we can keep this open as a tracking issue for the "logbot" pattern.

What do you prefer?


Updated by Doxios 🦊

## 🔄 Status Update: Cron Plugin Merged The **cron plugin** has been implemented and merged via #35 and #42. Here's how it relates to this issue: --- ### ✅ Cron Plugin — DONE Implemented in `cobot/plugins/cron/`: **What was proposed:** ```python cron.register("name", "*/12 * * *", callback) ``` **What was implemented:** ```yaml cron: jobs: - name: commit-archive schedule: "0 */12 * * *" mode: isolated # or main_session prompt: "Commit archive files" ``` **Key differences:** - Config-driven rather than programmatic registration (but `add_job()` API exists for dynamic registration) - Two execution modes: `isolated` (spawns subagent) and `main_session` (injects into main loop) - Jobs are prompts to the LLM, not raw callbacks **For the logbot use case**, we'd need to extend cron to support: ```yaml - name: commit-archive schedule: "0 */12 * * *" action: "git.commit_and_push" # Direct plugin method call, no LLM ``` This is a small addition — I can add `mode: direct` that calls a plugin method directly. --- ### 🔲 Archive Plugin — NOT YET IMPLEMENTED Still needed: ```python class ArchiveProvider(ABC): def write(self, channel: str, timestamp: int, data: dict) -> str def get_archive_path(self, channel: str, date: str) -> Path ``` Config: ```yaml archive: format: jsonl # or markdown, csv rotation: daily # or hourly, none base_path: ./archive ``` --- ### 🔲 Git Plugin — NOT YET IMPLEMENTED Still needed: ```python class GitProvider(ABC): def commit_and_push(self, paths: list[str], message: str) -> dict ``` Config: ```yaml git: repo_path: . auto_push: true remote: origin ``` --- ### 🔲 No-LLM Mode — NOT YET IMPLEMENTED For logbot to work without an LLM, we need: ```yaml agent: mode: passive # Skip LLM calls, just run plugins ``` --- ### Proposal: Split This Issue This issue covers 4 things. I suggest: 1. **Close this issue** as "cron done, others tracked separately" 2. **Create new issues:** - Archive Plugin (#XX) - Git Plugin (#XX) - Passive/No-LLM Mode (#XX) - Cron: Add `mode: direct` for callback-style jobs (#XX) Or we can keep this open as a tracking issue for the "logbot" pattern. **What do you prefer?** --- *Updated by Doxios 🦊*
Collaborator

Issue Split Complete

This issue has been split into focused issues:

Component Issue Status
Cron Plugin #35, #42 Merged
Archive Plugin #44 🔲 Open
Git Plugin #45 🔲 Open
Cron Direct Mode #46 🔲 Open
Passive Mode #47 🔲 Open

The logbot pattern can be implemented once #44, #45, #46, and #47 are complete.

Closing this issue as the work is now tracked separately.

## ✅ Issue Split Complete This issue has been split into focused issues: | Component | Issue | Status | |-----------|-------|--------| | **Cron Plugin** | #35, #42 | ✅ Merged | | **Archive Plugin** | #44 | 🔲 Open | | **Git Plugin** | #45 | 🔲 Open | | **Cron Direct Mode** | #46 | 🔲 Open | | **Passive Mode** | #47 | 🔲 Open | The **logbot pattern** can be implemented once #44, #45, #46, and #47 are complete. Closing this issue as the work is now tracked separately.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ultanio/cobot#3
No description provided.