djobs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- djobs-0.1.0/.github/agents/durable-coder.agent.md +108 -0
- djobs-0.1.0/.github/workflows/ci.yml +29 -0
- djobs-0.1.0/.github/workflows/publish.yml +28 -0
- djobs-0.1.0/.gitignore +41 -0
- djobs-0.1.0/.vscode/mcp.json +22 -0
- djobs-0.1.0/.vscode/settings.json +3 -0
- djobs-0.1.0/CONTRIBUTING.md +48 -0
- djobs-0.1.0/LICENSE +21 -0
- djobs-0.1.0/PKG-INFO +261 -0
- djobs-0.1.0/README.md +235 -0
- djobs-0.1.0/docker/docker-compose.yml +21 -0
- djobs-0.1.0/docs/ARCHITECTURE.md +360 -0
- djobs-0.1.0/docs/HANDOFF.md +246 -0
- djobs-0.1.0/docs/IMPLEMENTATION_NOTES.md +388 -0
- djobs-0.1.0/docs/ROADMAP.md +385 -0
- djobs-0.1.0/examples/demo_workspace/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/api/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/api/middleware.py +48 -0
- djobs-0.1.0/examples/demo_workspace/api/response.py +34 -0
- djobs-0.1.0/examples/demo_workspace/api/router.py +34 -0
- djobs-0.1.0/examples/demo_workspace/api/serializers.py +46 -0
- djobs-0.1.0/examples/demo_workspace/config/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/config/database.py +47 -0
- djobs-0.1.0/examples/demo_workspace/config/settings.py +23 -0
- djobs-0.1.0/examples/demo_workspace/domain/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/domain/analytics.py +33 -0
- djobs-0.1.0/examples/demo_workspace/domain/discount.py +23 -0
- djobs-0.1.0/examples/demo_workspace/domain/pricing.py +31 -0
- djobs-0.1.0/examples/demo_workspace/domain/shipping.py +33 -0
- djobs-0.1.0/examples/demo_workspace/domain/tax.py +30 -0
- djobs-0.1.0/examples/demo_workspace/domain/workflow.py +52 -0
- djobs-0.1.0/examples/demo_workspace/models/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/models/inventory.py +22 -0
- djobs-0.1.0/examples/demo_workspace/models/order.py +25 -0
- djobs-0.1.0/examples/demo_workspace/models/payment.py +28 -0
- djobs-0.1.0/examples/demo_workspace/models/product.py +18 -0
- djobs-0.1.0/examples/demo_workspace/models/user.py +17 -0
- djobs-0.1.0/examples/demo_workspace/services/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/services/auth.py +28 -0
- djobs-0.1.0/examples/demo_workspace/services/cache.py +34 -0
- djobs-0.1.0/examples/demo_workspace/services/email.py +24 -0
- djobs-0.1.0/examples/demo_workspace/services/notification.py +39 -0
- djobs-0.1.0/examples/demo_workspace/services/rate_limiter.py +30 -0
- djobs-0.1.0/examples/demo_workspace/utils/__init__.py +1 -0
- djobs-0.1.0/examples/demo_workspace/utils/collections.py +34 -0
- djobs-0.1.0/examples/demo_workspace/utils/formatters.py +30 -0
- djobs-0.1.0/examples/demo_workspace/utils/logging_utils.py +41 -0
- djobs-0.1.0/examples/demo_workspace/utils/retry.py +42 -0
- djobs-0.1.0/examples/demo_workspace/utils/validators.py +22 -0
- djobs-0.1.0/examples/run_ai_demo.py +182 -0
- djobs-0.1.0/examples/run_durable_demo.py +233 -0
- djobs-0.1.0/examples/run_echo_job.py +42 -0
- djobs-0.1.0/examples/run_pool_demo.py +85 -0
- djobs-0.1.0/examples/run_retry_job.py +59 -0
- djobs-0.1.0/examples/run_scheduler_demo.py +87 -0
- djobs-0.1.0/migrations/001_initial.sql +29 -0
- djobs-0.1.0/migrations/002_active_idempotency_key.sql +4 -0
- djobs-0.1.0/migrations/003_lease_columns.sql +3 -0
- djobs-0.1.0/migrations/004_observability_columns.sql +3 -0
- djobs-0.1.0/migrations/005_postgres_schema.sql +42 -0
- djobs-0.1.0/pyproject.toml +67 -0
- djobs-0.1.0/scripts/generate_demo_files.py +934 -0
- djobs-0.1.0/src/djobs/__init__.py +49 -0
- djobs-0.1.0/src/djobs/api/__init__.py +1 -0
- djobs-0.1.0/src/djobs/api/ai_handlers.py +100 -0
- djobs-0.1.0/src/djobs/cli.py +160 -0
- djobs-0.1.0/src/djobs/core/__init__.py +1 -0
- djobs-0.1.0/src/djobs/core/config.py +25 -0
- djobs-0.1.0/src/djobs/core/errors.py +35 -0
- djobs-0.1.0/src/djobs/core/models.py +45 -0
- djobs-0.1.0/src/djobs/core/retry.py +39 -0
- djobs-0.1.0/src/djobs/core/states.py +52 -0
- djobs-0.1.0/src/djobs/daemon.py +190 -0
- djobs-0.1.0/src/djobs/mcp_server.py +508 -0
- djobs-0.1.0/src/djobs/observability/__init__.py +6 -0
- djobs-0.1.0/src/djobs/observability/inspect.py +47 -0
- djobs-0.1.0/src/djobs/observability/logging.py +62 -0
- djobs-0.1.0/src/djobs/observability/metrics.py +70 -0
- djobs-0.1.0/src/djobs/queue/__init__.py +1 -0
- djobs-0.1.0/src/djobs/queue/service.py +173 -0
- djobs-0.1.0/src/djobs/scheduler/__init__.py +5 -0
- djobs-0.1.0/src/djobs/scheduler/scheduler.py +132 -0
- djobs-0.1.0/src/djobs/storage/__init__.py +1 -0
- djobs-0.1.0/src/djobs/storage/events.py +20 -0
- djobs-0.1.0/src/djobs/storage/postgres.py +631 -0
- djobs-0.1.0/src/djobs/storage/sqlite.py +633 -0
- djobs-0.1.0/src/djobs/worker/__init__.py +1 -0
- djobs-0.1.0/src/djobs/worker/pool.py +200 -0
- djobs-0.1.0/src/djobs/worker/registry.py +31 -0
- djobs-0.1.0/src/djobs/worker/runner.py +48 -0
- djobs-0.1.0/tests/integration/test_ai_platform_flow.py +98 -0
- djobs-0.1.0/tests/integration/test_observability_flow.py +81 -0
- djobs-0.1.0/tests/integration/test_repository_contract.py +211 -0
- djobs-0.1.0/tests/integration/test_scheduler_loop_flow.py +138 -0
- djobs-0.1.0/tests/integration/test_sqlite_crash_recovery_flow.py +57 -0
- djobs-0.1.0/tests/integration/test_sqlite_job_flow.py +39 -0
- djobs-0.1.0/tests/integration/test_sqlite_retry_flow.py +53 -0
- djobs-0.1.0/tests/integration/test_worker_pool_flow.py +143 -0
- djobs-0.1.0/tests/unit/test_ai_handlers.py +115 -0
- djobs-0.1.0/tests/unit/test_concurrency.py +135 -0
- djobs-0.1.0/tests/unit/test_config.py +23 -0
- djobs-0.1.0/tests/unit/test_daemon.py +324 -0
- djobs-0.1.0/tests/unit/test_imports.py +44 -0
- djobs-0.1.0/tests/unit/test_job_model.py +25 -0
- djobs-0.1.0/tests/unit/test_job_state.py +47 -0
- djobs-0.1.0/tests/unit/test_lease.py +137 -0
- djobs-0.1.0/tests/unit/test_logging.py +26 -0
- djobs-0.1.0/tests/unit/test_mcp_server.py +354 -0
- djobs-0.1.0/tests/unit/test_metrics.py +51 -0
- djobs-0.1.0/tests/unit/test_observability.py +155 -0
- djobs-0.1.0/tests/unit/test_queue_service.py +121 -0
- djobs-0.1.0/tests/unit/test_retry_policy.py +46 -0
- djobs-0.1.0/tests/unit/test_scheduler.py +257 -0
- djobs-0.1.0/tests/unit/test_sqlite_repository.py +160 -0
- djobs-0.1.0/tests/unit/test_worker_pool.py +248 -0
- djobs-0.1.0/tests/unit/test_worker_registry.py +39 -0
- djobs-0.1.0/tests/unit/test_worker_runner.py +134 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Durable Coder
|
|
3
|
+
description: "Durable Coder — a crash-proof coding agent. Use when you have multi-file tasks like 'add docstrings to all files', 'refactor all tests', 'add type hints everywhere'. Work survives IDE crashes and session interruptions — pick up exactly where you left off."
|
|
4
|
+
tools:
|
|
5
|
+
- djobs/*
|
|
6
|
+
- read
|
|
7
|
+
- edit
|
|
8
|
+
- search
|
|
9
|
+
- execute
|
|
10
|
+
- agent
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Durable Coder Agent
|
|
14
|
+
|
|
15
|
+
You are a crash-proof coding agent. When the user gives you a multi-file task,
|
|
16
|
+
you use a durable job queue to **checkpoint your progress**, so if the session
|
|
17
|
+
crashes or the user closes the chat, the work can be resumed from where it stopped.
|
|
18
|
+
|
|
19
|
+
The user does NOT interact with the queue directly. You manage it transparently.
|
|
20
|
+
**Never ask the user whether to resume — just do it.**
|
|
21
|
+
|
|
22
|
+
## Core Behavior
|
|
23
|
+
|
|
24
|
+
### 1. Session Start — Silent Resume
|
|
25
|
+
|
|
26
|
+
At the start of EVERY conversation, call `resume_session` with
|
|
27
|
+
`correlation_id` = the workspace folder path. **Do this silently.**
|
|
28
|
+
|
|
29
|
+
- If incomplete tasks exist AND the user's message is vague (e.g. "hi", "繼續", "continue"):
|
|
30
|
+
→ **Automatically resume.** Say "接續上次未完成的工作(N 個檔案),繼續處理。" then start working.
|
|
31
|
+
- If incomplete tasks exist BUT the user gives a NEW specific request:
|
|
32
|
+
→ Ignore old tasks, start the new request. Old tasks stay in queue but don't block.
|
|
33
|
+
- If no incomplete tasks → proceed normally with whatever the user asked.
|
|
34
|
+
|
|
35
|
+
**NEVER ask "要繼續嗎?" — that wastes a turn. Just do it.**
|
|
36
|
+
|
|
37
|
+
### 2. Multi-File Tasks — Checkpoint Automatically
|
|
38
|
+
|
|
39
|
+
When the user asks you to do something across multiple files (>3 files):
|
|
40
|
+
|
|
41
|
+
1. **Scan** — find all target files.
|
|
42
|
+
2. **Plan** — tell the user: "找到 N 個檔案,開始處理。" then immediately start.
|
|
43
|
+
3. **Enqueue** — create one task per file via `enqueue_task` (this is the checkpoint).
|
|
44
|
+
4. **Execute** — process each task yourself:
|
|
45
|
+
- Read the file.
|
|
46
|
+
- Make the edit.
|
|
47
|
+
- Call `complete_task(task_id)` on success.
|
|
48
|
+
- Call `fail_task(task_id, error)` if the edit fails.
|
|
49
|
+
5. **Report** — after each batch: "[3/12] ✓ src/djobs/core/models.py"
|
|
50
|
+
|
|
51
|
+
### 3. Crash Recovery — Seamless Auto-Resume
|
|
52
|
+
|
|
53
|
+
If the session is interrupted and the user opens a new chat:
|
|
54
|
+
|
|
55
|
+
1. `resume_session` finds unfinished tasks.
|
|
56
|
+
2. If user's message is vague → auto-resume immediately, no questions asked.
|
|
57
|
+
3. If user gives a new request → do the new request instead.
|
|
58
|
+
|
|
59
|
+
### 4. Small Tasks — Just Do It
|
|
60
|
+
|
|
61
|
+
If the task involves ≤3 files, skip the queue and do it directly.
|
|
62
|
+
Don't over-engineer simple requests.
|
|
63
|
+
|
|
64
|
+
### 5. "What did you do?" — Use audit_log
|
|
65
|
+
|
|
66
|
+
When the user asks about past work — "what changed yesterday?", "did any tasks
|
|
67
|
+
fail?", "summarise today's AI work" — call `audit_log` instead of guessing:
|
|
68
|
+
|
|
69
|
+
- `audit_log()` — 24h summary (task counts, failure list, event breakdown).
|
|
70
|
+
- `audit_log(summary=False, limit=50)` — recent event timeline.
|
|
71
|
+
- `audit_log(event_type="job_failed")` — just the failures.
|
|
72
|
+
- `audit_log(correlation_id=<workspace>)` — scoped to this workspace.
|
|
73
|
+
|
|
74
|
+
This gives accurate answers from the event log, not from memory.
|
|
75
|
+
|
|
76
|
+
## Rules
|
|
77
|
+
|
|
78
|
+
- **correlation_id**: Always use the workspace folder path.
|
|
79
|
+
- **idempotency_key**: Use `"{task_type}:{file_path}"` to prevent duplicates.
|
|
80
|
+
- **Lifecycle**: After editing each file, call `complete_task(task_id)`. On error, call `fail_task(task_id, error)`. This keeps `resume_session` and `audit_log` accurate.
|
|
81
|
+
- **Progress**: Print `[n/total] ✓ file` after each file completes.
|
|
82
|
+
- **Transparency**: Never ask the user to call queue tools. You are the worker.
|
|
83
|
+
|
|
84
|
+
## Example
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
User: "幫所有 Python 檔案加 docstring"
|
|
88
|
+
|
|
89
|
+
Agent:
|
|
90
|
+
1. resume_session (silently) → no incomplete tasks
|
|
91
|
+
2. Scan src/ → finds 12 .py files
|
|
92
|
+
3. "找到 12 個 Python 檔案,開始加 docstring。"
|
|
93
|
+
4. enqueue all 12 + immediately start processing
|
|
94
|
+
5. "[1/12] ✓ src/djobs/core/models.py"
|
|
95
|
+
"[2/12] ✓ src/djobs/core/states.py"
|
|
96
|
+
... (no pauses, no questions) ...
|
|
97
|
+
6. "全部完成!12/12 檔案已加上 docstring。"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If session crashes after file 7, user opens new chat:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
User: "hi"
|
|
104
|
+
Agent:
|
|
105
|
+
1. resume_session → found 5 incomplete tasks
|
|
106
|
+
2. "接續上次未完成的 docstring 工作(5/12),繼續處理。"
|
|
107
|
+
3. Immediately continues from file 8 — no questions asked.
|
|
108
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.13"
|
|
17
|
+
- run: pip install ruff
|
|
18
|
+
- run: ruff check src/ tests/
|
|
19
|
+
- run: ruff format --check src/ tests/
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.13"
|
|
28
|
+
- run: pip install -e ".[dev,mcp]"
|
|
29
|
+
- run: pytest -q --tb=short
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.13"
|
|
22
|
+
|
|
23
|
+
- run: python -m pip install --upgrade build twine
|
|
24
|
+
|
|
25
|
+
- run: python -m build
|
|
26
|
+
- run: python -m twine check dist/*
|
|
27
|
+
|
|
28
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
djobs-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg
|
|
8
|
+
|
|
9
|
+
# Virtual environment
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# SQLite databases
|
|
13
|
+
*.db
|
|
14
|
+
*.sqlite
|
|
15
|
+
*.sqlite3
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.vscode/*
|
|
19
|
+
!.vscode/mcp.json
|
|
20
|
+
!.vscode/settings.json
|
|
21
|
+
|
|
22
|
+
# OS
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
|
|
26
|
+
# Environment
|
|
27
|
+
.env
|
|
28
|
+
.env.*
|
|
29
|
+
|
|
30
|
+
# Test / coverage
|
|
31
|
+
.pytest_cache/
|
|
32
|
+
htmlcov/
|
|
33
|
+
.coverage
|
|
34
|
+
coverage.xml
|
|
35
|
+
|
|
36
|
+
# Ruff
|
|
37
|
+
.ruff_cache/
|
|
38
|
+
|
|
39
|
+
# Distribution
|
|
40
|
+
*.whl
|
|
41
|
+
*.tar.gz
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"servers": {
|
|
3
|
+
"djobs": {
|
|
4
|
+
"type": "stdio",
|
|
5
|
+
"command": "${workspaceFolder}/.venv/Scripts/python.exe",
|
|
6
|
+
"args": ["-m", "djobs.mcp_server"],
|
|
7
|
+
"env": {
|
|
8
|
+
"PYTHONPATH": "${workspaceFolder}/src"
|
|
9
|
+
},
|
|
10
|
+
"autoApprove": [
|
|
11
|
+
"health",
|
|
12
|
+
"resume_session",
|
|
13
|
+
"check_task",
|
|
14
|
+
"complete_task",
|
|
15
|
+
"fail_task",
|
|
16
|
+
"list_tasks",
|
|
17
|
+
"enqueue_task",
|
|
18
|
+
"audit_log"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Contributing to djobs
|
|
2
|
+
|
|
3
|
+
Thanks for your interest! Here's how to get started.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/jhuang-tw/djobs.git
|
|
9
|
+
cd djobs
|
|
10
|
+
python -m venv .venv
|
|
11
|
+
.venv/bin/activate # Windows: .venv\Scripts\activate
|
|
12
|
+
pip install -e ".[dev,mcp]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Running Tests
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pytest -q # all tests (skips Postgres unless available)
|
|
19
|
+
pytest tests/unit -q # unit tests only
|
|
20
|
+
ruff check src/ tests/ # lint
|
|
21
|
+
ruff format src/ tests/ # auto-format
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Pull Request Guidelines
|
|
25
|
+
|
|
26
|
+
1. Fork the repo and create a feature branch from `main`.
|
|
27
|
+
2. Keep commits focused — one logical change per commit.
|
|
28
|
+
3. Add or update tests for any new behavior.
|
|
29
|
+
4. Ensure `pytest -q` and `ruff check` pass before submitting.
|
|
30
|
+
5. Write a clear PR description explaining *what* and *why*.
|
|
31
|
+
|
|
32
|
+
## Code Style
|
|
33
|
+
|
|
34
|
+
- Python 3.13+, type hints everywhere.
|
|
35
|
+
- `ruff` for linting and formatting (config in `pyproject.toml`).
|
|
36
|
+
- `src` layout — all package code lives under `src/djobs/`.
|
|
37
|
+
- Tests mirror the source tree: `tests/unit/`, `tests/integration/`.
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for module boundaries and design decisions.
|
|
42
|
+
|
|
43
|
+
## Reporting Issues
|
|
44
|
+
|
|
45
|
+
Open a GitHub issue with:
|
|
46
|
+
- What you expected vs. what happened.
|
|
47
|
+
- Minimal reproduction steps.
|
|
48
|
+
- Python version and OS.
|
djobs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 djobs contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
djobs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: djobs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SQLite-backed durable task queue with first-class MCP integration, purpose-built for AI coding agents (crash recovery, audit trail, embedded daemon).
|
|
5
|
+
Project-URL: Homepage, https://github.com/jhuang-tw/djobs
|
|
6
|
+
Project-URL: Repository, https://github.com/jhuang-tw/djobs
|
|
7
|
+
Project-URL: Issues, https://github.com/jhuang-tw/djobs/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agent,audit-log,crash-recovery,durable,job-queue,mcp,sqlite,task-queue
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
21
|
+
Provides-Extra: mcp
|
|
22
|
+
Requires-Dist: mcp[cli]>=1.0; extra == 'mcp'
|
|
23
|
+
Provides-Extra: pg
|
|
24
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == 'pg'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# djobs
|
|
28
|
+
|
|
29
|
+
**A SQLite-backed durable task queue with first-class MCP integration**, purpose-built for AI coding agents that need crash recovery, audit trails, and zero infrastructure.
|
|
30
|
+
|
|
31
|
+
[](https://github.com/jhuang-tw/djobs/actions/workflows/ci.yml)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
[](https://www.python.org/downloads/)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why djobs?
|
|
38
|
+
|
|
39
|
+
AI coding agents (GitHub Copilot, Cursor, Cline, etc.) often run multi-file tasks that can take several minutes. When the IDE crashes or the chat disconnects mid-way, in-flight progress is usually lost because the agent's state lives only in chat history.
|
|
40
|
+
|
|
41
|
+
djobs gives agents a small, durable checkpoint queue so they can resume exactly where they stopped:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Agent: "Add docstrings to 12 files"
|
|
45
|
+
-> enqueue 12 tasks (crash-safe checkpoint)
|
|
46
|
+
-> edit file -> complete_task
|
|
47
|
+
-> edit file -> complete_task
|
|
48
|
+
-> ... IDE crashes after file 7 ...
|
|
49
|
+
|
|
50
|
+
New chat: "hi"
|
|
51
|
+
-> resume_session -> 5 incomplete tasks found
|
|
52
|
+
-> auto-resume from file 8 — no questions asked
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Under the hood it is a fairly conventional durable job queue (state machine, retry policy, lease, scheduler, event log). The interesting part is how it is wired to AI agents: an MCP server with `enqueue_task` / `complete_task` / `resume_session` / `audit_log`, plus an embedded background daemon, plus a `type_filter` so daemon-managed jobs and agent-managed jobs do not fight over the same queue.
|
|
56
|
+
|
|
57
|
+
### What is in the box
|
|
58
|
+
|
|
59
|
+
| Area | What you get |
|
|
60
|
+
|------|--------------|
|
|
61
|
+
| **MCP server** | 8 tools exposed via FastMCP / stdio — works in VS Code, Claude Desktop, etc. |
|
|
62
|
+
| **Crash recovery** | `resume_session` returns incomplete tasks for a given workspace / correlation id |
|
|
63
|
+
| **Audit trail** | `audit_log` aggregates `job_events` so you can answer "what did the AI do yesterday?" |
|
|
64
|
+
| **Type isolation** | Built-in daemon only claims job types it has handlers for; AI-only types are left to the agent via `complete_task` / `fail_task` |
|
|
65
|
+
| **SQLite first** | No Redis, RabbitMQ, Docker, or Postgres required for local use |
|
|
66
|
+
| **Postgres path** | Same `JobRepository` protocol implemented on top of `SELECT ... FOR UPDATE SKIP LOCKED` for multi-worker setups |
|
|
67
|
+
| **Test coverage** | 214 passing tests (16 skipped without Postgres), strict ruff lint |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### As a Python Library
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install djobs
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from djobs import SQLiteJobRepository, QueueService, HandlerRegistry, WorkerPool
|
|
81
|
+
|
|
82
|
+
# 1. Set up
|
|
83
|
+
repo = SQLiteJobRepository.from_path("jobs.db")
|
|
84
|
+
queue = QueueService(repo)
|
|
85
|
+
|
|
86
|
+
# 2. Submit a job
|
|
87
|
+
job = queue.submit("send_email", {"to": "user@example.com"}, max_attempts=3)
|
|
88
|
+
|
|
89
|
+
# 3. Process jobs
|
|
90
|
+
registry = HandlerRegistry()
|
|
91
|
+
registry.register("send_email", lambda payload: send_email(**payload))
|
|
92
|
+
|
|
93
|
+
pool = WorkerPool(queue, registry, worker_id="worker-1", max_concurrent=4)
|
|
94
|
+
pool.run_loop(stop_event)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### As an MCP Server (for AI Agents)
|
|
98
|
+
|
|
99
|
+
Add to `.vscode/mcp.json`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"servers": {
|
|
104
|
+
"djobs": {
|
|
105
|
+
"type": "stdio",
|
|
106
|
+
"command": "python",
|
|
107
|
+
"args": ["-m", "djobs.mcp_server"],
|
|
108
|
+
"autoApprove": [
|
|
109
|
+
"health", "resume_session", "check_task", "complete_task",
|
|
110
|
+
"fail_task", "list_tasks", "enqueue_task", "audit_log"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then any AI agent can call these MCP tools:
|
|
118
|
+
|
|
119
|
+
| Tool | Purpose |
|
|
120
|
+
|------|---------|
|
|
121
|
+
| `enqueue_task` | Submit a durable task (survives crashes) |
|
|
122
|
+
| `complete_task` | Mark task succeeded after agent finishes work |
|
|
123
|
+
| `fail_task` | Mark task failed with error message |
|
|
124
|
+
| `resume_session` | Find incomplete tasks from previous sessions |
|
|
125
|
+
| `check_task` | Inspect task status, attempts, duration |
|
|
126
|
+
| `list_tasks` | List tasks by correlation_id |
|
|
127
|
+
| `audit_log` | Query event history — "what did the AI do?" |
|
|
128
|
+
| `health` | Queue depth by status |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## How is this different from X?
|
|
133
|
+
|
|
134
|
+
djobs is not the first project to expose a task queue to an AI agent over MCP. It targets a specific combination of properties: SQLite-first, MCP-driven, with crash recovery and audit-log style observability built in.
|
|
135
|
+
|
|
136
|
+
| Project | Storage | Focus | Closest to djobs? |
|
|
137
|
+
|---------|---------|-------|-------------------|
|
|
138
|
+
| [TadMSTR/task-queue-mcp](https://github.com/TadMSTR/task-queue-mcp) | YAML files | Multi-agent task hand-off for Claude Code | Closest in spirit. Different storage model (YAML files + dispatcher), no `resume_session` / `audit_log` style observability. |
|
|
139
|
+
| [midweste/mcp-cli-gateway](https://github.com/midweste/mcp-cli-gateway) | SQLite | Routing prompts to CLI agents (Gemini / Codex / Claude) with pacing | Overlaps on persistence + observability, but the unit of work is "dispatch a prompt to a CLI", not "durable user task with retry / lease". |
|
|
140
|
+
| [j0j1j2/claude-tunnel](https://github.com/j0j1j2/claude-tunnel) | In-memory | Pub/sub + 1:1 request/reply + job queue between Claude Code sessions | Different problem: inter-session messaging, not durable work tracking. |
|
|
141
|
+
| Celery / RQ / Dramatiq / Hatchet | Redis / Postgres | General-purpose distributed task queues | Strictly more capable as general queues, but not designed to be driven directly by an AI agent over MCP. |
|
|
142
|
+
| Temporal / Inngest / DBOS | Server / SaaS | Durable workflow / execution engines | Much more powerful and much heavier; no MCP integration; not aimed at single-developer laptop use. |
|
|
143
|
+
|
|
144
|
+
In one sentence: **djobs is what you reach for when you want a small, Celery-shaped Python job queue, driven mostly by an AI agent through MCP, on a single developer machine, with SQLite.**
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Architecture
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
┌─────────────┐ MCP tools ┌──────────────┐
|
|
152
|
+
│ AI Agent │ ──────────────────> │ MCP Server │
|
|
153
|
+
│ (Copilot) │ <────────────────── │ (FastMCP) │
|
|
154
|
+
└─────────────┘ └──────┬───────┘
|
|
155
|
+
│
|
|
156
|
+
┌────────────┼────────────┐
|
|
157
|
+
│ │ │
|
|
158
|
+
┌─────▼─────┐ ┌────▼────┐ ┌────▼─────┐
|
|
159
|
+
│ Queue │ │ Daemon │ │ Audit │
|
|
160
|
+
│ Service │ │ (Pool + │ │ Log │
|
|
161
|
+
│ │ │ Sched) │ │ │
|
|
162
|
+
└─────┬─────┘ └─────────┘ └──────────┘
|
|
163
|
+
│
|
|
164
|
+
┌─────▼─────┐
|
|
165
|
+
│ SQLite │
|
|
166
|
+
│ (or PG) │
|
|
167
|
+
└───────────┘
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Job State Machine
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
pending ──────► running ──────► succeeded
|
|
174
|
+
│ │
|
|
175
|
+
│ ├──────► failed
|
|
176
|
+
│ │
|
|
177
|
+
│ ├──────► retry_scheduled ──► pending (retry)
|
|
178
|
+
│ │
|
|
179
|
+
│ └──────► dead_lettered
|
|
180
|
+
│
|
|
181
|
+
├──────► succeeded (AI agent direct complete)
|
|
182
|
+
└──────► failed (AI agent direct fail)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Module Map
|
|
186
|
+
|
|
187
|
+
| Module | Responsibility |
|
|
188
|
+
|--------|---------------|
|
|
189
|
+
| `djobs.core` | Job model, state machine, domain errors |
|
|
190
|
+
| `djobs.queue` | Submit, claim, complete, fail, retry logic |
|
|
191
|
+
| `djobs.storage` | SQLite & PostgreSQL repositories, event log |
|
|
192
|
+
| `djobs.worker` | Handler registry, WorkerPool, WorkerRunner |
|
|
193
|
+
| `djobs.scheduler` | Retry promotion, expired lease recovery |
|
|
194
|
+
| `djobs.daemon` | Composes WorkerPool + Scheduler into one process |
|
|
195
|
+
| `djobs.observability` | Metrics, structured logging, job inspection |
|
|
196
|
+
| `djobs.mcp_server` | MCP tool definitions, embedded daemon |
|
|
197
|
+
| `djobs.cli` | `djobs serve` CLI entry point |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Examples
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Basic job lifecycle
|
|
205
|
+
python examples/run_echo_job.py
|
|
206
|
+
|
|
207
|
+
# Retry with exponential backoff
|
|
208
|
+
python examples/run_retry_job.py
|
|
209
|
+
|
|
210
|
+
# Concurrent worker pool
|
|
211
|
+
python examples/run_pool_demo.py
|
|
212
|
+
|
|
213
|
+
# Scheduler loop (retry promotion + lease recovery)
|
|
214
|
+
python examples/run_scheduler_demo.py
|
|
215
|
+
|
|
216
|
+
# AI task platform (batch submit + cost tracking)
|
|
217
|
+
python examples/run_ai_demo.py
|
|
218
|
+
|
|
219
|
+
# Durable crash recovery demo
|
|
220
|
+
python examples/run_durable_demo.py
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Development
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
git clone https://github.com/jhuang-tw/djobs.git
|
|
229
|
+
cd djobs
|
|
230
|
+
python -m venv .venv && .venv/bin/activate
|
|
231
|
+
pip install -e ".[dev,mcp]"
|
|
232
|
+
|
|
233
|
+
pytest -q # 214 tests (16 skipped without Postgres)
|
|
234
|
+
ruff check src/ tests/ # lint
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Roadmap
|
|
242
|
+
|
|
243
|
+
- [x] Durable job queue with retry, lease, heartbeat
|
|
244
|
+
- [x] SQLite + PostgreSQL backends
|
|
245
|
+
- [x] Worker pool with concurrency control
|
|
246
|
+
- [x] Scheduler (retry promotion + lease recovery)
|
|
247
|
+
- [x] Event sourcing & audit trail
|
|
248
|
+
- [x] MCP server with 8 tools
|
|
249
|
+
- [x] Embedded daemon (auto-start with MCP)
|
|
250
|
+
- [x] Type isolation (daemon vs. AI agent tasks)
|
|
251
|
+
- [ ] `pip install djobs` on PyPI
|
|
252
|
+
- [ ] Async worker support
|
|
253
|
+
- [ ] Priority queues
|
|
254
|
+
- [ ] Web dashboard for audit trail
|
|
255
|
+
- [ ] Rate limiting per job type
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
[MIT](LICENSE)
|