Skip to content

Commit 6dc1469

Browse files
PleasePromptoclaude
andcommitted
chore: bump version to 0.6.0, update docs for API/background/Docker mounts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4d597c3 commit 6dc1469

16 files changed

Lines changed: 272 additions & 78 deletions

AGENTS.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file gives coding agents a current map of the repository.
44

55
## Project Overview
66

7-
ductor is a Telegram bot that routes chat input to official provider CLIs (`claude`, `codex`, `gemini`), streams responses back to Telegram, persists per-chat state, and runs cron/webhook/heartbeat automation in-process. It also has an optional direct WebSocket API server (`api.enabled=true`) with authenticated file upload/download endpoints.
7+
ductor is a Telegram bot that routes chat input to official provider CLIs (`claude`, `codex`, `gemini`), streams responses back to Telegram, persists per-chat state, and runs cron/webhook/heartbeat automation in-process. It also supports `/bg` one-shot background tasks and an optional direct WebSocket API server (`api.enabled=true`) with authenticated file upload/download endpoints.
88

99
Stack:
1010

@@ -65,6 +65,7 @@ Direct API Message (optional)
6565
| `files/` | Shared file helpers (path tags, MIME detection, storage naming, media prompt builder) |
6666
| `orchestrator/` | command registry, directives/hooks, flow routing, observer wiring |
6767
| `cli/` | provider wrappers, stream parsing, auth checks, process registry, model caches |
68+
| `background/` | on-demand `/bg` task runner and async result delivery |
6869
| `session/` | chat sessions with provider-isolated buckets |
6970
| `cron/` | in-process scheduler and one-shot task execution |
7071
| `webhook/` | HTTP hooks (`wake` and `cron_task`) |
@@ -84,13 +85,14 @@ Direct API Message (optional)
8485
- Skill sync spans `~/.ductor/workspace/skills`, `~/.claude/skills`, `~/.codex/skills`, `~/.gemini/skills`.
8586
- normal mode: links
8687
- Docker mode: managed copies (`.ductor_managed` marker)
88+
- Docker user mounts (`docker.mounts`) map host directories into the sandbox under `/mnt/<name>`.
8789
- Streaming fallback is automatic; `/stop` abort checks are enforced during event loop processing.
8890
- Session state is provider-isolated; `/new` resets only the active provider bucket.
8991
- File access policy (`file_access`) is shared by Telegram file sends and API file downloads (`GET /files`).
9092

9193
## Background Systems
9294

93-
All run as in-process asyncio tasks:
95+
Periodic in-process tasks:
9496

9597
- `CronObserver`
9698
- `HeartbeatObserver`
@@ -102,6 +104,10 @@ All run as in-process asyncio tasks:
102104
- skill sync watcher
103105
- update observer (upgradeable installs)
104106

107+
On-demand in-process subsystem:
108+
109+
- `BackgroundObserver` (`/bg` task execution)
110+
105111
Optional network service (not a periodic observer task):
106112

107113
- `ApiServer`
@@ -128,6 +134,11 @@ Optional network service (not a periodic observer task):
128134
| `ductor docker rebuild` | Stop bot, remove container & image, rebuilt on next start |
129135
| `ductor docker enable` | Set `docker.enabled = true` |
130136
| `ductor docker disable` | Stop container, set `docker.enabled = false` |
137+
| `ductor docker mount <path>` | Add host directory mount to `docker.mounts` |
138+
| `ductor docker unmount <path>` | Remove host directory mount from `docker.mounts` |
139+
| `ductor docker mounts` | Show configured Docker mounts and targets |
140+
| `ductor api enable` | Enable direct WebSocket API server config (beta) |
141+
| `ductor api disable` | Disable direct WebSocket API server config (beta) |
131142
| `ductor service install` | Install as background service |
132143
| `ductor service [sub]` | Service management (status/stop/logs/...) |
133144

README.md

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ You can:
3333

3434
- chat from Telegram with Claude/Codex/Gemini,
3535
- stream responses live into edited Telegram messages,
36+
- offload one-off tasks via `/bg` and receive a final result when they finish,
3637
- run cron jobs and webhooks,
3738
- let heartbeat checks proactively notify you,
3839
- isolate runtime with Docker.
@@ -77,10 +78,11 @@ ductor executes the real provider CLIs as subprocesses. It does not proxy or spo
7778

7879
### Automation
7980

81+
- Background tasks (`/bg`): push one-off tasks from the main chat into async execution while the chat stays responsive; receive a final completion message when done
8082
- Cron jobs: in-process scheduler with timezone support, per-job overrides, quiet hours, dependency queue
8183
- Webhooks: `wake` (inject into active chat) and `cron_task` (isolated task run) modes
8284
- Heartbeat: proactive checks in active sessions with cooldown + quiet hours
83-
- Cleanup: daily retention cleanup for `telegram_files/` and `output_to_user/`
85+
- Cleanup: daily retention cleanup for `telegram_files/`, `output_to_user/`, and `api_files/`
8486

8587
### Infrastructure
8688

@@ -89,6 +91,7 @@ ductor executes the real provider CLIs as subprocesses. It does not proxy or spo
8991
- macOS: `launchd` Launch Agent
9092
- Windows: Task Scheduler task
9193
- Docker sidecar sandbox support (`Dockerfile.sandbox`)
94+
- Configurable Docker host-directory mounts (`docker.mounts`, `ductor docker mount ...`)
9295
- Restart protocol (`EXIT_RESTART = 42`) + sentinel-based user notifications
9396
- Version/update flow with Telegram `/upgrade` callback path
9497

@@ -125,10 +128,28 @@ Detailed installation: [`docs/installation.md`](docs/installation.md)
125128
ductor docker enable
126129
ductor docker disable
127130
ductor docker rebuild
131+
ductor docker mount /absolute/or/env/path
132+
ductor docker unmount /absolute/or/env/path
133+
ductor docker mounts
128134
```
129135

130136
- `enable` / `disable`: toggles `docker.enabled` in `~/.ductor/config/config.json`
131137
- `rebuild`: stops the bot, removes Docker container + image, rebuilds on next start
138+
- `mount`: adds a host directory to `docker.mounts` (resolved absolute path; duplicates ignored)
139+
- `unmount`: removes a configured mount (exact/resolved/basename match)
140+
- `mounts`: shows configured host mounts and resolved container targets (`/mnt/<name>`)
141+
- mount/unmount changes require restart or container rebuild to apply
142+
143+
## API management (beta)
144+
145+
```bash
146+
ductor api enable
147+
ductor api disable
148+
```
149+
150+
- `enable`: writes/updates `config.api`, generates token if missing
151+
- `disable`: sets `config.api.enabled=false`
152+
- restart required after changes
132153

133154
## Run as a background service
134155

@@ -159,17 +180,10 @@ You (Telegram)
159180
-> streamed or non-streamed response back to Telegram
160181
```
161182

162-
Background observers run in the same process:
183+
Background systems run in the same process:
163184

164-
- `CronObserver`
165-
- `HeartbeatObserver`
166-
- `WebhookObserver`
167-
- `CleanupObserver`
168-
- `CodexCacheObserver`
169-
- `GeminiCacheObserver`
170-
- `UpdateObserver` (only for upgradeable installs: `pipx`/`pip`)
171-
- rule sync watcher (`CLAUDE.md`/`AGENTS.md`/`GEMINI.md`)
172-
- skill sync watcher
185+
- periodic observers/watchers: `CronObserver`, `HeartbeatObserver`, `WebhookObserver`, `CleanupObserver`, `CodexCacheObserver`, `GeminiCacheObserver`, `UpdateObserver` (only for upgradeable installs), rule sync watcher, skill sync watcher
186+
- on-demand subsystem: `BackgroundObserver` (`/bg` task runner)
173187

174188
Session behavior (important):
175189

@@ -201,8 +215,11 @@ Session behavior (important):
201215
user_tools/
202216
telegram_files/
203217
output_to_user/
218+
api_files/
204219
```
205220

221+
`workspace/api_files/` is created lazily on first API upload.
222+
206223
## Configuration
207224

208225
Config file: `~/.ductor/config/config.json`
@@ -255,6 +272,8 @@ Full schema: [`docs/config.md`](docs/config.md)
255272
| `ductor restart` | Stop and re-exec bot |
256273
| `ductor upgrade` | Upgrade package and restart (non-dev mode) |
257274
| `ductor uninstall` | Remove bot data + uninstall package |
275+
| `ductor docker <enable|disable|rebuild|mount|unmount|mounts>` | Docker mode/config + user mount management |
276+
| `ductor api <enable|disable>` | Direct WebSocket API toggle (beta) |
258277
| `ductor service ...` | Service management (`install/status/start/stop/logs/uninstall`) |
259278
| `ductor help` | Help + status |
260279

docs/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ ductor routes chat input to official provider CLIs (`claude`, `codex`, `gemini`)
1414
8. `docs/modules/files.md` -- shared file parsing/storage/prompt helpers.
1515
9. `docs/modules/cli.md` -- provider wrappers, stream parsing, process control.
1616
10. `docs/modules/workspace.md` -- `~/.ductor` seeding, rule deployment/sync, runtime notices.
17-
11. Remaining module docs (`session`, `cron`, `webhook`, `heartbeat`, `cleanup`, `infra`, `supervisor`, `security`, `logging`, `skill_system`).
17+
11. Remaining module docs (`background`, `session`, `cron`, `webhook`, `heartbeat`, `cleanup`, `infra`, `supervisor`, `security`, `logging`, `skill_system`).
1818

1919
## System in 60 Seconds
2020

@@ -23,13 +23,14 @@ ductor routes chat input to official provider CLIs (`claude`, `codex`, `gemini`)
2323
- `ductor_bot/files/`: shared file tag parsing, MIME detection/classification, storage naming, transport-agnostic media prompt builder.
2424
- `ductor_bot/orchestrator/`: command dispatch, directives/hooks, normal + heartbeat flows, observer/server wiring.
2525
- `ductor_bot/cli/`: Claude/Codex/Gemini wrappers, stream-event normalization, process registry, auth detection, model caches.
26+
- `ductor_bot/background/`: on-demand `/bg` task execution and async result delivery.
2627
- `ductor_bot/session/`: per-chat session lifecycle with provider-isolated buckets in `sessions.json`.
2728
- `ductor_bot/cron/`: in-process scheduler for `cron_jobs.json` with task overrides, quiet hours, dependency queue.
2829
- `ductor_bot/webhook/`: HTTP ingress (`/hooks/{hook_id}`) with `bearer`/`hmac`, `wake`/`cron_task`, and shared dependency queue.
2930
- `ductor_bot/heartbeat/`: periodic proactive checks in active sessions.
30-
- `ductor_bot/cleanup/`: daily retention cleanup for top-level files in `telegram_files`, `output_to_user`, and `api_files`.
31+
- `ductor_bot/cleanup/`: daily recursive retention cleanup for `telegram_files`, `output_to_user`, and `api_files` (plus empty-dir pruning).
3132
- `ductor_bot/workspace/`: path resolution, home seeding from `_home_defaults`, RULES variant deployment, rule sync, skill sync.
32-
- `ductor_bot/infra/`: PID lock, restart/update sentinels, Docker manager, service backends (Linux/macOS/Windows), updater/version checks.
33+
- `ductor_bot/infra/`: PID lock, restart/update sentinels, Docker manager (incl. optional user mounts), service backends (Linux/macOS/Windows), updater/version checks.
3334

3435
Runtime behavior note:
3536

@@ -45,6 +46,7 @@ Runtime behavior note:
4546
- Module docs:
4647
- [setup_wizard](modules/setup_wizard.md)
4748
- [bot](modules/bot.md)
49+
- [background](modules/background.md)
4850
- [api](modules/api.md)
4951
- [files](modules/files.md)
5052
- [cli](modules/cli.md)

docs/architecture.md

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ Direct API message (optional, `api.enabled=true`)
2929
-> WebSocket stream events + final result
3030
```
3131

32-
Also running in background:
32+
Background systems:
3333

3434
- `CronObserver`: schedules `cron_jobs.json` entries.
3535
- `HeartbeatObserver`: periodic checks in existing sessions.
3636
- `WebhookObserver`: HTTP ingress for external triggers.
3737
- `CleanupObserver`: daily retention cleanup for workspace file directories.
38+
- `BackgroundObserver`: on-demand `/bg` task execution and result delivery.
3839
- `GeminiCacheObserver`: periodic Gemini model-cache refresh (`~/.ductor/config/gemini_models.json`).
3940
- `CodexCacheObserver`: periodic Codex model-cache refresh (`~/.ductor/config/codex_models.json`).
4041
- `UpdateObserver`: periodic PyPI version check + Telegram notification (upgradeable installs only).
@@ -57,7 +58,7 @@ Default path:
5758
4. Configure logging.
5859
5. Load/create `~/.ductor/config/config.json`.
5960
6. Deep-merge runtime config with `AgentConfig` defaults.
60-
7. Run `init_workspace(paths)`.
61+
7. Run `init_workspace(paths)` inside `load_config()`.
6162
8. Validate required config fields.
6263
9. Acquire PID lock (`bot.pid`, `kill_existing=True`).
6364
10. Start `TelegramBot`.
@@ -76,21 +77,18 @@ Default path:
7677
### `Orchestrator.create()` (`ductor_bot/orchestrator/core.py`)
7778

7879
1. Resolve paths from `ductor_home`.
79-
2. Run `init_workspace(paths)` in a worker thread.
80-
3. Set `DUCTOR_HOME` env var.
81-
4. If Docker enabled: run `DockerManager.setup()` and keep recovery wiring.
82-
5. If Docker container is active: re-sync skills in Docker-safe copy mode.
83-
6. Inject runtime environment notice into workspace rule files (`inject_runtime_environment`).
84-
7. Build orchestrator instance.
85-
8. Check provider auth (`check_all_auth`) and set authenticated provider set.
86-
9. Start `GeminiCacheObserver` (`~/.ductor/config/gemini_models.json`) and refresh runtime Gemini model registry from its callback.
87-
10. Start `CodexCacheObserver` (`~/.ductor/config/codex_models.json`).
88-
11. Create `CronObserver` and `WebhookObserver` with shared Codex cache.
89-
12. Start cron, heartbeat, webhook, cleanup observers.
90-
13. If `api.enabled=true`: start `ApiServer` (auto-generate token when empty, wire message/abort handlers and file context).
91-
14. Start rule-sync and skill-sync watcher tasks.
92-
93-
`init_workspace()` is called in both `__main__.py` and `Orchestrator.create()`; behavior is idempotent.
80+
2. Set `DUCTOR_HOME` env var.
81+
3. If Docker enabled: run `DockerManager.setup()` (includes auth mounts + optional `mount_host_cache` + `docker.mounts`) and keep recovery wiring.
82+
4. If Docker container is active: re-sync skills in Docker-safe copy mode.
83+
5. Inject runtime environment notice into workspace rule files (`inject_runtime_environment`).
84+
6. Build orchestrator instance.
85+
7. Check provider auth (`check_all_auth`) and set authenticated provider set.
86+
8. Start `GeminiCacheObserver` (`~/.ductor/config/gemini_models.json`) and refresh runtime Gemini model registry from its callback.
87+
9. Start `CodexCacheObserver` (`~/.ductor/config/codex_models.json`).
88+
10. Create `BackgroundObserver`, `CronObserver`, and `WebhookObserver` (cron/webhook share Codex cache).
89+
11. Start cron, heartbeat, webhook, cleanup observers.
90+
12. If `api.enabled=true`: start `ApiServer` (auto-generate token when empty, wire message/abort handlers and file context).
91+
13. Start rule-sync and skill-sync watcher tasks.
9492

9593
## Message Routing
9694

@@ -168,7 +166,8 @@ Per connection:
168166

169167
1. `ws://<host>:<port>/ws` handshake.
170168
2. First frame must be auth JSON with token (10s timeout).
171-
3. Session `chat_id` defaults to first `allowed_user_ids` entry (fallback `1`); auth payload may override.
169+
- `auth_ok` returns E2E server key plus provider metadata (`providers`) and active runtime fields when available.
170+
3. Session `chat_id` defaults to `config.api.chat_id` (`>0`) or first `allowed_user_ids` entry (fallback `1`); auth payload may override.
172171
4. `message` frames run under per-`chat_id` lock and use streaming callbacks (`text_delta`, `tool_activity`, `system_status`, `result`).
173172
5. `abort` frame or `/stop` message calls orchestrator abort path (`ProcessRegistry.kill_all`).
174173

@@ -178,8 +177,6 @@ Additional HTTP endpoints:
178177
- `GET /files?path=...` (Bearer auth + `file_access` root checks),
179178
- `POST /upload` (Bearer auth + multipart save to `workspace/api_files/YYYY-MM-DD/`).
180179

181-
Current wiring note: `config.api.chat_id` exists in schema but is not used by startup defaulting.
182-
183180
## Callback Query Flow
184181

185182
`TelegramBot._on_callback_query()`:
@@ -201,6 +198,14 @@ Lock usage is path-dependent (e.g., queue cancel and upgrade callbacks are handl
201198

202199
## Background Systems
203200

201+
### Background (`/bg`) flow
202+
203+
- `TelegramBot._on_bg(...)` submits one-shot work via `Orchestrator.submit_background_task(...)`.
204+
- `BackgroundObserver` enforces max 5 active tasks per chat and runs tasks asynchronously.
205+
- Execution uses shared one-shot task runner (`infra/task_runner.py`) with current provider/model config.
206+
- Completion callback (`TelegramBot._on_bg_result`) sends a new Telegram message (notification-friendly).
207+
- `/stop` abort path cancels both active CLI subprocesses and active background tasks for the chat.
208+
204209
### Cron flow
205210

206211
- `CronObserver` watches `cron_jobs.json` mtime every 5 seconds.
@@ -246,8 +251,8 @@ Lock usage is path-dependent (e.g., queue cancel and upgrade callbacks are handl
246251

247252
- Hourly check in `user_timezone`.
248253
- Runs at most once per day when local hour equals `cleanup.check_hour`.
249-
- Deletes old top-level files in `workspace/telegram_files/`, `workspace/output_to_user/`, and `workspace/api_files/`.
250-
- Deletion is non-recursive, so files inside date subdirectories (`YYYY-MM-DD/`) are not cleaned by current logic.
254+
- Deletes old files recursively in `workspace/telegram_files/`, `workspace/output_to_user/`, and `workspace/api_files/`.
255+
- Prunes empty subdirectories after file deletion (including date-based upload folders).
251256

252257
## Restart & Supervisor
253258

docs/automation.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Default prompt asks the model to review memory + cron context and either send so
147147

148148
Cleanup runs once per day at `cleanup.check_hour` (in `user_timezone`), checked hourly.
149149

150-
Deletes old top-level files from:
150+
Deletes old files (recursive) from:
151151

152152
- `workspace/telegram_files/`
153153
- `workspace/output_to_user/`
@@ -159,12 +159,7 @@ Retention windows:
159159
- `cleanup.output_to_user_days`
160160
- `cleanup.api_files_days`
161161

162-
Cleanup is non-recursive.
163-
164-
Current implication:
165-
166-
- uploads are stored in dated subdirectories (`YYYY-MM-DD/`),
167-
- those subdirectory files are not removed by current cleanup logic.
162+
Cleanup also prunes empty subdirectories after deletion, so dated upload folders are removed once empty.
168163

169164
## Config blocks
170165

0 commit comments

Comments
 (0)