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.
Files changed (148) hide show
  1. {agentexec-0.2.0rc1 → agentexec-0.3.0}/CHANGELOG.md +107 -0
  2. {agentexec-0.2.0rc1 → agentexec-0.3.0}/PKG-INFO +2 -2
  3. agentexec-0.3.0/RELEASE.md +91 -0
  4. agentexec-0.3.0/docs/api-reference/activity.md +401 -0
  5. agentexec-0.3.0/docs/api-reference/core.md +532 -0
  6. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/api-reference/pipeline.md +8 -8
  7. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/api-reference/runner.md +5 -5
  8. agentexec-0.3.0/docs/concepts/activity-tracking.md +431 -0
  9. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/concepts/architecture.md +63 -44
  10. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/concepts/task-lifecycle.md +33 -27
  11. agentexec-0.3.0/docs/concepts/worker-pool.md +363 -0
  12. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/contributing.md +19 -9
  13. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/deployment/docker.md +23 -18
  14. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/deployment/production.md +49 -62
  15. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/getting-started/configuration.md +30 -22
  16. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/getting-started/installation.md +2 -2
  17. agentexec-0.3.0/docs/getting-started/quickstart.md +253 -0
  18. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/guides/basic-usage.md +112 -114
  19. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/guides/custom-runners.md +34 -34
  20. agentexec-0.3.0/docs/guides/fastapi-integration.md +296 -0
  21. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/guides/openai-runner.md +5 -5
  22. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/guides/pipelines.md +6 -6
  23. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docs/index.md +23 -10
  24. {agentexec-0.2.0rc1 → agentexec-0.3.0}/pyproject.toml +17 -5
  25. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/__init__.py +6 -3
  26. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/__init__.py +76 -57
  27. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/events.py +9 -4
  28. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/handlers.py +28 -37
  29. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/models.py +106 -120
  30. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/producer.py +15 -24
  31. agentexec-0.3.0/src/agentexec/cli.py +162 -0
  32. agentexec-0.3.0/src/agentexec/core/db.py +65 -0
  33. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/core/queue.py +3 -3
  34. agentexec-0.3.0/src/agentexec/core/results.py +80 -0
  35. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/core/task.py +1 -1
  36. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/schedule.py +3 -3
  37. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/base.py +2 -1
  38. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/kafka.py +8 -2
  39. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/redis.py +25 -17
  40. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/worker/pool.py +246 -172
  41. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_activity_tracking.py +80 -90
  42. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_db.py +10 -9
  43. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_public_api.py +2 -2
  44. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_queue_partitions.py +2 -1
  45. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_results.py +55 -1
  46. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_runners.py +10 -11
  47. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_schedule.py +9 -4
  48. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_state.py +1 -0
  49. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_state_backend.py +7 -4
  50. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_task.py +2 -2
  51. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_task_locking.py +2 -27
  52. agentexec-0.3.0/tests/test_worker_logging.py +440 -0
  53. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_worker_pool.py +64 -96
  54. agentexec-0.3.0/tests/test_worker_resilience.py +198 -0
  55. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/package.json +1 -1
  56. agentexec-0.2.0rc1/.claude/skills/prepare-release/SKILL.md +0 -57
  57. agentexec-0.2.0rc1/docs/api-reference/activity.md +0 -455
  58. agentexec-0.2.0rc1/docs/api-reference/core.md +0 -476
  59. agentexec-0.2.0rc1/docs/concepts/activity-tracking.md +0 -520
  60. agentexec-0.2.0rc1/docs/concepts/worker-pool.md +0 -499
  61. agentexec-0.2.0rc1/docs/getting-started/quickstart.md +0 -262
  62. agentexec-0.2.0rc1/docs/guides/fastapi-integration.md +0 -301
  63. agentexec-0.2.0rc1/examples/multi-tenancy/README.md +0 -92
  64. agentexec-0.2.0rc1/examples/multi-tenancy/example.py +0 -188
  65. agentexec-0.2.0rc1/examples/openai-agents-fastapi/README.md +0 -147
  66. agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic/README +0 -1
  67. agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic/env.py +0 -82
  68. agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic/script.py.mako +0 -28
  69. agentexec-0.2.0rc1/examples/openai-agents-fastapi/alembic.ini +0 -148
  70. agentexec-0.2.0rc1/examples/openai-agents-fastapi/compose.yml +0 -10
  71. agentexec-0.2.0rc1/examples/openai-agents-fastapi/context.py +0 -21
  72. agentexec-0.2.0rc1/examples/openai-agents-fastapi/db.py +0 -27
  73. agentexec-0.2.0rc1/examples/openai-agents-fastapi/main.py +0 -45
  74. agentexec-0.2.0rc1/examples/openai-agents-fastapi/models.py +0 -9
  75. agentexec-0.2.0rc1/examples/openai-agents-fastapi/pipeline.py +0 -246
  76. agentexec-0.2.0rc1/examples/openai-agents-fastapi/pyproject.toml +0 -35
  77. agentexec-0.2.0rc1/examples/openai-agents-fastapi/tools.py +0 -46
  78. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/bun.lock +0 -422
  79. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/index.html +0 -13
  80. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/package.json +0 -27
  81. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/public/vite.svg +0 -4
  82. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/App.tsx +0 -35
  83. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/api/agents.ts +0 -37
  84. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/api/queries.ts +0 -51
  85. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/components/Layout.tsx +0 -38
  86. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/index.css +0 -263
  87. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/main.tsx +0 -10
  88. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/pages/AgentDetailPage.tsx +0 -45
  89. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/pages/AgentListPage.tsx +0 -68
  90. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/src/styles/github-dark.css +0 -617
  91. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/tsconfig.json +0 -21
  92. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/tsconfig.node.json +0 -11
  93. agentexec-0.2.0rc1/examples/openai-agents-fastapi/ui/vite.config.ts +0 -19
  94. agentexec-0.2.0rc1/examples/openai-agents-fastapi/views.py +0 -118
  95. agentexec-0.2.0rc1/examples/openai-agents-fastapi/worker.py +0 -86
  96. agentexec-0.2.0rc1/examples/queue-fairness/README.md +0 -75
  97. agentexec-0.2.0rc1/examples/queue-fairness/run.py +0 -215
  98. agentexec-0.2.0rc1/src/agentexec/core/db.py +0 -52
  99. agentexec-0.2.0rc1/src/agentexec/core/logging.py +0 -27
  100. agentexec-0.2.0rc1/src/agentexec/core/results.py +0 -40
  101. agentexec-0.2.0rc1/src/agentexec/worker/logging.py +0 -84
  102. agentexec-0.2.0rc1/tests/test_activity_tracking.py.bak +0 -427
  103. agentexec-0.2.0rc1/tests/test_worker_logging.py +0 -248
  104. agentexec-0.2.0rc1/ui/.gitignore +0 -3
  105. {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/ci.yml +0 -0
  106. {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/docker-publish.yml +0 -0
  107. {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/npm-publish.yml +0 -0
  108. {agentexec-0.2.0rc1 → agentexec-0.3.0}/.github/workflows/publish.yml +0 -0
  109. {agentexec-0.2.0rc1 → agentexec-0.3.0}/.gitignore +0 -0
  110. {agentexec-0.2.0rc1 → agentexec-0.3.0}/README.md +0 -0
  111. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker/Dockerfile +0 -0
  112. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker/README.md +0 -0
  113. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker/entrypoint.py +0 -0
  114. {agentexec-0.2.0rc1 → agentexec-0.3.0}/docker-compose.kafka.yml +0 -0
  115. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/schemas.py +0 -0
  116. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/activity/status.py +0 -0
  117. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/config.py +0 -0
  118. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/core/__init__.py +0 -0
  119. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/pipeline.py +0 -0
  120. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/runners/__init__.py +0 -0
  121. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/runners/base.py +0 -0
  122. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/runners/openai.py +0 -0
  123. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/state/__init__.py +0 -0
  124. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/tracker.py +0 -0
  125. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/worker/__init__.py +0 -0
  126. {agentexec-0.2.0rc1 → agentexec-0.3.0}/src/agentexec/worker/event.py +0 -0
  127. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_activity_schemas.py +0 -0
  128. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_config.py +0 -0
  129. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_kafka_integration.py +0 -0
  130. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_pipeline.py +0 -0
  131. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_pipeline_flow.py +0 -0
  132. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_queue.py +0 -0
  133. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_self_describing_results.py +0 -0
  134. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_task_types.py +0 -0
  135. {agentexec-0.2.0rc1 → agentexec-0.3.0}/tests/test_worker_event.py +0 -0
  136. {agentexec-0.2.0rc1/examples/openai-agents-fastapi → agentexec-0.3.0}/ui/.gitignore +0 -0
  137. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/README.md +0 -0
  138. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/bun.lock +0 -0
  139. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/ActiveAgentsBadge.tsx +0 -0
  140. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/ProgressBar.tsx +0 -0
  141. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/StatusBadge.tsx +0 -0
  142. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/TaskDetail.tsx +0 -0
  143. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/TaskList.tsx +0 -0
  144. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/components/index.ts +0 -0
  145. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/index.ts +0 -0
  146. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/src/types.ts +0 -0
  147. {agentexec-0.2.0rc1 → agentexec-0.3.0}/ui/tsconfig.json +0 -0
  148. {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.2.0rc1
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
+ ```