Skip to content

Releases: PleasePrompto/ductor

v0.15.0

14 Mar 13:01

Choose a tag to compare

ductor v0.15.0

Multi-language UI, smart heartbeat system, cron routing, image processing, and a bunch of quality-of-life improvements.

TL;DR

  • 7 languages — EN, DE, NL, FR, RU, ES, PT. Switch with one config line, hot-reloadable.
  • Heartbeat per group/topic — independent schedules, custom prompts, per-target quiet hours. Each target runs its own loop.
  • Cron results route back — jobs remember where they were created and deliver results there, not broadcast.
  • Images auto-convert to WebP — resize + compress incoming photos, no more Claude API dimension errors.
  • /model never blocks — switch models while the agent is working.
  • Seen indicator + technical footer — optional read receipt emoji and token/cost info on every response.
  • Transport-aware delivery — Telegram and Matrix messages stay on their transport. Cascading fallback when a transport is unavailable.

Multi-Language Support

Bot UI (commands, status messages, onboarding) is now available in 7 languages.

Code Language
en English (default)
de Deutsch
nl Nederlands
fr Français
ru Русский
es Español
pt Português
{"language": "de"}

Hot-reloadable — change without restart.

Heartbeat Per Group/Topic

Heartbeat is no longer limited to private chats. Configure independent heartbeats for any group or forum topic, each with its own prompt, interval, and quiet hours.

{
  "heartbeat": {
    "enabled": true,
    "interval_minutes": 30,
    "group_targets": [
      {
        "enabled": true,
        "chat_id": -1001234567890,
        "topic_id": 5,
        "prompt": "Check server status and report anomalies.",
        "interval_minutes": 5,
        "quiet_start": 23,
        "quiet_end": 7
      }
    ]
  }
}
  • Each target with a custom interval_minutes runs its own independent asyncio loop
  • enabled: false pauses a target without removing it
  • Chat validation with 1-hour TTL cache before firing
  • Delivery fallback: if the target chat/topic is gone, the result goes to your main private chat
  • The whole heartbeat system can be started/stopped via hot-reload (no restart needed)

Cron Result Routing

Cron jobs now remember where they were created and send results back there.

  • Jobs created in a group topic → results go to that topic (UNICAST)
  • Jobs without routing context → broadcast to all users (legacy behavior, backward compatible)
  • Delivery failure → fallback to main user private chat with explanation
  • /cron overview now shows the routing target per job
  • enabled: false is now properly respected — disabled jobs don't execute or reschedule

Environment variables DUCTOR_CHAT_ID, DUCTOR_TOPIC_ID, and DUCTOR_TRANSPORT are auto-captured by cron_add.py.

Image Processing

Incoming images are automatically resized and converted to WebP after download. No more Claude API "exceeds dimension limit" errors from phone cameras.

  • Max 2000px (configurable), WebP output (configurable), quality 85 (configurable)
  • Works across all transports: Telegram, Matrix, API
  • Errors are non-fatal — original file used as fallback
  • New dependency: Pillow (installed automatically)
{
  "image": {
    "max_dimension": 2000,
    "output_format": "webp",
    "quality": 85
  }
}

/model Never Blocks

The /model selector now opens instantly, even while the agent is processing. The busy-info ("process still running — new model applies to next message") only appears after you actually switch, not when opening the selector.

  • Topic-aware: switching in Topic A doesn't affect Topic B
  • ProcessRegistry now tracks topic_id for per-topic busy checks

Seen Indicator & Technical Footer

Two opt-in features, both default off:

  • Seen indicator — 👀 emoji reaction on incoming messages (Telegram) or read receipt (Matrix)
  • Technical footer — model name, token count, cost, and duration appended to every response
{
  "scene": {
    "seen_reaction": true,
    "technical_footer": true
  }
}

Both are transport-agnostic (defined in BotProtocol) and hot-reloadable.

Transport-Aware Delivery

The MessageBus now routes UNICAST envelopes to the correct transport only. When Telegram and Matrix run in parallel, a heartbeat for a Telegram group stays on Telegram.

  • TransportAdapter declares its transport_name ("tg" / "mx")
  • Cascading fallback: target transport unavailable → other transport gets the message with explanation
  • DUCTOR_TRANSPORT env var propagated to CLI subprocesses

Tool Activity: Shell Label

Tool activity now shows [TOOL: Shell] instead of [TOOL: Bash] — platform-neutral across Linux, macOS, and Windows. Applied to Telegram, Matrix, and API transports.

Bug Fixes

  • Media ignores group_mention_only (#57) — media messages in groups now respect the config flag
  • UpdateObserver on all agents (#58) — only the main agent runs the update checker
  • Cron reschedule kills running jobs (#56) — executing jobs survive cron_jobs.json changes
  • Heartbeat not delivered to topictopic_id now passed as thread_id to Telegram
  • Technical footer missing in streaming — footer is appended as text segment before finalize

Documentation

  • All 13 docs files updated for new features
  • RULES files (CLAUDE.md/AGENTS.md/GEMINI.md) updated with all config sections
  • config.example.json updated with language, image, scene, and heartbeat.group_targets
  • README updated with language support and feature list

Hot-Reloadable Settings

These settings now take effect within seconds — no restart needed:

model, provider, streaming, heartbeat, cleanup, scene, image, language, allowed_user_ids, allowed_group_ids, group_mention_only

v0.14.0

11 Mar 05:09

Choose a tag to compare

ductor v0.14.0

Matrix support, modular messenger architecture, and the /interrupt command.

Matrix Transport

ductor now runs on Matrix alongside Telegram. Use it as your only transport or run both in parallel — same agent, same workspace, same sessions.

  • Segment-based streaming — responses arrive as separate messages, clean and readable
  • Reaction buttons — emoji digits (1️⃣–🔟) replace Telegram's inline keyboards, with text input fallback
  • Incoming media — images, audio, video, and files are downloaded and injected into the conversation
  • Full command parity — every command works with both !cmd and /cmd prefix
  • Auth model — room allowlist + user allowlist + group_mention_only support
  • Auto-join/leave — invited to an allowed room? Joined. Unauthorized? Rejected and left.
ductor install matrix      # install matrix-nio dependency

Then add to config:

{"transports": ["telegram", "matrix"]}

Setup guide: docs/matrix-setup.md

Modular Messenger Architecture

The entire bot layer has been restructured into a transport plugin system. The core (orchestrator, sessions, CLI, cron, webhooks) never knows which messenger delivered the message.

  • BotProtocol — runtime-checkable interface that every transport implements
  • TransportRegistry — factory dict for bot creation, lazy imports
  • MultiBotAdapter — runs multiple transports in parallel on a shared orchestrator
  • SessionKey — transport-prefixed persistence keys (tg:123 vs mx:456) prevent session collisions
  • MessengerCapabilities — feature matrix per transport (streaming style, button support, etc.)

Adding a new messenger (Discord, Slack, Signal, ...) means implementing BotProtocol in a new sub-package. Guide: docs/modules/messenger.md

/interrupt — Soft Interrupt

New command for when you don't want to nuke everything.

Command What it does
/interrupt Sends SIGINT to the current process. Queued messages continue.
/stop Kills the current process and discards all queued messages.
/stop_all Kills everything — all messages, sessions, tasks, all agents.

Trigger words: esc, interrupt, skip, überspringen

Works across Telegram (private, groups, topics), Matrix rooms, sub-agent chats, and named sessions.

Bug Fixes

  • Bot no longer responds to /command@other_bot (#36) — in group chats with multiple bots, commands addressed to another bot are now correctly ignored
  • Codex Docker stdin on Windows (#40) — docker exec -i keeps stdin open so prompts reach the CLI
  • Docker container paths (#38) — /ductor/... paths from inside the container are translated to host paths for file sending
  • Matrix message replay — sent events are tracked to prevent duplicate processing after reconnect
  • Matrix /stop_all — now correctly interrupts across all transports, not just the current one

Documentation

  • New Matrix setup guide with step-by-step instructions
  • Group setup tip: how to use /where to find and add group IDs
  • Expanded config.example.json with all features
  • Updated all module docs for the new messenger architecture

Community

  • PR #29 by @n-haminger — Matrix transport implementation. Merged and restructured into the modular plugin system.
  • PR #37 by @liuh886 — Group command filtering and Windows Docker fixes. Ported into the restructured codebase.

Upgrade

ductor upgrade
# or
pipx upgrade ductor

v0.13.0

08 Mar 04:29

Choose a tag to compare

ductor v0.13.0

Docker sandbox extras, external API secrets, and a round of cross-platform fixes.

Docker Extras — AI/ML Packages in Your Sandbox

Your Docker sandbox can now run AI/ML workloads. Want your agent to transcribe audio, analyze images, or scrape websites? Pick the packages you need during onboarding — or add them later with a single command.

Available extras: Whisper (speech-to-text), PyTorch CPU (machine learning), OpenCV (image/video processing), Tesseract (OCR), EasyOCR (multi-language OCR), Playwright (browser automation), yt-dlp (media download).

  • Pick during setup — the onboarding wizard lets you choose which extras to install
  • Add or remove anytimeductor docker extras-add whisper, ductor docker extras-remove whisper
  • Smart dependencies — selecting EasyOCR automatically includes PyTorch, no manual juggling
  • Stays lightweight — PyTorch is pinned to CPU-only (~200 MB instead of ~900 MB with CUDA), keeping your image lean
  • See what's happening — image builds stream progress to your terminal so you're never staring at a blank screen
ductor docker extras-add whisper    # adds whisper + dependencies
ductor docker extras-list           # show installed extras
ductor docker rebuild               # rebuild image with new extras

External API Secrets (~/.ductor/.env)

A single file for all your external API keys. Your agents can use OpenAI, custom services, or any API that needs a token — just drop the key in ~/.ductor/.env and every CLI session picks it up automatically.

  • No restart needed — edit the file while the bot runs, your next message already uses the new keys
  • Safe defaults — your system environment variables always take priority, nothing gets accidentally overwritten
  • Works everywhere — Linux, macOS, Windows, with and without Docker
# ~/.ductor/.env
OPENAI_API_KEY=sk-...
CUSTOM_SERVICE_TOKEN=abc123

Bug Fixes

  • Models don't randomly disappear anymore (#34) — if model discovery fails (network issue, API down), the bot now falls back to its last known list instead of showing zero models
  • Windows + Docker works (#33) — fixed path translation and a startup crash on Windows
  • Gemini actually reads input (#33) — removed a flag that silently blocked Gemini from reading prompts
  • Forum topic replies land in the right place (#31) — async sub-agent results now arrive in the topic you started them from, not somewhere random in the group
  • Gemini fallback cleanup — reconciled community PR with our cache architecture for a cleaner codebase

Documentation

  • CLI agents now know how to restart the bot on their own (touch ~/.ductor/restart-requested)
  • Full .env documentation across config, infra, and workspace docs

Community

PR #35 by @liuh886 — Windows Docker support, Gemini model defaults, and session stability fixes. Merged and extended with our own improvements on top. Thank you!

Upgrade

ductor upgrade
# or
pip install --upgrade ductor

v0.12.0

04 Mar 08:19

Choose a tag to compare

Forum Topics — Multiple Chats in One Group

ductor now fully supports Telegram forum topics. Create a group, enable topics, and each topic becomes its own isolated chat with its own conversation context.

  • Topic isolation — every topic has its own session, independent from General and your private chat
  • Per-topic model selection — run /model inside a topic to change just that topic's provider
  • Background tasks stay in their topic — task questions and results are delivered back to the topic they were started in
  • Named sessions work in topics/session creates sessions scoped to the current topic
  • Topic names in logs and status — topics show their name instead of just an ID

A group with 5 topics gives you 6 independent conversations (General + 5 topics), all sharing the same workspace, tools, and memory.

Group Chat Management

  • /where — see all tracked groups (active, rejected, left) with their IDs
  • /leave <group_id> — manually leave a group
  • Auto-leave — bot auto-leaves groups not in allowed_group_ids
  • Group audit — runs on startup, on config reload, and every 24 hours
  • allowed_user_ids and allowed_group_ids are now hot-reloadable — edit config.json, changes apply within seconds
  • group_mention_only is now hot-reloadable — no restart needed to toggle

Background Tasks

  • Task results now include the actual result — previously, the final task result injection could send the original prompt instead of the computed answer. Fixed.
  • TASKMEMORY.md content appended to results — when a task completes, its memory file is included so the parent agent gets the full picture
  • Resume reminders — task agents get a short reminder on each follow-up about how to communicate (ask_parent, TASKMEMORY)
  • Delete tasks — new delete_task.py tool lets models permanently remove finished tasks (entry + folder)
  • Sub-agent folder cleanup — fixed a bug where sub-agent task folders were not cleaned up on removal

Timeout Recovery (PR #24 by @n-haminger)

  • Session preserved on CLI timeout — when the CLI times out, the session ID is now captured early and preserved so the next message auto-resumes where it left off
  • Clear user message — shows timeout duration and confirms the session is preserved

Inter-Agent Communication (PR #25 by @n-haminger)

  • --summary flag for async messagesask_agent_async.py --summary "Short description" lets the sender control what preview text the recipient sees in their Telegram notification

Internal Improvements

  • Unified MessageBus with Envelope system — all background delivery (cron, webhooks, heartbeat, tasks, inter-agent) now routes through a single bus
  • Major orchestrator refactoring — extracted selectors, providers, and lifecycle into separate modules
  • Centralized shared code, eliminated duplication across modules
  • Comprehensive documentation rewrite

Full Changelog: v0.11.0...v0.12.0

ductor v0.11.0

02 Mar 16:02

Choose a tag to compare

ductor v0.11.0

Background tasks, timeout resilience, startup recovery, and a bunch of fixes. The biggest release since launch — 88 files changed, ~6800 lines added.

Background Tasks

Every chat — main or sub-agent — can now delegate long-running work to background tasks. You keep chatting while the task runs autonomously.

The agent decides on its own when to delegate (anything likely taking >30 seconds), but you can also tell it explicitly. When a task finishes, its full result flows back into your chat context — as if the agent had done the work itself.

You:  "Research the top 5 competitors and write a summary"
  → Agent delegates this to a background task automatically
  → You keep chatting: "While that's running, explain our pricing model"
  → Task finishes → result delivered into your conversation

You:  "Delegate this: generate PDF reports for all Q4 metrics"
  → Explicitly delegated — task starts, you keep chatting
  → Task has a question? It asks the agent, agent asks you, you answer, task continues
/tasks                      → view/manage all background tasks

Each task gets its own memory file (TASKMEMORY.md) in the workspace and can be resumed with follow-up prompts. Tasks are isolated per agent — a sub-agent's tasks live in its own workspace with its own provider.

Timeout Controller

Long-running CLI streams no longer die silently at the timeout boundary.

  • Staged warnings at T-60s and T-10s so you know what's coming
  • Activity-based extension — if the CLI is still producing output, the timeout extends automatically (configurable max)
  • User-facing messages in Telegram for all timeout events

Startup Recovery

When the bot restarts (service restart, reboot, crash), interrupted work is automatically recovered.

  • Boot ID tracking — detects restart vs. reboot
  • Inflight turn persistence — tracks what was running when the bot stopped
  • Recovery planner — resumes interrupted foreground turns and named sessions
  • Lifecycle notifications — Telegram shows "Service restarted" / "Reboot detected"

Fixes

  • Internal API startup failure propagation — if the inter-agent API can't bind its port, startup fails immediately instead of continuing in a broken state (#21, thanks @clawinbox24!)
  • Gemini 3.1 models visible — removed isActiveModel filter that hid preview models like gemini-3.1-pro-preview (#22)
  • Gemini model cache refresh on startup — models are now always rediscovered on bot restart (matching existing Codex behavior)
  • Task rules for Geminiask_parent.py instructions are now more prominent so Gemini CLI agents actually use the tool instead of writing questions as text
  • Session recovery status — stores correct "idle" status instead of "running"
  • Chat-ID resolution — happens before parallel-limit check in TaskHub
  • Per-agent CLI binding — sub-agent tasks use the correct provider
  • Agent isolation — task list/cancel/resume API endpoints respect agent boundaries

Documentation

  • README rewrite — sessions, background tasks, and sub-agents each get their own section with examples and a comparison table
  • Updated docs across all modules for new features

Community

This release includes work inspired by two community pull requests:

  • #20 by @jhste102lab — timeout resilience, restart visibility, and auto-resume recovery (changes adapted and integrated)
  • #21 by @clawinbox24 — propagate internal API startup failures (merged directly)

Thank you both!

Stats

  • 88 files changed, ~6850 insertions
  • 2727+ tests passing
  • mypy clean, ruff clean

Upgrade

ductor upgrade
# or
pip install --upgrade ductor

ductor v0.10.0

01 Mar 08:38

Choose a tag to compare

ductor v0.10.0

Async sub-agent results now flow back into the main agent's conversation — with full context.

How /agents works now

Each sub-agent is a full agent with its own Telegram chat — you can open it and talk to it directly, just like your main agent. It has its own workspace, memory, and provider configuration.

The new part: when the main agent delegates a task to a sub-agent, the result flows back into your main conversation with full context. Direct interaction with a sub-agent stays in its own chat.

Two ways to use sub-agents

1. Direct chat — open the sub-agent's Telegram bot and talk to it like any other agent. Own context, own conversation.

2. Delegation from main agent — the main agent sends a task, the result comes back into your main chat:

Synchronous delegation (blocking)

You: "Ask codex-agent to check the API tests"
  → Main agent calls sub-agent (blocks)
  → Sub-agent executes, returns result as tool output
  → Main agent processes result in the SAME conversation turn
  → Main agent responds to you with full context

Asynchronous delegation (non-blocking) — fixed in v0.10.0

You: "Give codex-agent a task: migrate the database schema"
  → Main agent fires off task, keeps chatting with you
  → Sub-agent works in the background (can take minutes)
  → Sub-agent finishes
  → Result is injected into the main agent's CURRENT active session
  → Main agent processes with full conversation context
  → Main agent reports the result to you

Before v0.10.0: The async result arrived in a new, context-less session. The main agent didn't know what you originally asked or why it delegated the task. It responded blindly.

Now: The result is injected into whatever session is currently active (even if you did /new or switched providers in the meantime). The prompt is self-contained — it includes the original task description and the sub-agent's response. The main agent always has enough context to give you a meaningful answer.

What happens under the hood

  1. When the main agent sends an async task, the full task message is preserved
  2. When the result arrives, the per-chat lock is acquired (no concurrent session access)
  3. The current active session is resolved via get_active() — always the latest, not the original
  4. A self-contained prompt is built: original task + sub-agent result + session hint
  5. The CLI resumes the current session with --resume
  6. Session metrics (cost, tokens, session ID) are updated after execution
  7. Telegram shows "typing..." while the main agent processes the result

/session vs /agents — when to use which

/session — independent conversations with their own context

Named sessions let you start separate conversations inside the same Telegram chat, each with its own context. They are completely independent from your main chat — the main agent doesn't know about them and they don't share context with it.

/session Fix the login bug              → starts session "firmowl" (own context)
/session @codex Refactor the parser     → starts session "pureray" on Codex (own context)
@firmowl Also check the tests          → follow-up in firmowl's context
/sessions                               → list/manage all active sessions

/agents — full agents you can talk to directly or delegate to

Sub-agents have their own Telegram chat — use them directly for independent work, or let the main agent delegate tasks. Delegated results flow back into the main chat with full context.

# Direct chat (own context):
Open codex-agent's Telegram bot → "Refactor the parser module"

# Delegation (main chat context):
Main chat → "Ask codex-agent to write tests for the API module"
  → Result flows back into main agent's session
  → Main agent tells you the result
/session (Named Sessions) /agents (Sub-Agents)
What it is Independent conversation with its own context Full agent with its own Telegram chat
Context Own context, independent from main chat Own context in direct chat — delegation flows back into main chat context
Workspace Same workspace as main agent Own workspace, own memory
Provider Any provider/model per session Own default provider/model
Result Telegram reply (standalone) Direct chat: standalone. Delegation: injected into main agent's session.
Use case Parallel side-conversations you manage yourself Use directly or let the main agent orchestrate
Setup None — just /session <prompt> ductor agents add <name> + BotFather token

Rule of thumb: Use /session when you want a separate conversation on the side. Use sub-agents when you want a dedicated agent you can either talk to directly or let the main agent delegate to.

Technical changes

  • bus.py: AsyncInterAgentResult now carries original_message — the full task text sent to the sub-agent, populated in all code paths (success, timeout, error)
  • app.py: on_async_interagent_result() acquires the per-chat sequential lock and shows a typing indicator while the result is being processed
  • core.py: handle_async_interagent_result() resumes the current active session, builds a self-contained prompt, and updates session metrics after execution
  • test_interagent.py: Updated tests for new API + 2 new tests (original message in prompt, resume of active session)
  • README.md: Added "Sessions vs. Sub-Agents" comparison table

Upgrade

ductor upgrade
# or
pip install --upgrade ductor

ductor v0.9.1

01 Mar 06:53

Choose a tag to compare

ductor v0.9.1

Hotfix for v0.9.0 — fixes a type error in /stop_all when background tasks are active.

Bug fix

  • /stop_all with background tasks: Fixed a TypeError in abort_all_agents() that occurred when a sub-agent had active background tasks. The background task registry was iterated by task ID (string) instead of chat ID (int), causing cancel_all() to fail.

What's in v0.9.0

Named inter-agent sessions, /stop_all for multi-agent abort, provider-switch detection, and UX improvements.

Named inter-agent sessions

Inter-agent communication now uses persistent named sessions — sub-agents remember previous conversations and can resume where they left off.

  • Automatic session resume: When the main agent talks to a sub-agent, the conversation continues in the same CLI session. No more cold starts on every message.
  • Per-sender isolation: Each calling agent gets its own named session (ia-main, ia-helper, etc.), so parallel conversations don't interfere.
  • --new flag: Pass --new in ask_agent / ask_agent_async to force a fresh session when you need a clean slate.
  • Session persistence: Named sessions survive bot restarts — stored in each agent's named_sessions.json.
  • Provider-switch detection: If a sub-agent switches provider (e.g. codex → claude), the old session is automatically ended (session IDs aren't portable across providers) and a fresh one is created. A notification is sent to the Telegram chat so you know what happened.

Thanks to @n-haminger for the Named Sessions implementation in #18!

/stop_all — kill everything

New command that stops all running processes across all agents at once.

  • /stop_all command: Kills CLI processes on the main agent and every sub-agent, plus cancels in-flight async inter-agent tasks on the bus.
  • "stop all" bare words: Type "stop all", "stopp alle", "cancel all", or "abort all" in chat — works the same as the command, no slash needed.
  • Multi-provider safe: Works regardless of how many sub-agents you have or which providers they use. /stop still only kills local processes — use /stop_all when you want to nuke everything.

UX improvements

  • Case-insensitive commands: /Session, /STATUS, /Model all work now. Mobile keyboards that auto-capitalize after / no longer break commands.
  • Changelog keeps upgrade button: Tapping "Changelog" on an update notification no longer removes the "Upgrade now" button. Both stay visible, and "Upgrade now" is also shown below the changelog text.
  • Clean command names: Fixed /agent\_start displaying with a backslash in usage messages — now shows /agent_start correctly.

Upgrade

ductor upgrade
# or
pip install --upgrade ductor

ductor v0.9.0

01 Mar 06:46

Choose a tag to compare

ductor v0.9.0

Named inter-agent sessions, /stop_all for multi-agent abort, provider-switch detection, and UX improvements.

Named inter-agent sessions

Inter-agent communication now uses persistent named sessions — sub-agents remember previous conversations and can resume where they left off.

  • Automatic session resume: When the main agent talks to a sub-agent, the conversation continues in the same CLI session. No more cold starts on every message.
  • Per-sender isolation: Each calling agent gets its own named session (ia-main, ia-helper, etc.), so parallel conversations don't interfere.
  • --new flag: Pass --new in ask_agent / ask_agent_async to force a fresh session when you need a clean slate.
  • Session persistence: Named sessions survive bot restarts — stored in each agent's named_sessions.json.
  • Provider-switch detection: If a sub-agent switches provider (e.g. codex → claude), the old session is automatically ended (session IDs aren't portable across providers) and a fresh one is created. A notification is sent to the Telegram chat so you know what happened.

Thanks to @n-haminger for the Named Sessions implementation in #18!

/stop_all — kill everything

New command that stops all running processes across all agents at once.

  • /stop_all command: Kills CLI processes on the main agent and every sub-agent, plus cancels in-flight async inter-agent tasks on the bus.
  • "stop all" bare words: Type "stop all", "stopp alle", "cancel all", or "abort all" in chat — works the same as the command, no slash needed.
  • Multi-provider safe: Works regardless of how many sub-agents you have or which providers they use. /stop still only kills local processes — use /stop_all when you want to nuke everything.

UX improvements

  • Case-insensitive commands: /Session, /STATUS, /Model all work now. Mobile keyboards that auto-capitalize after / no longer break commands.
  • Changelog keeps upgrade button: Tapping "Changelog" on an update notification no longer removes the "Upgrade now" button. Both stay visible, and "Upgrade now" is also shown below the changelog text.
  • Clean command names: Fixed /agent\_start displaying with a backslash in usage messages — now shows /agent_start correctly.

Upgrade

ductor upgrade
# or
pip install --upgrade ductor

ductor v0.8.0

28 Feb 09:00

Choose a tag to compare

ductor v0.8.0

Multi-agent system, group chat mode, Docker sandboxing for multi-agent, and robust inter-agent communication.

Multi-agent system

Run multiple ductor agents in a single process — each with its own Telegram bot, provider config, and workspace, coordinated by a central supervisor.

  • Single-process supervisor: AgentSupervisor manages main + sub-agents as supervised asyncio tasks with automatic crash recovery (exponential backoff, max 5 retries).
  • agents.json config: Define sub-agents with their own Telegram token, provider, model, and reasoning effort. Hot-reloaded via FileWatcher — add/remove agents without restart.
  • Inter-agent bus: In-memory InterAgentBus for sync and async messaging between agents. CLI tools use ask_agent.py / ask_agent_async.py via the internal HTTP API.
  • Internal HTTP API: Localhost aiohttp server (127.0.0.1:8799) bridges CLI subprocesses to the bus. Endpoints: /interagent/send, /interagent/send_async, /interagent/agents, /interagent/health.
  • Shared knowledge: SHAREDMEMORY.md at root is automatically synced into every agent's MAINMEMORY.md via SharedKnowledgeSync.
  • Health monitoring: Live health status per agent (starting/running/stopped/crashed) with uptime, restart count, and last crash error. Exposed via /interagent/health and ductor status.
  • Agent identity: Each agent gets its own CLAUDE.md / AGENTS.md / GEMINI.md with injected identity (name, role, available peers).
  • CLI management: ductor agents lists all sub-agents. ductor agents add <name> / ductor agents remove <name> for interactive management.
  • Telegram commands: /agents lists running agents with health status. /agent_restart <name> restarts a sub-agent in-process.
  • Ordered startup: Main agent starts first (Docker, workspace, auth), sub-agents start only after main is ready — no race conditions.

Thanks to @n-haminger for the initial multi-agent system implementation in #16!

Group mention-only mode

New group_mention_only config option for running ductor in Telegram group chats without responding to every message.

  • Bot only responds when mentioned (@botname) or replied to directly.
  • Works for both text and media messages.
  • Enable via "group_mention_only": true in config.json.

Thanks to @n-haminger for #15!

Docker multi-agent support

Shared Docker container architecture for multi-agent mode — all agents use one sandbox.

  • Single shared container: Root ~/.ductor mounted at /ductor. Main agent creates the container; sub-agents reuse it via class-level asyncio lock (no race conditions).
  • Per-agent working directory: docker exec -w /ductor/agents/<name>/workspace ensures each agent operates in its own workspace.
  • Container path mapping: Host paths are translated to container paths (~/.ductor/agents/test/workspace/ductor/agents/test/workspace).
  • Inter-agent communication in Docker: --add-host=host.docker.internal:host-gateway for Linux DNS resolution. InternalAgentAPI binds to 0.0.0.0 in Docker mode so containers reach the bus.
  • No rebuild needed: Existing Docker users just need ductor stop && ductor — the image stays the same, only the container is recreated with correct flags.

Bug fixes

  • Codex parser: parse_codex_result() no longer leaks raw JSONL when Codex produces no assistant text (e.g. silent-success tasks). Differentiates between genuine empty output and unparseable output.
  • Skill sync race conditions: _ensure_copy and _newest_mtime tolerate concurrent file operations from parallel agents (marker deletion, __pycache__ repopulation, partial rmtree).
  • Lint/type fixes: Resolved ruff SIM102/SIM103/TRY300 warnings and mypy type narrowing issues across multiple files.

Upgrade

ductor upgrade
# or
pip install --upgrade ductor

Existing Docker users:

ductor stop    # removes old container
ductor         # creates new container with correct multi-agent flags

ductor v0.7.0

27 Feb 09:33

Choose a tag to compare

ductor v0.7.0

Named background sessions, @model shortcuts, config hot-reload, and a streamlined README.

Named background sessions

Background tasks are no longer fire-and-forget. /session creates named sessions with persistent CLI session IDs that support follow-up messages, provider isolation, and interactive management.

  • Named sessions: /session Fix the login bug starts a named session (e.g. "firmowl") and delivers tagged results.
  • Provider targeting: /session @codex Refactor the parser runs on a specific provider.
  • @model shortcuts: /session @opus Analyze architecture resolves model-to-provider automatically (@opus = Claude, @flash = Gemini, @codex = Codex).
  • Foreground follow-ups: @firmowl Also check the tests streams a follow-up in the current chat.
  • Background follow-ups: /session @firmowl Add error handling queues a background follow-up.
  • Session management: /sessions shows all active sessions with end/refresh buttons.
  • Button routing: Buttons in session results route back to the correct session.
  • Per-label process control: Sessions can be individually stopped without killing the main chat.

Smart per-provider model resolution

Cross-provider sessions now correctly resolve models instead of leaking the default provider's model.

  • BackgroundSubmit carries provider_override and model_override through the full execution chain.
  • CLIService._make_cli() separates the provider-override path from the normal model-resolution path.
  • Gemini sessions pass empty model (CLI auto-selects) instead of inheriting Claude's "sonnet".
  • Auth-aware /session help text shows only providers you have authenticated.

Config hot-reload

Edit config.json while the bot is running — hot-reloadable fields apply immediately without restart.

  • Mtime-based watcher: 5-second poll on config.json, same pattern as cron/webhook watchers.
  • Hot-reloadable fields: model, provider, reasoning_effort, cli_timeout, max_budget_usd, max_turns, permission_mode, streaming.*, heartbeat.*, cleanup.*, cli_parameters.*, and more.
  • Restart-required fields: telegram_token, allowed_user_ids, docker.*, api.* — logged as warnings.
  • Safe reload: Pydantic validation before applying. Failed reloads are logged and skipped.

Documentation overhaul

  • README: Streamlined from 337 to 206 lines with Mermaid architecture diagram, focused structure, and named sessions documentation.
  • 14 module docs synchronized with implementation changes.
  • "Why ductor?" section: Explains the official-CLIs-only philosophy.

Other improvements

  • Compact session names without hyphens for better mobile readability.
  • Improved cron job display in /cron selector.
  • CI quality gate with pre-commit config.

Upgrade

ductor upgrade
# or
pip install --upgrade ductor