Skip to content

ductor v0.19.0

Choose a tag to compare

@PleasePrompto PleasePrompto released this 12 Jul 07:34

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 slack extra.
  • /effort — set reasoning effort per chat/topic, now for Claude (--effort, including max) 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_ID finally 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 /proc and 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 (lowmax); 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 /stop always 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.py no 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.json stops 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)/model changes now apply to already-running Codex sessions on resume instead of being silently ignored (verified against the real codex exec resume grammar).
  • 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-codex are 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.
  • /model persists 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 on loginctl enable-linger.
  • Two-per-row selectors/model and /effort keyboards 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

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 . — clean
  • mypy 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 --versionductor 0.19.0

Full changelog: v0.18.2...v0.19.0