agentexec 0.2.0rc1__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.0rc1 → agentexec-0.3.0}/CHANGELOG.md +107 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/PKG-INFO +2 -2
- agentexec-0.3.0/RELEASE.md +91 -0
- 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.0rc1 → agentexec-0.3.0}/docs/api-reference/pipeline.md +8 -8
- {agentexec-0.2.0rc1 → 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.0rc1 → agentexec-0.3.0}/docs/concepts/architecture.md +63 -44
- {agentexec-0.2.0rc1 → 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.0rc1 → agentexec-0.3.0}/docs/contributing.md +19 -9
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/deployment/docker.md +23 -18
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/deployment/production.md +49 -62
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/getting-started/configuration.md +30 -22
- {agentexec-0.2.0rc1 → 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.0rc1 → agentexec-0.3.0}/docs/guides/basic-usage.md +112 -114
- {agentexec-0.2.0rc1 → 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.0rc1 → agentexec-0.3.0}/docs/guides/openai-runner.md +5 -5
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/guides/pipelines.md +6 -6
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/index.md +23 -10
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/pyproject.toml +17 -5
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/__init__.py +6 -3
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/__init__.py +76 -57
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/events.py +9 -4
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/handlers.py +28 -37
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/models.py +106 -120
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/producer.py +15 -24
- agentexec-0.3.0/src/agentexec/cli.py +162 -0
- agentexec-0.3.0/src/agentexec/core/db.py +65 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/core/queue.py +3 -3
- agentexec-0.3.0/src/agentexec/core/results.py +80 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/core/task.py +1 -1
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/schedule.py +3 -3
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/base.py +2 -1
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/kafka.py +8 -2
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/redis.py +25 -17
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/worker/pool.py +246 -172
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_activity_tracking.py +80 -90
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_db.py +10 -9
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_public_api.py +2 -2
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_queue_partitions.py +2 -1
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_results.py +55 -1
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_runners.py +10 -11
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_schedule.py +9 -4
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_state.py +1 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_state_backend.py +7 -4
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_task.py +2 -2
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_task_locking.py +2 -27
- agentexec-0.3.0/tests/test_worker_logging.py +440 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_worker_pool.py +64 -96
- agentexec-0.3.0/tests/test_worker_resilience.py +198 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/package.json +1 -1
- agentexec-0.2.0rc1/.claude/skills/prepare-release/SKILL.md +0 -57
- agentexec-0.2.0rc1/docs/api-reference/activity.md +0 -455
- agentexec-0.2.0rc1/docs/api-reference/core.md +0 -476
- agentexec-0.2.0rc1/docs/concepts/activity-tracking.md +0 -520
- agentexec-0.2.0rc1/docs/concepts/worker-pool.md +0 -499
- agentexec-0.2.0rc1/docs/getting-started/quickstart.md +0 -262
- agentexec-0.2.0rc1/docs/guides/fastapi-integration.md +0 -301
- agentexec-0.2.0rc1/examples/multi-tenancy/README.md +0 -92
- agentexec-0.2.0rc1/examples/multi-tenancy/example.py +0 -188
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/README.md +0 -147
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic/README +0 -1
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic/env.py +0 -82
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic/script.py.mako +0 -28
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic.ini +0 -148
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/compose.yml +0 -10
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/context.py +0 -21
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/db.py +0 -27
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/main.py +0 -45
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/models.py +0 -9
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/pipeline.py +0 -246
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/pyproject.toml +0 -35
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/tools.py +0 -46
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/bun.lock +0 -422
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/index.html +0 -13
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/package.json +0 -27
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/public/vite.svg +0 -4
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/App.tsx +0 -35
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/api/agents.ts +0 -37
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/api/queries.ts +0 -51
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/components/Layout.tsx +0 -38
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/index.css +0 -263
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/main.tsx +0 -10
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/pages/AgentDetailPage.tsx +0 -45
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/pages/AgentListPage.tsx +0 -68
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/styles/github-dark.css +0 -617
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/tsconfig.json +0 -21
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/tsconfig.node.json +0 -11
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/vite.config.ts +0 -19
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/views.py +0 -118
- agentexec-0.2.0rc1/examples/openai-agents-fastapi/worker.py +0 -86
- agentexec-0.2.0rc1/examples/queue-fairness/README.md +0 -75
- agentexec-0.2.0rc1/examples/queue-fairness/run.py +0 -215
- agentexec-0.2.0rc1/src/agentexec/core/db.py +0 -52
- agentexec-0.2.0rc1/src/agentexec/core/logging.py +0 -27
- agentexec-0.2.0rc1/src/agentexec/core/results.py +0 -40
- agentexec-0.2.0rc1/src/agentexec/worker/logging.py +0 -84
- agentexec-0.2.0rc1/tests/test_activity_tracking.py.bak +0 -427
- agentexec-0.2.0rc1/tests/test_worker_logging.py +0 -248
- agentexec-0.2.0rc1/ui/.gitignore +0 -3
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/ci.yml +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/docker-publish.yml +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/npm-publish.yml +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/publish.yml +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/.gitignore +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/README.md +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker/Dockerfile +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker/README.md +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker/entrypoint.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker-compose.kafka.yml +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/schemas.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/status.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/config.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/core/__init__.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/pipeline.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/runners/__init__.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/runners/base.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/runners/openai.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/__init__.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/tracker.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/worker/__init__.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/worker/event.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_activity_schemas.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_config.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_kafka_integration.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_pipeline.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_pipeline_flow.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_queue.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_self_describing_results.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_task_types.py +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_worker_event.py +0 -0
- {agentexec-0.2.0rc1/examples/openai-agents-fastapi → agentexec-0.3.0}/ui/.gitignore +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/README.md +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/bun.lock +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/ActiveAgentsBadge.tsx +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/ProgressBar.tsx +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/StatusBadge.tsx +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/TaskDetail.tsx +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/TaskList.tsx +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/index.ts +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/index.ts +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/types.ts +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/tsconfig.json +0 -0
- {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/vite.config.ts +0 -0
|
@@ -1,5 +1,112 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
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
|
|
9
|
+
|
|
10
|
+
### New Features
|
|
11
|
+
|
|
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
|
|
17
|
+
|
|
18
|
+
## v0.2.0
|
|
19
|
+
|
|
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.
|
|
22
|
+
|
|
23
|
+
### Breaking Changes
|
|
24
|
+
|
|
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
|
|
29
|
+
|
|
30
|
+
**Async activity API**
|
|
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
|
|
40
|
+
|
|
41
|
+
**Task context serialization**
|
|
42
|
+
- `Task.context` is now `Mapping[str, Any]` (raw dict), not a typed BaseModel — hydration happens at execution time
|
|
43
|
+
- `Task.create()` is now async
|
|
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
|
+
|
|
48
|
+
**Removed APIs**
|
|
49
|
+
- `set_global_session`/`get_global_session`/`remove_global_session` — use `configure_engine`/`get_session`
|
|
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
|
|
52
|
+
|
|
53
|
+
### New Features
|
|
54
|
+
|
|
55
|
+
**CLI entrypoint**
|
|
56
|
+
- New `agentexec` CLI command: `agentexec run mymodule:pool --create-tables --workers 4`
|
|
57
|
+
|
|
58
|
+
**Partitioned Redis queues**
|
|
59
|
+
- Tasks with `lock_key` route to dedicated partition queues with per-partition locking and SCAN-based fair dequeue
|
|
60
|
+
|
|
61
|
+
**Activity handler pattern**
|
|
62
|
+
- Pluggable persistence via `PostgresHandler` (default) and `IPCHandler` (worker processes)
|
|
63
|
+
|
|
64
|
+
**Task retry**
|
|
65
|
+
- Failed tasks requeue as high priority with `AGENTEXEC_MAX_TASK_RETRIES` (default 3)
|
|
66
|
+
|
|
67
|
+
**Kafka backend (experimental)**
|
|
68
|
+
- `pip install agentexec[kafka]` for queue and schedule via Kafka
|
|
69
|
+
|
|
70
|
+
**Typed worker IPC**
|
|
71
|
+
- `TaskFailed` and `ActivityEvent` messages flow over `multiprocessing.Queue` with pydantic validation
|
|
72
|
+
|
|
73
|
+
**Schedule composite keys**
|
|
74
|
+
- `{task_name}:{cron}:{context_hash}` for unique schedule identity
|
|
75
|
+
|
|
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
|
|
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
|
|
88
|
+
- Class-based backend architecture with ABCs (`BaseStateBackend`, `BaseQueueBackend`, `BaseScheduleBackend`)
|
|
89
|
+
- `Task` is pure data, `TaskDefinition` owns behavior
|
|
90
|
+
- Status enum extracted to `activity/status.py` (no SQLAlchemy dependency)
|
|
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
|
+
|
|
3
110
|
## v0.1.7
|
|
4
111
|
|
|
5
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
|
|
@@ -21,7 +21,7 @@ Requires-Dist: openai-agents>=0.1.0
|
|
|
21
21
|
Requires-Dist: pydantic-settings>=2.5.0
|
|
22
22
|
Requires-Dist: pydantic>=2.12.0
|
|
23
23
|
Requires-Dist: redis>=7.0.1
|
|
24
|
-
Requires-Dist: sqlalchemy>=2.0.44
|
|
24
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.44
|
|
25
25
|
Provides-Extra: kafka
|
|
26
26
|
Requires-Dist: aiokafka>=0.11.0; extra == 'kafka'
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Release Process
|
|
2
|
+
|
|
3
|
+
This document describes how to publish a new release of agentexec.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Creating a **GitHub Release** triggers three publish workflows automatically:
|
|
8
|
+
|
|
9
|
+
| Workflow | Target | Secret Required |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `publish.yml` | PyPI | `PYPI_API_TOKEN` |
|
|
12
|
+
| `docker-publish.yml` | ghcr.io (`agentexec-worker`) | `GITHUB_TOKEN` (built-in) |
|
|
13
|
+
| `npm-publish.yml` | npm (`agentexec-ui`) | `NPM_TOKEN` |
|
|
14
|
+
|
|
15
|
+
Docker and npm publishing can also be triggered manually via workflow dispatch.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1. Ensure CI is green
|
|
20
|
+
|
|
21
|
+
Push all changes to `main` and verify the CI workflow passes (unit tests on
|
|
22
|
+
Python 3.12/3.13 and Kafka integration tests).
|
|
23
|
+
|
|
24
|
+
### 2. Bump the version
|
|
25
|
+
|
|
26
|
+
Update the version in `pyproject.toml`:
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
version = "X.Y.Z" # stable release
|
|
30
|
+
version = "X.Y.ZrcN" # release candidate
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For the UI package, also update `ui/package.json`.
|
|
34
|
+
|
|
35
|
+
Commit and push:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git add pyproject.toml
|
|
39
|
+
git commit -m "Bump version to X.Y.Z"
|
|
40
|
+
git push
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Update CHANGELOG.md
|
|
44
|
+
|
|
45
|
+
Add a new section at the top of `CHANGELOG.md` with release notes covering
|
|
46
|
+
breaking changes, new features, improvements, bug fixes, and testing updates.
|
|
47
|
+
|
|
48
|
+
### 4. Tag the release
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git tag vX.Y.Z
|
|
52
|
+
git push origin vX.Y.Z
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 5. Create the GitHub Release
|
|
56
|
+
|
|
57
|
+
This is what triggers the publish workflows.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
gh release create vX.Y.Z --title "vX.Y.Z" --notes-file CHANGELOG.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For release candidates, mark as a pre-release:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
gh release create vX.Y.ZrcN --title "vX.Y.ZrcN" --generate-notes --prerelease
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 6. Verify
|
|
70
|
+
|
|
71
|
+
Check that all three publish workflows succeeded:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
gh run list --limit 5
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **PyPI**: https://pypi.org/project/agentexec/
|
|
78
|
+
- **Docker**: https://ghcr.io/agent-ci/agentexec-worker
|
|
79
|
+
- **npm**: https://www.npmjs.com/package/agentexec-ui
|
|
80
|
+
|
|
81
|
+
## Manual Publishing
|
|
82
|
+
|
|
83
|
+
Docker and npm workflows support manual dispatch for one-off builds:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Docker with a custom tag
|
|
87
|
+
gh workflow run docker-publish.yml -f tag=dev
|
|
88
|
+
|
|
89
|
+
# npm with a version override
|
|
90
|
+
gh workflow run npm-publish.yml -f version=0.2.0-beta.1
|
|
91
|
+
```
|
|
@@ -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
|
+
```
|