ductor v0.19.0
ductor v0.19.0
The biggest community release so far: Slack becomes ductor's third chat transport, reasoning effort gets a first-class /effort command, and a wave of contributor PRs makes background tasks, cron delivery, and Codex noticeably more reliable. Ten merged PRs, four squashed bugs — thank you all. 💜
TL;DR
- Slack transport — run your agents from Slack (Socket Mode, wizard setup, native streaming, all 8 languages). Install with the
slackextra. /effort— set reasoning effort per chat/topic, now for Claude (--effort, includingmax) and Codex. Every topic keeps its own level.- Topic-scoped process kills —
/model,/new,/reset, and error recovery no longer abort other topics' in-flight work or background tasks. - Background tasks hardened — completed results survive cancel races, resume can't overlap a live run,
DUCTOR_TASK_IDfinally reaches task subprocesses, and finished-task history prunes itself. - Cron delivery tracking — if Telegram is down when a cron result lands, you'll see it (
last: success (delivery failed)) and the full result is preserved for resend instead of vanishing. - Codex prompts via stdin — no more
Argument list too long, and prompts stay out of/procand logs. Codex resumes also re-assert your selected model and effort now. append_system_prompt_files— inject your own files (glossary, style guide, …) into the system prompt on every turn.- Selector polish — model and effort keyboards render two buttons per row instead of one unreadable line.
Slack transport (#114)
@victor-develop battle-tested this in a long-lived fork and backported it upstream: a full Slack transport speaking the same orchestrator as Telegram and Matrix — Socket Mode (no public webhook needed), onboarding wizard integration, thread-aware routing, media, and Slack-native streaming with graceful fallback. Config mirrors the Matrix pattern (pip install "ductor[slack]", then ductor onboarding). Closes #165.
Per-session reasoning effort (#164)
@ryuhaneul brought effort to parity with model selection: /effort (or the thinking-level step in /model) applies to the current chat or topic only, the main chat sets the global default, and /status shows the active level. Claude gains --effort support for the first time (low … max); Codex keeps -c model_reasoning_effort — now actually switchable at runtime.
Reliability
- Topic-scoped kills (#166, @ryuhaneul) — six call sites used to sweep the whole chat and killed sibling topics' subprocesses (exit 143, billed work lost). Now they only touch the invoking topic; background tasks and named sessions are exempt, exactly as
/stopalways promised. - Cancel vs. completion race (#167, @ryuhaneul) — a cancel arriving while a finished task result was being delivered silently discarded it. The delivery is now shielded; done means delivered.
- Resume overlap guard (#158, reported by @Tony-ooo) — resuming a task whose previous run was still alive spawned a second execution and delivered intermediate state as "completed". Resume now refuses while a run is in flight.
DUCTOR_TASK_ID(#159, reported by @Tony-ooo) —ask_parent.pyno longer dies with "DUCTOR_TASK_ID not set"; the id is derived from the task's process label and injected into the subprocess env (Docker included).- Cron delivery tracking (#160, reported by @Tony-ooo) — execution success and delivery success are recorded separately in
cron_jobs.json; on delivery failure the original result text is kept so it can be resent without re-running the job. - Task history retention (#138, @attid) —
tasks.jsonstops growing forever: finished tasks are pruned by age (finished_retention_hours, default 7 days) and count (finished_keep_last, default 100), running/waiting tasks are never touched. - Startup survives unreachable users — a configured user who never pressed /start ("chat not found") no longer crashes the boot notification path.
Codex
- Prompts via stdin (#137, @attid) — ductor's composed prompts (hooks + memory + context) could exceed the OS argv limit; they now stream through stdin on all platforms, which also keeps them out of
/proc/<pid>/cmdline. The streaming path feeds stdin concurrently, so >64 KiB prompts can't deadlock. - Resume re-asserts model (#168, @ryuhaneul) —
/modelchanges now apply to already-running Codex sessions on resume instead of being silently ignored (verified against the realcodex exec resumegrammar). - Model lists refreshed (#155, @ryuhaneul) — RULES templates, tool help texts, and discovery fallbacks now match the current Codex/Gemini lineups; stale names like
gpt-5.2-codexare gone.
Also in this release
append_system_prompt_files(#157, @ryuhaneul) — workspace-relative files appended to the system prompt on every agent-driven turn (chat, named sessions, tasks, inter-agent); path-traversal safe, 256 KiB cap per file./modelpersists in fresh topics (#169, @ryuhaneul) — selecting a model in a topic without a live session no longer silently discards the choice; count-based staleness now resets only the affected provider bucket instead of wiping all bucket history.- Root installs work (#162, reported by @SpriteOvO) — onboarding as root (e.g. minimal containers without
sudo) no longer crashes onloginctl enable-linger. - Two-per-row selectors —
/modeland/effortkeyboards are readable on phones again. - Reply context stays intact: replying to any message (including cron/task posts) always carries the quoted text to the agent.
Merged PRs
- #114 by @victor-develop — Slack transport
- #164, #166, #167, #168, #169, #157, #155 by @ryuhaneul — effort, kill scoping, task races, Codex resume, topic model persistence, prompt files, model lists
- #137, #138 by @attid — Codex stdin prompts, task retention
And thanks to @Tony-ooo, @SpriteOvO, and @abhisheksoni27 for the sharp bug reports and the Slack nudge. 🙏
Verification
pytest -p xdist -n 8— full suite green (4,000+ tests, incl. the new Slack suite)ruff format --check .,ruff check .— cleanmypy ductor_bot— clean (251 files, strict)- Live smoke test against a real Telegram bot: chat/streaming,
/model,/effort(flag verified in CLI argv), background task env, Codex stdin path ductor --version→ductor 0.19.0
Full changelog: v0.18.2...v0.19.0