agentexec 0.2.0rc2__tar.gz → 0.3.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.
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/CHANGELOG.md +61 -67
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/PKG-INFO +1 -1
- agentexec-0.3.0/docs/api-reference/activity.md +401 -0
- agentexec-0.3.0/docs/api-reference/core.md +532 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/api-reference/pipeline.md +8 -8
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/api-reference/runner.md +5 -5
- agentexec-0.3.0/docs/concepts/activity-tracking.md +431 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/concepts/architecture.md +63 -44
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/concepts/task-lifecycle.md +33 -27
- agentexec-0.3.0/docs/concepts/worker-pool.md +363 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/contributing.md +19 -9
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/deployment/docker.md +23 -18
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/deployment/production.md +49 -62
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/getting-started/configuration.md +30 -22
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/getting-started/installation.md +2 -2
- agentexec-0.3.0/docs/getting-started/quickstart.md +253 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/guides/basic-usage.md +112 -114
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/guides/custom-runners.md +34 -34
- agentexec-0.3.0/docs/guides/fastapi-integration.md +296 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/guides/openai-runner.md +5 -5
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/guides/pipelines.md +6 -6
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docs/index.md +23 -10
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/pyproject.toml +9 -1
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/__init__.py +6 -3
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/cli.py +8 -2
- agentexec-0.3.0/src/agentexec/core/results.py +80 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/schedule.py +0 -1
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/worker/pool.py +33 -8
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_results.py +55 -1
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_schedule.py +5 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_state.py +1 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_state_backend.py +7 -4
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_task_locking.py +0 -25
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_worker_logging.py +1 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_worker_pool.py +18 -1
- agentexec-0.3.0/tests/test_worker_resilience.py +198 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/package.json +1 -1
- agentexec-0.2.0rc2/.claude/skills/prepare-release/SKILL.md +0 -57
- agentexec-0.2.0rc2/.python-version +0 -1
- agentexec-0.2.0rc2/docs/api-reference/activity.md +0 -455
- agentexec-0.2.0rc2/docs/api-reference/core.md +0 -476
- agentexec-0.2.0rc2/docs/concepts/activity-tracking.md +0 -520
- agentexec-0.2.0rc2/docs/concepts/worker-pool.md +0 -499
- agentexec-0.2.0rc2/docs/getting-started/quickstart.md +0 -262
- agentexec-0.2.0rc2/docs/guides/fastapi-integration.md +0 -301
- agentexec-0.2.0rc2/examples/basic/debug_spawn.py +0 -30
- agentexec-0.2.0rc2/examples/basic/enqueue.py +0 -26
- agentexec-0.2.0rc2/examples/basic/entrypoint.sh +0 -2
- agentexec-0.2.0rc2/examples/basic/worker.py +0 -50
- agentexec-0.2.0rc2/examples/multi-tenancy/README.md +0 -92
- agentexec-0.2.0rc2/examples/multi-tenancy/example.py +0 -188
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/README.md +0 -147
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/alembic/README +0 -1
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/alembic/env.py +0 -82
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/alembic/script.py.mako +0 -28
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/alembic.ini +0 -148
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/compose.yml +0 -10
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/context.py +0 -21
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/db.py +0 -27
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/main.py +0 -45
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/models.py +0 -9
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/pipeline.py +0 -246
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/pyproject.toml +0 -35
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/tools.py +0 -46
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/bun.lock +0 -422
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/index.html +0 -13
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/package.json +0 -27
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/public/vite.svg +0 -4
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/App.tsx +0 -35
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/api/agents.ts +0 -37
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/api/queries.ts +0 -51
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/components/Layout.tsx +0 -38
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/index.css +0 -263
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/main.tsx +0 -10
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/pages/AgentDetailPage.tsx +0 -45
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/pages/AgentListPage.tsx +0 -68
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/src/styles/github-dark.css +0 -617
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/tsconfig.json +0 -21
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/tsconfig.node.json +0 -11
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/ui/vite.config.ts +0 -19
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/views.py +0 -118
- agentexec-0.2.0rc2/examples/openai-agents-fastapi/worker.py +0 -86
- agentexec-0.2.0rc2/examples/queue-fairness/README.md +0 -75
- agentexec-0.2.0rc2/examples/queue-fairness/run.py +0 -215
- agentexec-0.2.0rc2/research/multiprocessing.md +0 -778
- agentexec-0.2.0rc2/src/agentexec/core/results.py +0 -41
- agentexec-0.2.0rc2/ui/.gitignore +0 -3
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/.github/workflows/ci.yml +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/.github/workflows/docker-publish.yml +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/.github/workflows/npm-publish.yml +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/.github/workflows/publish.yml +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/.gitignore +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/README.md +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/RELEASE.md +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docker/Dockerfile +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docker/README.md +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docker/entrypoint.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/docker-compose.kafka.yml +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/__init__.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/events.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/handlers.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/models.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/producer.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/schemas.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/activity/status.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/config.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/core/__init__.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/core/db.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/core/queue.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/core/task.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/pipeline.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/runners/__init__.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/runners/base.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/runners/openai.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/state/__init__.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/state/base.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/state/kafka.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/state/redis.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/tracker.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/worker/__init__.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/src/agentexec/worker/event.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_activity_schemas.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_activity_tracking.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_config.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_db.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_kafka_integration.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_pipeline.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_pipeline_flow.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_public_api.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_queue.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_queue_partitions.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_runners.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_self_describing_results.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_task.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_task_types.py +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/tests/test_worker_event.py +0 -0
- {agentexec-0.2.0rc2/examples/openai-agents-fastapi → agentexec-0.3.0}/ui/.gitignore +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/README.md +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/bun.lock +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/components/ActiveAgentsBadge.tsx +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/components/ProgressBar.tsx +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/components/StatusBadge.tsx +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/components/TaskDetail.tsx +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/components/TaskList.tsx +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/components/index.ts +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/index.ts +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/src/types.ts +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/tsconfig.json +0 -0
- {agentexec-0.2.0rc2 → agentexec-0.3.0}/ui/vite.config.ts +0 -0
|
@@ -1,93 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v0.
|
|
4
|
-
|
|
5
|
-
Second release candidate for 0.2.0. Completes the async migration and
|
|
6
|
-
stabilizes the worker pool architecture.
|
|
3
|
+
## v0.3.0
|
|
7
4
|
|
|
8
5
|
### Breaking Changes
|
|
9
6
|
|
|
10
|
-
**
|
|
11
|
-
-
|
|
12
|
-
- Database URLs must use async drivers (e.g. `sqlite+aiosqlite://`, `postgresql+asyncpg://`)
|
|
13
|
-
- `sqlalchemy[asyncio]` is now a core dependency; `aiosqlite` added as dev dependency
|
|
14
|
-
|
|
15
|
-
**Async activity query API**
|
|
16
|
-
- `activity.list()`, `activity.detail()`, and `activity.count_active()` are now async and accept `AsyncSession`
|
|
17
|
-
- Activity handlers are now async (`async def __call__`)
|
|
18
|
-
|
|
19
|
-
**Removed `session` parameter from activity mutations**
|
|
20
|
-
- `activity.create()`, `activity.update()`, `activity.complete()`, and `activity.error()` no longer accept a `session` parameter — the handler owns its own session lifecycle
|
|
21
|
-
|
|
22
|
-
**Queue priority parameter**
|
|
23
|
-
- `BaseQueueBackend.push()` signature changed from `high_priority: bool` to `priority: Priority | None`
|
|
24
|
-
- Affects Redis, Kafka, and any custom queue backend implementations
|
|
7
|
+
**Permanent task failure raises `TaskFailedError`**
|
|
8
|
+
- Callers that previously relied on `TimeoutError` to detect a task that exhausted its retries now receive `ax.TaskFailedError` as soon as the failure is recorded
|
|
25
9
|
|
|
26
10
|
### New Features
|
|
27
11
|
|
|
28
|
-
**
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- `
|
|
33
|
-
|
|
34
|
-
**Async engine disposal**
|
|
35
|
-
- `dispose_engine()` ensures the async engine's background threads exit cleanly on shutdown
|
|
36
|
-
|
|
37
|
-
### Architecture Changes
|
|
38
|
-
|
|
39
|
-
**Worker pool refactor**
|
|
40
|
-
- Workers now use the `spawn` multiprocessing start method with explicit context — no inherited state
|
|
41
|
-
- `StateEvent` replaced with stdlib `multiprocessing.Event` — removes dependency on the state backend for shutdown coordination
|
|
42
|
-
- Event handling and scheduling extracted into `_EventHandler` and `_Scheduler` classes
|
|
43
|
-
- Worker processes are now daemonic with `SIGINT` ignored — clean shutdown driven by the event
|
|
44
|
-
- `pool.start()` handles `CancelledError` directly for Ctrl+C shutdown
|
|
45
|
-
|
|
46
|
-
**Logging overhaul**
|
|
47
|
-
- Removed `worker/logging.py` and `core/logging.py` — all modules use stdlib `logging.getLogger(__name__)`
|
|
48
|
-
- Spawned workers bootstrap a `StreamHandler` on the root logger so logs reach stderr
|
|
49
|
-
- Pool messages use `logger.info`/`logger.error` instead of `print()`
|
|
50
|
-
|
|
51
|
-
### Bug Fixes
|
|
52
|
-
|
|
53
|
-
- Fixed crash when worker receives a task for an unregistered task name (now logs error and skips)
|
|
54
|
-
- Failed tasks now log full tracebacks via `logger.exception` instead of `logger.error`
|
|
55
|
-
- Kafka consumer handles `None` message values without crashing
|
|
56
|
-
- `ActivityUpdated.status` is now `Status` enum instead of raw string
|
|
57
|
-
- `raise e` instead of bare `raise` in task execution for clearer tracebacks
|
|
12
|
+
**Terminal failure contract for `ax.gather` / `ax.get_result`**
|
|
13
|
+
- When a task exhausts its retries, the pool stores a `TaskFailure` record at the task's result key (same convention and TTL as success results)
|
|
14
|
+
- `ax.get_result` raises `ax.TaskFailedError` — carrying `task_name`, `agent_id`, `error`, and `attempts` — on its next poll (~0.5s) instead of polling until the timeout
|
|
15
|
+
- `ax.gather` fails fast on the first permanent failure; remaining tasks keep running and their results stay retrievable, so re-gathering survivors resolves instantly
|
|
16
|
+
- `TaskFailedError` is exported on the `ax` namespace
|
|
58
17
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- Dependency groups use PEP 735 `[dependency-groups]` instead of `[tool.uv]`
|
|
62
|
-
- Ruff line-length increased to 110
|
|
63
|
-
- Removed verbose docstring examples and redundant comments throughout activity models
|
|
64
|
-
- `selectinload` for eager loading of activity logs in `get_by_agent_id`
|
|
65
|
-
- Redis backend adds `type: ignore` annotations for redis-py async stubs
|
|
18
|
+
## v0.2.0
|
|
66
19
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
First release candidate for 0.2.0. Major refactor of the backend, queue,
|
|
70
|
-
activity, and worker systems.
|
|
20
|
+
Major refactor of the backend, queue, activity, worker, and database layers.
|
|
21
|
+
If you're upgrading from 0.1.x, read the **Breaking Changes** section closely.
|
|
71
22
|
|
|
72
23
|
### Breaking Changes
|
|
73
24
|
|
|
74
|
-
**
|
|
75
|
-
- `
|
|
76
|
-
-
|
|
25
|
+
**Fully async database layer**
|
|
26
|
+
- `configure_engine()` and `get_session()` now require an async SQLAlchemy engine (`AsyncEngine`) and return `AsyncSession`
|
|
27
|
+
- Database URLs must use async drivers (e.g. `sqlite+aiosqlite://`, `postgresql+asyncpg://`)
|
|
28
|
+
- `sqlalchemy[asyncio]` is now a core dependency
|
|
77
29
|
|
|
78
30
|
**Async activity API**
|
|
79
|
-
-
|
|
31
|
+
- All activity functions are async: `await ax.activity.create(...)`, `await ax.activity.update(...)`, `await ax.activity.complete(...)`, `await ax.activity.error(...)`
|
|
32
|
+
- `activity.list()`, `activity.detail()`, and `activity.count_active()` are async and accept `AsyncSession`
|
|
33
|
+
- Activity handlers are async (`async def __call__`)
|
|
34
|
+
- The `session` parameter was removed from activity mutations — the handler owns its own session lifecycle
|
|
35
|
+
|
|
36
|
+
**Pool entry point**
|
|
37
|
+
- `pool.run()` was removed. Use `await pool.start()` in an asyncio loop, or the new `agentexec run mymodule:pool` CLI
|
|
38
|
+
- `AGENTEXEC_QUEUE_NAME` renamed to `AGENTEXEC_QUEUE_PREFIX` (old name still accepted as alias)
|
|
39
|
+
- `agentexec.state.redis_backend` renamed to `agentexec.state.redis` — update `AGENTEXEC_STATE_BACKEND` if set explicitly
|
|
80
40
|
|
|
81
41
|
**Task context serialization**
|
|
82
42
|
- `Task.context` is now `Mapping[str, Any]` (raw dict), not a typed BaseModel — hydration happens at execution time
|
|
83
43
|
- `Task.create()` is now async
|
|
84
44
|
|
|
45
|
+
**Queue backend protocol**
|
|
46
|
+
- `BaseQueueBackend.push()` signature changed from `high_priority: bool` to `priority: Priority | None` — affects Redis, Kafka, and any custom queue backend
|
|
47
|
+
|
|
85
48
|
**Removed APIs**
|
|
86
49
|
- `set_global_session`/`get_global_session`/`remove_global_session` — use `configure_engine`/`get_session`
|
|
87
50
|
- `state.backend.publish`/`subscribe` (pubsub), `index_add`/`index_range`/`index_remove`, `clear`, `configure`
|
|
51
|
+
- `worker/logging.py` and `core/logging.py` — all modules use stdlib `logging.getLogger(__name__)` directly
|
|
88
52
|
|
|
89
53
|
### New Features
|
|
90
54
|
|
|
55
|
+
**CLI entrypoint**
|
|
56
|
+
- New `agentexec` CLI command: `agentexec run mymodule:pool --create-tables --workers 4`
|
|
57
|
+
|
|
91
58
|
**Partitioned Redis queues**
|
|
92
59
|
- Tasks with `lock_key` route to dedicated partition queues with per-partition locking and SCAN-based fair dequeue
|
|
93
60
|
|
|
@@ -101,18 +68,45 @@ activity, and worker systems.
|
|
|
101
68
|
- `pip install agentexec[kafka]` for queue and schedule via Kafka
|
|
102
69
|
|
|
103
70
|
**Typed worker IPC**
|
|
104
|
-
- `TaskFailed
|
|
71
|
+
- `TaskFailed` and `ActivityEvent` messages flow over `multiprocessing.Queue` with pydantic validation
|
|
105
72
|
|
|
106
73
|
**Schedule composite keys**
|
|
107
74
|
- `{task_name}:{cron}:{context_hash}` for unique schedule identity
|
|
108
75
|
|
|
109
|
-
|
|
76
|
+
**Activity model `create()` classmethod**
|
|
77
|
+
- `Activity.create()` encapsulates record + initial log entry creation in one async call
|
|
78
|
+
|
|
79
|
+
**Async engine disposal**
|
|
80
|
+
- `dispose_engine()` ensures the async engine's background threads exit cleanly on shutdown
|
|
110
81
|
|
|
82
|
+
### Architecture Changes
|
|
83
|
+
|
|
84
|
+
**Worker pool refactor**
|
|
85
|
+
- Workers use the `spawn` multiprocessing start method with explicit context — no inherited state
|
|
86
|
+
- Event handling and scheduling extracted into `_EventHandler` and `_Scheduler` classes
|
|
87
|
+
- `StateEvent` replaced with stdlib `multiprocessing.Event` — removes dependency on the state backend for shutdown coordination
|
|
111
88
|
- Class-based backend architecture with ABCs (`BaseStateBackend`, `BaseQueueBackend`, `BaseScheduleBackend`)
|
|
112
89
|
- `Task` is pure data, `TaskDefinition` owns behavior
|
|
113
|
-
- Session management via `configure_engine`/`get_session` (Pool owns the engine)
|
|
114
90
|
- Status enum extracted to `activity/status.py` (no SQLAlchemy dependency)
|
|
115
91
|
|
|
92
|
+
**Logging**
|
|
93
|
+
- All modules use stdlib `logging.getLogger(__name__)`
|
|
94
|
+
- Spawned workers bootstrap a `StreamHandler` on the root logger so logs reach stderr
|
|
95
|
+
- Pool messages use `logger.info`/`logger.error` instead of `print()`
|
|
96
|
+
|
|
97
|
+
### Bug Fixes
|
|
98
|
+
|
|
99
|
+
- **Orphaned worker processes on shutdown.** SIGTERM (systemd/docker stop), SIGKILL, and SIGHUP were leaving worker processes running. Fixed via an asyncio SIGTERM handler in the CLI and `prctl(PR_SET_PDEATHSIG)` in each worker so the kernel terminates workers when the pool dies
|
|
100
|
+
- **Worker and scheduler error loops throttled.** Infra failures (e.g. Redis unreachable) were producing 100k+ log lines per second. Added a 1s sleep after outer-loop exceptions
|
|
101
|
+
- **Unregistered task name crash.** Worker now logs an error and skips instead of crashing when it receives a task for an unknown name
|
|
102
|
+
- Failed tasks now log full tracebacks via `logger.exception` instead of `logger.error`
|
|
103
|
+
- Kafka consumer handles `None` message values without crashing
|
|
104
|
+
- `ActivityUpdated.status` is a `Status` enum instead of raw string
|
|
105
|
+
|
|
106
|
+
### Documentation
|
|
107
|
+
|
|
108
|
+
- Full documentation sweep for the async API — connection strings, CLI usage, `await` on activity calls across all guides and API references
|
|
109
|
+
|
|
116
110
|
## v0.1.7
|
|
117
111
|
|
|
118
112
|
### New Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentexec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Production-ready orchestration for OpenAI Agents with Redis-backed coordination, activity tracking, and workflow management
|
|
5
5
|
Project-URL: Homepage, https://github.com/Agent-CI/agentexec
|
|
6
6
|
Project-URL: Documentation, https://github.com/Agent-CI/agentexec#readme
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Activity API Reference
|
|
2
|
+
|
|
3
|
+
This document covers the activity tracking API for monitoring task execution.
|
|
4
|
+
|
|
5
|
+
All activity functions are `async` and operate on `AsyncSession` from
|
|
6
|
+
`sqlalchemy.ext.asyncio`. The session parameter is optional — when omitted,
|
|
7
|
+
functions fall back to `get_session()` from `agentexec.core.db`, which
|
|
8
|
+
requires `configure_engine()` to have been called.
|
|
9
|
+
|
|
10
|
+
## Module: agentexec.activity
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import agentexec as ax
|
|
14
|
+
|
|
15
|
+
# Lifecycle (async)
|
|
16
|
+
await ax.activity.create(task_name, message)
|
|
17
|
+
await ax.activity.update(agent_id, message)
|
|
18
|
+
await ax.activity.complete(agent_id)
|
|
19
|
+
await ax.activity.error(agent_id)
|
|
20
|
+
await ax.activity.cancel_pending()
|
|
21
|
+
|
|
22
|
+
# Query (async)
|
|
23
|
+
await ax.activity.list()
|
|
24
|
+
await ax.activity.detail(agent_id=...)
|
|
25
|
+
await ax.activity.count_active()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## create()
|
|
31
|
+
|
|
32
|
+
Create a new activity record with an initial `QUEUED` log entry.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
async def create(
|
|
36
|
+
task_name: str,
|
|
37
|
+
message: str = "Agent queued",
|
|
38
|
+
agent_id: str | uuid.UUID | None = None,
|
|
39
|
+
metadata: dict[str, Any] | None = None,
|
|
40
|
+
) -> uuid.UUID
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Parameters
|
|
44
|
+
|
|
45
|
+
| Parameter | Type | Default | Description |
|
|
46
|
+
|-----------|------|---------|-------------|
|
|
47
|
+
| `task_name` | `str` | required | Name of the task |
|
|
48
|
+
| `message` | `str` | `"Agent queued"` | Initial log message |
|
|
49
|
+
| `agent_id` | `str \| UUID \| None` | `None` | Pre-generated ID (auto-generated if omitted) |
|
|
50
|
+
| `metadata` | `dict \| None` | `None` | Arbitrary metadata attached to the activity |
|
|
51
|
+
|
|
52
|
+
### Returns
|
|
53
|
+
|
|
54
|
+
`uuid.UUID` — the activity's `agent_id`.
|
|
55
|
+
|
|
56
|
+
### Example
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import agentexec as ax
|
|
60
|
+
|
|
61
|
+
agent_id = await ax.activity.create(
|
|
62
|
+
task_name="my_task",
|
|
63
|
+
message="Task created",
|
|
64
|
+
metadata={"organization_id": "org-123"},
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Usually called automatically by `ax.enqueue()`. Call it directly only for
|
|
69
|
+
advanced use cases where you need to register an activity outside the normal
|
|
70
|
+
enqueue flow.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## update()
|
|
75
|
+
|
|
76
|
+
Append a log entry to an existing activity.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
async def update(
|
|
80
|
+
agent_id: str | uuid.UUID,
|
|
81
|
+
message: str,
|
|
82
|
+
percentage: int | None = None,
|
|
83
|
+
status: Status | None = None,
|
|
84
|
+
) -> bool
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Parameters
|
|
88
|
+
|
|
89
|
+
| Parameter | Type | Default | Description |
|
|
90
|
+
|-----------|------|---------|-------------|
|
|
91
|
+
| `agent_id` | `str \| UUID` | required | Activity identifier |
|
|
92
|
+
| `message` | `str` | required | Log message |
|
|
93
|
+
| `percentage` | `int \| None` | `None` | Progress (0-100) |
|
|
94
|
+
| `status` | `Status \| None` | `None` | Status override (defaults to `RUNNING`) |
|
|
95
|
+
|
|
96
|
+
### Example
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# Progress update
|
|
100
|
+
await ax.activity.update(agent_id, "Processing data...", percentage=30)
|
|
101
|
+
|
|
102
|
+
# Explicit status
|
|
103
|
+
from agentexec.activity import Status
|
|
104
|
+
await ax.activity.update(agent_id, "Starting", status=Status.RUNNING)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## complete()
|
|
110
|
+
|
|
111
|
+
Mark an activity as successfully completed.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
async def complete(
|
|
115
|
+
agent_id: str | uuid.UUID,
|
|
116
|
+
message: str = "Agent completed",
|
|
117
|
+
percentage: int = 100,
|
|
118
|
+
) -> bool
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Example
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
await ax.activity.complete(agent_id)
|
|
125
|
+
await ax.activity.complete(agent_id, "Partial completion", percentage=75)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## error()
|
|
131
|
+
|
|
132
|
+
Mark an activity as failed.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
async def error(
|
|
136
|
+
agent_id: str | uuid.UUID,
|
|
137
|
+
message: str = "Agent failed",
|
|
138
|
+
percentage: int = 100,
|
|
139
|
+
) -> bool
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Example
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
try:
|
|
146
|
+
await risky_operation()
|
|
147
|
+
except Exception as e:
|
|
148
|
+
await ax.activity.error(agent_id, f"Failed: {e}")
|
|
149
|
+
raise
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## cancel_pending()
|
|
155
|
+
|
|
156
|
+
Cancel all queued and running activities. Typically called at pool startup
|
|
157
|
+
to reconcile state from a previous run.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
async def cancel_pending(
|
|
161
|
+
session: AsyncSession | None = None,
|
|
162
|
+
) -> int
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Returns
|
|
166
|
+
|
|
167
|
+
`int` — number of activities canceled.
|
|
168
|
+
|
|
169
|
+
### Example
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from agentexec.core.db import get_session
|
|
173
|
+
|
|
174
|
+
async with get_session() as db:
|
|
175
|
+
canceled = await ax.activity.cancel_pending(session=db)
|
|
176
|
+
print(f"Canceled {canceled} activities")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## list()
|
|
182
|
+
|
|
183
|
+
Get a paginated list of activities.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
async def list(
|
|
187
|
+
session: AsyncSession | None = None,
|
|
188
|
+
page: int = 1,
|
|
189
|
+
page_size: int = 50,
|
|
190
|
+
metadata_filter: dict[str, Any] | None = None,
|
|
191
|
+
) -> ActivityListSchema
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Parameters
|
|
195
|
+
|
|
196
|
+
| Parameter | Type | Default | Description |
|
|
197
|
+
|-----------|------|---------|-------------|
|
|
198
|
+
| `session` | `AsyncSession \| None` | `None` | Session (falls back to `get_session()`) |
|
|
199
|
+
| `page` | `int` | `1` | Page number (1-indexed) |
|
|
200
|
+
| `page_size` | `int` | `50` | Items per page |
|
|
201
|
+
| `metadata_filter` | `dict \| None` | `None` | Filter by metadata fields |
|
|
202
|
+
|
|
203
|
+
### Example
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
result = await ax.activity.list(page=1, page_size=20)
|
|
207
|
+
|
|
208
|
+
print(f"Total: {result.total}")
|
|
209
|
+
print(f"Page {result.page}, total_pages: {result.total_pages}")
|
|
210
|
+
|
|
211
|
+
for item in result.items:
|
|
212
|
+
print(f"{item.agent_id}: {item.status}")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### With Metadata Filter
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Only activities for org-123
|
|
219
|
+
result = await ax.activity.list(
|
|
220
|
+
metadata_filter={"organization_id": "org-123"},
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## detail()
|
|
227
|
+
|
|
228
|
+
Get detailed activity with full log history.
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
async def detail(
|
|
232
|
+
session: AsyncSession | None = None,
|
|
233
|
+
agent_id: str | uuid.UUID | None = None,
|
|
234
|
+
metadata_filter: dict[str, Any] | None = None,
|
|
235
|
+
) -> ActivityDetailSchema | None
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Returns
|
|
239
|
+
|
|
240
|
+
`ActivityDetailSchema | None` — activity details, or `None` if not found (or
|
|
241
|
+
the metadata filter didn't match).
|
|
242
|
+
|
|
243
|
+
### Example
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
activity = await ax.activity.detail(agent_id=agent_id)
|
|
247
|
+
|
|
248
|
+
if activity:
|
|
249
|
+
print(f"Task: {activity.agent_type}")
|
|
250
|
+
for log in activity.logs:
|
|
251
|
+
print(f" [{log.created_at}] {log.status} {log.message}")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## count_active()
|
|
257
|
+
|
|
258
|
+
Return the number of queued or running activities.
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
async def count_active(
|
|
262
|
+
session: AsyncSession | None = None,
|
|
263
|
+
) -> int
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Status Enum
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
class Status(str, Enum):
|
|
272
|
+
QUEUED = "QUEUED"
|
|
273
|
+
RUNNING = "RUNNING"
|
|
274
|
+
COMPLETE = "COMPLETE"
|
|
275
|
+
ERROR = "ERROR"
|
|
276
|
+
CANCELED = "CANCELED"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
| Status | Description |
|
|
280
|
+
|--------|-------------|
|
|
281
|
+
| `QUEUED` | Task is waiting in queue |
|
|
282
|
+
| `RUNNING` | Task is being executed |
|
|
283
|
+
| `COMPLETE` | Task finished successfully |
|
|
284
|
+
| `ERROR` | Task failed with error |
|
|
285
|
+
| `CANCELED` | Task was canceled |
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Response Schemas
|
|
290
|
+
|
|
291
|
+
### ActivityLogSchema
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
class ActivityLogSchema(BaseModel):
|
|
295
|
+
id: uuid.UUID
|
|
296
|
+
message: str
|
|
297
|
+
status: Status
|
|
298
|
+
percentage: int | None = 0
|
|
299
|
+
created_at: datetime
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### ActivityDetailSchema
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
class ActivityDetailSchema(BaseModel):
|
|
306
|
+
id: uuid.UUID | None
|
|
307
|
+
agent_id: uuid.UUID
|
|
308
|
+
agent_type: str
|
|
309
|
+
created_at: datetime
|
|
310
|
+
updated_at: datetime
|
|
311
|
+
logs: list[ActivityLogSchema]
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### ActivityListItemSchema
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
class ActivityListItemSchema(BaseModel):
|
|
318
|
+
agent_id: uuid.UUID
|
|
319
|
+
agent_type: str
|
|
320
|
+
status: Status
|
|
321
|
+
latest_log_message: str | None
|
|
322
|
+
latest_log_timestamp: datetime | None
|
|
323
|
+
percentage: int | None
|
|
324
|
+
started_at: datetime | None
|
|
325
|
+
elapsed_time_seconds: int # computed
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### ActivityListSchema
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
class ActivityListSchema(BaseModel):
|
|
332
|
+
items: list[ActivityListItemSchema]
|
|
333
|
+
total: int
|
|
334
|
+
page: int
|
|
335
|
+
page_size: int
|
|
336
|
+
total_pages: int # computed
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## ORM Models
|
|
342
|
+
|
|
343
|
+
For advanced queries, use the ORM models directly with an `AsyncSession`.
|
|
344
|
+
|
|
345
|
+
### Activity
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
class Activity(Base):
|
|
349
|
+
__tablename__ = "{prefix}activity"
|
|
350
|
+
|
|
351
|
+
id: Mapped[UUID]
|
|
352
|
+
agent_id: Mapped[UUID] # Unique, indexed
|
|
353
|
+
agent_type: Mapped[str | None]
|
|
354
|
+
created_at: Mapped[datetime]
|
|
355
|
+
updated_at: Mapped[datetime]
|
|
356
|
+
metadata_: Mapped[dict | None] # Column is "metadata"
|
|
357
|
+
logs: Mapped[list[ActivityLog]]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### Classmethods (all async)
|
|
361
|
+
|
|
362
|
+
- `Activity.create(session, agent_id, task_name, message, metadata=None)`
|
|
363
|
+
- `Activity.append_log(session, agent_id, message, status, percentage=None)`
|
|
364
|
+
- `Activity.get_by_agent_id(session, agent_id, metadata_filter=None)`
|
|
365
|
+
- `Activity.get_list(session, page=1, page_size=50, metadata_filter=None)`
|
|
366
|
+
- `Activity.get_pending_ids(session)`
|
|
367
|
+
- `Activity.get_active_count(session)`
|
|
368
|
+
|
|
369
|
+
### ActivityLog
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
class ActivityLog(Base):
|
|
373
|
+
__tablename__ = "{prefix}activity_log"
|
|
374
|
+
|
|
375
|
+
id: Mapped[UUID]
|
|
376
|
+
activity_id: Mapped[UUID]
|
|
377
|
+
message: Mapped[str]
|
|
378
|
+
status: Mapped[Status]
|
|
379
|
+
percentage: Mapped[int | None]
|
|
380
|
+
created_at: Mapped[datetime]
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Direct ORM Usage
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from sqlalchemy import select
|
|
389
|
+
from agentexec.activity.models import Activity, ActivityLog, Status
|
|
390
|
+
from agentexec.core.db import get_session
|
|
391
|
+
|
|
392
|
+
async with get_session() as db:
|
|
393
|
+
# Latest log for a given agent
|
|
394
|
+
result = await db.execute(
|
|
395
|
+
select(ActivityLog)
|
|
396
|
+
.where(ActivityLog.activity_id == activity_id)
|
|
397
|
+
.order_by(ActivityLog.created_at.desc())
|
|
398
|
+
.limit(1)
|
|
399
|
+
)
|
|
400
|
+
latest = result.scalar_one_or_none()
|
|
401
|
+
```
|