Model or dataset
PleasePrompto/ductor avatar
PleasePrompto/ductor

ductor: your coding CLIs, driven from a chat window

Control Claude Code, Codex CLI and Gemini CLI from Telegram. Live streaming, persistent memory, cron jobs, webhooks, Docker sandboxing.

459 stars89 forksPythonMIT

At a glance

What is it?
The tool does not embed a model or proxy an API. It runs the official command line clients as subprocesses and forwards what you type, so an existing subscription is what pays for it. The design puts four layers of context isolation above that, from group topics down to fully separate sub-agents with their own credentials.
Who is it for?
This suits someone who already pays for a coding assistant subscription and wants to reach it from a phone, and who is comfortable with the fact that the assistant is whatever CLI they installed rather than a fixed model. It is a poor fit if you need the assistant to work while your machine is off, since it runs locally as subprocesses, and a poor fit if you want one shared conversation across every transport, because sub-agents in particular need their own bot credentials.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 79 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 10, 2026, and from our analysis. They are not legal advice.

Editorial analysis

It shells out to the official CLIs, which is the whole architecture

The positioning line is that it uses only official command line clients, with nothing spoofed and nothing proxied, and that means something specific rather than being a marketing phrase.

The mechanism is subprocess execution. When you send a message, the text becomes a console command as though you had typed it yourself, and the client runs on your machine. Responses stream back as the client produces them.

The consequence is the subscription argument. Because there is no API call, the cost is whatever you already pay for the coding tool itself, and the documentation names the specific tiers. It also means the assistant is the CLI you have installed, so a new model inside that CLI is available to you without ductor changing.

The trade is that everything the CLI can do is exposed, because there is no layer interpreting or filtering the command. The page says this plainly in one line: the messaging layer is a transport, and the agent behind it is the same one you would get at a terminal.

Installation reflects the same simplicity. One command, and the wizard that follows handles the CLI checks, the transport setup, the timezone, optional container support, and an optional background service install:

bash
pipx install ductor    # or: uv tool install ductor
ductor

All state lives in plain JSON and markdown under a directory in your home, which means you can read what the bot remembers about you.

Four levels of context, and only the conversation is actually isolated

The documentation builds the interaction model in numbered layers, each adding isolation rather than capability. Reading the layers in order tells you what a given problem needs.

The first is a single private chat, one to one, where every message goes to whichever client is active and the model is switchable with a command that opens an interactive picker. The documentation says this is all you need.

The second is a group. On the messaging service that supports forum topics, each topic becomes its own conversation with its own client context, and on the other transport each room is the same idea. The worked example shows a group with a general topic and four named topics, which is five independent conversations running in parallel, plus your private chat for six. A model can be set per topic.

The third is a named session inside any chat, started with a command and addressed afterwards with an at-sign prefix. It is described as opening a second terminal window next to the current one, and it works inside a sub-agent chat as well.

The fourth is a background task, which is a different thing rather than a fifth flavour. The chat continues while the task runs, the result arrives when it finishes, and the task can ask a question through the agent to you and wait for the answer before continuing.

What all of them share is the important caveat: the chats share one workspace, the same tools, the same memory, and the same files. Only the conversation context is isolated. So switching topics changes what the model remembers about the conversation and nothing else.

Pre-existing group topics show as numbers, and that is the platform's fault

There is a small defect documented with a blame assignment, and it is worth knowing about before you set up a group.

The messaging platform's bot interface has no method that lists existing forum topics. So a bot added to a group that already has ten topics sees ten unnamed ones. The tool learns topic names from the events the platform sends when a topic is created or edited, which means a topic that existed before the bot arrived stays anonymous until somebody renames it.

What you see in that case is a generic label with the topic's number. The documentation is explicit that this is a limitation of the platform rather than of the tool, and it is the kind of note that saves an afternoon of debugging a bot that appears not to work.

The same honesty appears elsewhere in the layering. The named session example shows a session name being derived from the text of the command, producing a nonsense name, and the example then uses that generated name. It is a small detail that tells you the naming is automatic and you should read back what it chose rather than assume.

The background task section has a similar small honesty in it. A task with a question does not fail and does not guess. It asks the agent, the agent asks you, you answer, and the task continues. For a delegated task that will run unattended, that is the difference between a pause and a wrong answer.

Sub-agents are separate bots with separate credentials

The fifth layer is a different order of thing again, and the difference matters more than the naming suggests.

A sub-agent is described as a completely separate bot with its own chat, its own workspace, its own memory, its own authentication for the command line client, and its own configuration for things like the heartbeat, timeouts, and default model. Each one can even use a different transport, so the main agent can be on one messaging service while a sub-agent lives on another.

The cost of that isolation is stated in the one-line setup command: adding a sub-agent needs its own bot token from the platform. So this is not a free way to get a second perspective. It is a second identity.

The reward is that the sub-agent works in its own workspace under a directory per agent, and that you can delegate between agents. The worked example has the main assistant ask a sub-agent running a different client to write tests, with the sub-agent working in its own workspace and the result returning to the main chat.

One more layer of the picture is worth separating. A background task and a sub-agent are not the same. A background task shares your identity, your workspace, and your client's authentication, and simply runs while you keep talking. A sub-agent has all three of its own. Choosing the wrong one gives you a background task with none of the isolation you wanted.

Transports are optional dependencies, so the core is transport agnostic

The package metadata shows how the modularity is implemented, and it is a straightforward one: optional dependencies.

The required set is a validation library, a library for the messaging platform that ships by default, an async HTTP client, a cron expression parser, a YAML parser, a terminal formatting library, an interactive prompt library, a file type detector, an image library, and timezone data. Then each capability is an extra. Matrix support is one library behind an extra. Slack support is two libraries behind an extra. There is an extra for API signing, and separate extras for tests and for linting.

That layout is the claim made in the prose, that new transports plug into a transport agnostic core. The cost of the arrangement is that a Slack user installs the package with an extra rather than the plain name, and that the main messaging library is the one that is not optional, which is a slight asymmetry with a core described as transport agnostic.

The timezone data being a hard dependency is the small detail worth noting. Since the tool schedules jobs and reports times, and since a Python install on a slim container often has no system timezone database, shipping it as a dependency removes a class of runtime failure.

The cron parser being required rather than optional is consistent with the automation side of the project, and it is the same parser that makes the scheduled job expressions work with the timezone you configured during onboarding.

Tests run in parallel lanes, but the test lane itself is kept sequential

The development task runner is worth reading because of one comment in it.

The runner has parallel lanes for linting, formatting check, type checking, a translation completeness check, and tests, and the parallel form is gated behind a minimum version of the runner itself. There is a fix target that formats and then applies lint fixes.

The test target is sequential by default, and the comment says why: it is the safe default. A parallel test target exists behind an opt-in flag, and its comment says it is not verified parallel safe across all of the tests, of which there are more than twenty-two hundred.

That is a good practice and a rare one. Most projects turn on parallel test execution and discover the flakiness later. Here the switch is present, the condition is written down, and the count of tests that have not been verified under it is stated rather than left as a surprise.

The translation check is also a signal about scope. A project that checks translation completeness as a build lane is one where the user-facing strings are systematically externalised, which matches a tool that speaks to users in several languages and several chat platforms.

The type check is scoped to the bot package rather than the whole tree, and the test extras include a library for mocking time and one for running an async HTTP test server, both of which are the tools you need for a project that schedules jobs and calls platform APIs.

Editorial conclusion

This suits someone who already pays for a coding assistant subscription and wants to reach it from a phone, and who is comfortable with the fact that the assistant is whatever CLI they installed rather than a fixed model. It is a poor fit if you need the assistant to work while your machine is off, since it runs locally as subprocesses, and a poor fit if you want one shared conversation across every transport, because sub-agents in particular need their own bot credentials. Before you install, check three things: which CLI you want it to drive, since it checks for the five it knows and the onboarding wizard will tell you what is missing; which transport you are registering, because each has its own credential shape and the messaging library is an optional dependency; and whether you will use named sessions, since they are the difference between one conversation and several. The newest release is v0.20.1 from 2026-07-23 and the last push carries the same date.

Frequently asked questions

How does ductor connect to Claude Code and Codex?

It runs the official command line clients as subprocesses and forwards your messages as console input, so responses stream back the way the client produces them. There is no API proxying and no header spoofing, which means the cost comes from whatever subscription you already have for that client.

What is the difference between a ductor named session and a background task?

A named session is another conversation context inside a chat, addressed with an at-sign prefix, sharing your workspace and authentication. A background task runs autonomously while you keep chatting, has its own memory file, can ask you a question through the agent and wait for the answer, and returns its result when it finishes.

What do ductor sub-agents need that the main agent does not?

Each sub-agent is a separate bot with its own chat, workspace, memory, command line authentication and configuration, and it lives in its own directory under the agent data folder. Adding one needs its own bot token from the messaging platform, and each sub-agent can use a different transport.

Why do my ductor group topics show as numbers instead of names?

The messaging platform's bot interface cannot list existing forum topics, so the tool only learns names from topic created and topic edited events. Topics that existed before the bot was added stay unnamed and show a generic label with their number until somebody renames them.

What does ductor need to run?

Python 3.11 or newer, at least one of the supported command line clients installed, and a credential for one transport. Matrix and Slack are optional dependencies, so Slack users install the package with the Slack extra and then configure the bot and app tokens.

Official sources

  1. License: MIT
  2. PleasePrompto/ductor on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pleaseprompto-ductor.svg)](https://hysenlabs.com/projects/pleaseprompto-ductor)