agentexec 0.1.6__tar.gz → 0.2.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.0/.github/workflows/ci.yml +125 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/CHANGELOG.md +113 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/PKG-INFO +134 -53
- {agentexec-0.1.6 → agentexec-0.2.0}/README.md +129 -51
- agentexec-0.2.0/RELEASE.md +91 -0
- agentexec-0.2.0/docker-compose.kafka.yml +48 -0
- agentexec-0.2.0/docs/api-reference/activity.md +401 -0
- agentexec-0.2.0/docs/api-reference/core.md +532 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/api-reference/pipeline.md +8 -8
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/api-reference/runner.md +5 -5
- agentexec-0.2.0/docs/concepts/activity-tracking.md +431 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/concepts/architecture.md +63 -44
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/concepts/task-lifecycle.md +33 -27
- agentexec-0.2.0/docs/concepts/worker-pool.md +363 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/contributing.md +19 -9
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/deployment/docker.md +23 -18
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/deployment/production.md +49 -62
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/getting-started/configuration.md +30 -22
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/getting-started/installation.md +2 -2
- agentexec-0.2.0/docs/getting-started/quickstart.md +253 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/guides/basic-usage.md +112 -114
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/guides/custom-runners.md +34 -34
- agentexec-0.2.0/docs/guides/fastapi-integration.md +296 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/guides/openai-runner.md +5 -5
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/guides/pipelines.md +6 -6
- {agentexec-0.1.6 → agentexec-0.2.0}/docs/index.md +23 -10
- {agentexec-0.1.6 → agentexec-0.2.0}/pyproject.toml +23 -5
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/__init__.py +4 -2
- agentexec-0.2.0/src/agentexec/activity/__init__.py +122 -0
- agentexec-0.2.0/src/agentexec/activity/events.py +30 -0
- agentexec-0.2.0/src/agentexec/activity/handlers.py +94 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/activity/models.py +107 -130
- agentexec-0.2.0/src/agentexec/activity/producer.py +170 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/activity/schemas.py +9 -5
- agentexec-0.2.0/src/agentexec/activity/status.py +11 -0
- agentexec-0.2.0/src/agentexec/cli.py +162 -0
- agentexec-0.2.0/src/agentexec/config.py +167 -0
- agentexec-0.2.0/src/agentexec/core/db.py +65 -0
- agentexec-0.2.0/src/agentexec/core/queue.py +65 -0
- agentexec-0.2.0/src/agentexec/core/results.py +41 -0
- agentexec-0.2.0/src/agentexec/core/task.py +213 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/pipeline.py +1 -1
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/runners/base.py +2 -2
- agentexec-0.2.0/src/agentexec/schedule.py +102 -0
- agentexec-0.2.0/src/agentexec/state/__init__.py +38 -0
- agentexec-0.2.0/src/agentexec/state/base.py +106 -0
- agentexec-0.2.0/src/agentexec/state/kafka.py +304 -0
- agentexec-0.2.0/src/agentexec/state/redis.py +272 -0
- agentexec-0.2.0/src/agentexec/tracker.py +48 -0
- agentexec-0.2.0/src/agentexec/worker/event.py +31 -0
- agentexec-0.2.0/src/agentexec/worker/pool.py +580 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_activity_schemas.py +0 -2
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_activity_tracking.py +136 -151
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_config.py +7 -9
- agentexec-0.2.0/tests/test_db.py +39 -0
- agentexec-0.2.0/tests/test_kafka_integration.py +158 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_pipeline.py +0 -2
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_pipeline_flow.py +0 -49
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_public_api.py +2 -4
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_queue.py +20 -60
- agentexec-0.2.0/tests/test_queue_partitions.py +174 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_results.py +29 -52
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_runners.py +13 -16
- agentexec-0.2.0/tests/test_schedule.py +390 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_self_describing_results.py +17 -42
- agentexec-0.2.0/tests/test_state.py +60 -0
- agentexec-0.2.0/tests/test_state_backend.py +123 -0
- agentexec-0.2.0/tests/test_task.py +378 -0
- agentexec-0.2.0/tests/test_task_locking.py +153 -0
- agentexec-0.2.0/tests/test_worker_event.py +71 -0
- agentexec-0.2.0/tests/test_worker_logging.py +439 -0
- agentexec-0.2.0/tests/test_worker_pool.py +487 -0
- agentexec-0.2.0/tests/test_worker_resilience.py +198 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/package.json +1 -1
- agentexec-0.1.6/.claude/settings.local.json +0 -7
- agentexec-0.1.6/.claude/skills/prepare-release/SKILL.md +0 -57
- agentexec-0.1.6/docs/api-reference/activity.md +0 -455
- agentexec-0.1.6/docs/api-reference/core.md +0 -476
- agentexec-0.1.6/docs/concepts/activity-tracking.md +0 -520
- agentexec-0.1.6/docs/concepts/worker-pool.md +0 -499
- agentexec-0.1.6/docs/getting-started/quickstart.md +0 -262
- agentexec-0.1.6/docs/guides/fastapi-integration.md +0 -301
- agentexec-0.1.6/examples/multi-tenancy/README.md +0 -92
- agentexec-0.1.6/examples/multi-tenancy/example.py +0 -188
- agentexec-0.1.6/examples/openai-agents-fastapi/README.md +0 -147
- agentexec-0.1.6/examples/openai-agents-fastapi/alembic/README +0 -1
- agentexec-0.1.6/examples/openai-agents-fastapi/alembic/env.py +0 -82
- agentexec-0.1.6/examples/openai-agents-fastapi/alembic/script.py.mako +0 -28
- agentexec-0.1.6/examples/openai-agents-fastapi/alembic.ini +0 -148
- agentexec-0.1.6/examples/openai-agents-fastapi/compose.yml +0 -10
- agentexec-0.1.6/examples/openai-agents-fastapi/context.py +0 -21
- agentexec-0.1.6/examples/openai-agents-fastapi/db.py +0 -27
- agentexec-0.1.6/examples/openai-agents-fastapi/main.py +0 -45
- agentexec-0.1.6/examples/openai-agents-fastapi/models.py +0 -9
- agentexec-0.1.6/examples/openai-agents-fastapi/pipeline.py +0 -246
- agentexec-0.1.6/examples/openai-agents-fastapi/pyproject.toml +0 -35
- agentexec-0.1.6/examples/openai-agents-fastapi/tools.py +0 -46
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/bun.lock +0 -422
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/index.html +0 -13
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/package.json +0 -27
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/public/vite.svg +0 -4
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/App.tsx +0 -35
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/api/agents.ts +0 -37
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/api/queries.ts +0 -51
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/components/Layout.tsx +0 -38
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/index.css +0 -263
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/main.tsx +0 -10
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/pages/AgentDetailPage.tsx +0 -45
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/pages/AgentListPage.tsx +0 -68
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/src/styles/github-dark.css +0 -617
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/tsconfig.json +0 -21
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/tsconfig.node.json +0 -11
- agentexec-0.1.6/examples/openai-agents-fastapi/ui/vite.config.ts +0 -19
- agentexec-0.1.6/examples/openai-agents-fastapi/views.py +0 -118
- agentexec-0.1.6/examples/openai-agents-fastapi/worker.py +0 -86
- agentexec-0.1.6/src/agentexec/activity/__init__.py +0 -39
- agentexec-0.1.6/src/agentexec/activity/tracker.py +0 -286
- agentexec-0.1.6/src/agentexec/config.py +0 -102
- agentexec-0.1.6/src/agentexec/core/db.py +0 -62
- agentexec-0.1.6/src/agentexec/core/logging.py +0 -33
- agentexec-0.1.6/src/agentexec/core/queue.py +0 -132
- agentexec-0.1.6/src/agentexec/core/results.py +0 -64
- agentexec-0.1.6/src/agentexec/core/task.py +0 -336
- agentexec-0.1.6/src/agentexec/state/__init__.py +0 -264
- agentexec-0.1.6/src/agentexec/state/backend.py +0 -320
- agentexec-0.1.6/src/agentexec/state/redis_backend.py +0 -443
- agentexec-0.1.6/src/agentexec/tracker.py +0 -67
- agentexec-0.1.6/src/agentexec/worker/event.py +0 -48
- agentexec-0.1.6/src/agentexec/worker/logging.py +0 -104
- agentexec-0.1.6/src/agentexec/worker/pool.py +0 -385
- agentexec-0.1.6/tests/test_activity_tracking.py.bak +0 -427
- agentexec-0.1.6/tests/test_db.py +0 -134
- agentexec-0.1.6/tests/test_state.py +0 -185
- agentexec-0.1.6/tests/test_state_backend.py +0 -292
- agentexec-0.1.6/tests/test_task.py +0 -316
- agentexec-0.1.6/tests/test_task_locking.py +0 -260
- agentexec-0.1.6/tests/test_worker_event.py +0 -133
- agentexec-0.1.6/tests/test_worker_logging.py +0 -280
- agentexec-0.1.6/tests/test_worker_pool.py +0 -284
- agentexec-0.1.6/ui/.gitignore +0 -3
- {agentexec-0.1.6 → agentexec-0.2.0}/.github/workflows/docker-publish.yml +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/.github/workflows/npm-publish.yml +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/.github/workflows/publish.yml +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/.gitignore +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docker/Dockerfile +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docker/README.md +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/docker/entrypoint.py +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/core/__init__.py +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/runners/__init__.py +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/runners/openai.py +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/src/agentexec/worker/__init__.py +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/tests/test_task_types.py +0 -0
- {agentexec-0.1.6/examples/openai-agents-fastapi → agentexec-0.2.0}/ui/.gitignore +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/README.md +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/bun.lock +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/components/ActiveAgentsBadge.tsx +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/components/ProgressBar.tsx +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/components/StatusBadge.tsx +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/components/TaskDetail.tsx +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/components/TaskList.tsx +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/components/index.ts +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/index.ts +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/src/types.ts +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/tsconfig.json +0 -0
- {agentexec-0.1.6 → agentexec-0.2.0}/ui/vite.config.ts +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
# -----------------------------------------------------------------------
|
|
11
|
+
# Unit tests — no external services (fakeredis + SQLite)
|
|
12
|
+
# -----------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.12", "3.13"]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v6
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: true
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
run: uv python install ${{ matrix.python-version }}
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: uv sync --dev
|
|
34
|
+
|
|
35
|
+
- name: Run unit tests
|
|
36
|
+
run: |
|
|
37
|
+
uv run pytest tests/ \
|
|
38
|
+
--ignore=tests/test_kafka_integration.py \
|
|
39
|
+
-o "addopts=" \
|
|
40
|
+
-v --tb=long
|
|
41
|
+
|
|
42
|
+
# -----------------------------------------------------------------------
|
|
43
|
+
# Kafka integration tests — real broker via docker run
|
|
44
|
+
# -----------------------------------------------------------------------
|
|
45
|
+
test-kafka:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
|
|
51
|
+
- name: Start Kafka broker
|
|
52
|
+
run: |
|
|
53
|
+
docker run -d --name kafka \
|
|
54
|
+
-p 9092:9092 \
|
|
55
|
+
-e KAFKA_NODE_ID=1 \
|
|
56
|
+
-e KAFKA_PROCESS_ROLES=broker,controller \
|
|
57
|
+
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
|
|
58
|
+
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
|
|
59
|
+
-e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
|
|
60
|
+
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
|
|
61
|
+
-e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT \
|
|
62
|
+
-e KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT \
|
|
63
|
+
-e KAFKA_LOG_CLEANER_MIN_COMPACTION_LAG_MS=0 \
|
|
64
|
+
-e KAFKA_LOG_CLEANER_MIN_CLEANABLE_RATIO=0.01 \
|
|
65
|
+
-e KAFKA_LOG_RETENTION_MS=60000 \
|
|
66
|
+
-e KAFKA_NUM_PARTITIONS=1 \
|
|
67
|
+
-e KAFKA_AUTO_CREATE_TOPICS_ENABLE=true \
|
|
68
|
+
-e KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS=0 \
|
|
69
|
+
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
|
|
70
|
+
-e CLUSTER_ID=ciTestCluster0001 \
|
|
71
|
+
apache/kafka:3.9.0
|
|
72
|
+
|
|
73
|
+
- name: Install uv
|
|
74
|
+
uses: astral-sh/setup-uv@v6
|
|
75
|
+
with:
|
|
76
|
+
enable-cache: true
|
|
77
|
+
|
|
78
|
+
- name: Set up Python
|
|
79
|
+
run: uv python install 3.12
|
|
80
|
+
|
|
81
|
+
- name: Install dependencies
|
|
82
|
+
run: uv sync --dev --extra kafka
|
|
83
|
+
|
|
84
|
+
- name: Wait for Kafka to be ready
|
|
85
|
+
run: |
|
|
86
|
+
echo "Waiting for Kafka..."
|
|
87
|
+
for i in $(seq 1 30); do
|
|
88
|
+
if nc -z localhost 9092 2>/dev/null; then
|
|
89
|
+
echo "Kafka port is open"
|
|
90
|
+
sleep 5
|
|
91
|
+
echo "Kafka is ready"
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
echo " attempt $i/30..."
|
|
95
|
+
sleep 2
|
|
96
|
+
done
|
|
97
|
+
echo "Kafka failed to start"
|
|
98
|
+
docker logs kafka
|
|
99
|
+
exit 1
|
|
100
|
+
|
|
101
|
+
- name: Run Kafka integration tests
|
|
102
|
+
timeout-minutes: 2
|
|
103
|
+
run: |
|
|
104
|
+
uv run pytest tests/test_kafka_integration.py \
|
|
105
|
+
-o "addopts=" \
|
|
106
|
+
-v --tb=long 2>&1 | tee /tmp/kafka_test_output.txt
|
|
107
|
+
exit ${PIPESTATUS[0]}
|
|
108
|
+
env:
|
|
109
|
+
AGENTEXEC_STATE_BACKEND: agentexec.state.kafka
|
|
110
|
+
KAFKA_BOOTSTRAP_SERVERS: localhost:9092
|
|
111
|
+
AGENTEXEC_KAFKA_DEFAULT_PARTITIONS: "2"
|
|
112
|
+
AGENTEXEC_KAFKA_REPLICATION_FACTOR: "1"
|
|
113
|
+
|
|
114
|
+
- name: Show Kafka logs on failure
|
|
115
|
+
if: failure()
|
|
116
|
+
run: docker logs kafka 2>&1 | tail -50
|
|
117
|
+
|
|
118
|
+
- name: Create failure check annotation with output
|
|
119
|
+
if: failure()
|
|
120
|
+
run: |
|
|
121
|
+
if [ -f /tmp/kafka_test_output.txt ]; then
|
|
122
|
+
grep -E '\[queue_|FAILED|ERROR|AssertionError|TIMEOUT|short test summary' /tmp/kafka_test_output.txt | tail -9 | while IFS= read -r line; do
|
|
123
|
+
echo "::warning::$line"
|
|
124
|
+
done
|
|
125
|
+
fi
|
|
@@ -1,5 +1,118 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.0
|
|
4
|
+
|
|
5
|
+
Major refactor of the backend, queue, activity, worker, and database layers.
|
|
6
|
+
If you're upgrading from 0.1.x, read the **Breaking Changes** section closely.
|
|
7
|
+
|
|
8
|
+
### Breaking Changes
|
|
9
|
+
|
|
10
|
+
**Fully async database layer**
|
|
11
|
+
- `configure_engine()` and `get_session()` now require an async SQLAlchemy engine (`AsyncEngine`) and return `AsyncSession`
|
|
12
|
+
- Database URLs must use async drivers (e.g. `sqlite+aiosqlite://`, `postgresql+asyncpg://`)
|
|
13
|
+
- `sqlalchemy[asyncio]` is now a core dependency
|
|
14
|
+
|
|
15
|
+
**Async activity API**
|
|
16
|
+
- All activity functions are async: `await ax.activity.create(...)`, `await ax.activity.update(...)`, `await ax.activity.complete(...)`, `await ax.activity.error(...)`
|
|
17
|
+
- `activity.list()`, `activity.detail()`, and `activity.count_active()` are async and accept `AsyncSession`
|
|
18
|
+
- Activity handlers are async (`async def __call__`)
|
|
19
|
+
- The `session` parameter was removed from activity mutations — the handler owns its own session lifecycle
|
|
20
|
+
|
|
21
|
+
**Pool entry point**
|
|
22
|
+
- `pool.run()` was removed. Use `await pool.start()` in an asyncio loop, or the new `agentexec run mymodule:pool` CLI
|
|
23
|
+
- `AGENTEXEC_QUEUE_NAME` renamed to `AGENTEXEC_QUEUE_PREFIX` (old name still accepted as alias)
|
|
24
|
+
- `agentexec.state.redis_backend` renamed to `agentexec.state.redis` — update `AGENTEXEC_STATE_BACKEND` if set explicitly
|
|
25
|
+
|
|
26
|
+
**Task context serialization**
|
|
27
|
+
- `Task.context` is now `Mapping[str, Any]` (raw dict), not a typed BaseModel — hydration happens at execution time
|
|
28
|
+
- `Task.create()` is now async
|
|
29
|
+
|
|
30
|
+
**Queue backend protocol**
|
|
31
|
+
- `BaseQueueBackend.push()` signature changed from `high_priority: bool` to `priority: Priority | None` — affects Redis, Kafka, and any custom queue backend
|
|
32
|
+
|
|
33
|
+
**Removed APIs**
|
|
34
|
+
- `set_global_session`/`get_global_session`/`remove_global_session` — use `configure_engine`/`get_session`
|
|
35
|
+
- `state.backend.publish`/`subscribe` (pubsub), `index_add`/`index_range`/`index_remove`, `clear`, `configure`
|
|
36
|
+
- `worker/logging.py` and `core/logging.py` — all modules use stdlib `logging.getLogger(__name__)` directly
|
|
37
|
+
|
|
38
|
+
### New Features
|
|
39
|
+
|
|
40
|
+
**CLI entrypoint**
|
|
41
|
+
- New `agentexec` CLI command: `agentexec run mymodule:pool --create-tables --workers 4`
|
|
42
|
+
|
|
43
|
+
**Partitioned Redis queues**
|
|
44
|
+
- Tasks with `lock_key` route to dedicated partition queues with per-partition locking and SCAN-based fair dequeue
|
|
45
|
+
|
|
46
|
+
**Activity handler pattern**
|
|
47
|
+
- Pluggable persistence via `PostgresHandler` (default) and `IPCHandler` (worker processes)
|
|
48
|
+
|
|
49
|
+
**Task retry**
|
|
50
|
+
- Failed tasks requeue as high priority with `AGENTEXEC_MAX_TASK_RETRIES` (default 3)
|
|
51
|
+
|
|
52
|
+
**Kafka backend (experimental)**
|
|
53
|
+
- `pip install agentexec[kafka]` for queue and schedule via Kafka
|
|
54
|
+
|
|
55
|
+
**Typed worker IPC**
|
|
56
|
+
- `TaskFailed` and `ActivityEvent` messages flow over `multiprocessing.Queue` with pydantic validation
|
|
57
|
+
|
|
58
|
+
**Schedule composite keys**
|
|
59
|
+
- `{task_name}:{cron}:{context_hash}` for unique schedule identity
|
|
60
|
+
|
|
61
|
+
**Activity model `create()` classmethod**
|
|
62
|
+
- `Activity.create()` encapsulates record + initial log entry creation in one async call
|
|
63
|
+
|
|
64
|
+
**Async engine disposal**
|
|
65
|
+
- `dispose_engine()` ensures the async engine's background threads exit cleanly on shutdown
|
|
66
|
+
|
|
67
|
+
### Architecture Changes
|
|
68
|
+
|
|
69
|
+
**Worker pool refactor**
|
|
70
|
+
- Workers use the `spawn` multiprocessing start method with explicit context — no inherited state
|
|
71
|
+
- Event handling and scheduling extracted into `_EventHandler` and `_Scheduler` classes
|
|
72
|
+
- `StateEvent` replaced with stdlib `multiprocessing.Event` — removes dependency on the state backend for shutdown coordination
|
|
73
|
+
- Class-based backend architecture with ABCs (`BaseStateBackend`, `BaseQueueBackend`, `BaseScheduleBackend`)
|
|
74
|
+
- `Task` is pure data, `TaskDefinition` owns behavior
|
|
75
|
+
- Status enum extracted to `activity/status.py` (no SQLAlchemy dependency)
|
|
76
|
+
|
|
77
|
+
**Logging**
|
|
78
|
+
- All modules use stdlib `logging.getLogger(__name__)`
|
|
79
|
+
- Spawned workers bootstrap a `StreamHandler` on the root logger so logs reach stderr
|
|
80
|
+
- Pool messages use `logger.info`/`logger.error` instead of `print()`
|
|
81
|
+
|
|
82
|
+
### Bug Fixes
|
|
83
|
+
|
|
84
|
+
- **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
|
|
85
|
+
- **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
|
|
86
|
+
- **Unregistered task name crash.** Worker now logs an error and skips instead of crashing when it receives a task for an unknown name
|
|
87
|
+
- Failed tasks now log full tracebacks via `logger.exception` instead of `logger.error`
|
|
88
|
+
- Kafka consumer handles `None` message values without crashing
|
|
89
|
+
- `ActivityUpdated.status` is a `Status` enum instead of raw string
|
|
90
|
+
|
|
91
|
+
### Documentation
|
|
92
|
+
|
|
93
|
+
- Full documentation sweep for the async API — connection strings, CLI usage, `await` on activity calls across all guides and API references
|
|
94
|
+
|
|
95
|
+
## v0.1.7
|
|
96
|
+
|
|
97
|
+
### New Features
|
|
98
|
+
|
|
99
|
+
**Scheduled tasks with cron expressions**
|
|
100
|
+
- `@pool.schedule("task_name", "*/5 * * * *")` decorator registers and schedules a task in one step
|
|
101
|
+
- `pool.add_schedule()` for imperative scheduling of already-registered tasks
|
|
102
|
+
- Cron expressions evaluated in configurable timezone (`AGENTEXEC_SCHEDULER_TIMEZONE`, default UTC)
|
|
103
|
+
- Repeat budget: `-1` for forever (default), `0` for one-shot, `N` for N more executions
|
|
104
|
+
- Scheduler runs automatically inside `pool.run()` — no extra setup needed
|
|
105
|
+
- Idempotent registration: keyed by task name, so restarts and multiple pool instances overwrite instead of duplicating
|
|
106
|
+
- Clock-drift resilient: next run computed from intended anchor time, not wall clock
|
|
107
|
+
- Skips missed intervals after downtime instead of enqueuing a burst of catch-up tasks
|
|
108
|
+
- New `croniter` dependency for cron expression parsing
|
|
109
|
+
|
|
110
|
+
### Improvements
|
|
111
|
+
|
|
112
|
+
**State backend sorted set operations**
|
|
113
|
+
- Added `zadd()`, `zrangebyscore()`, `zrem()` to `StateBackend` protocol and Redis implementation
|
|
114
|
+
- Used internally by the scheduler for efficient due-task polling
|
|
115
|
+
|
|
3
116
|
## v0.1.6
|
|
4
117
|
|
|
5
118
|
### New Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentexec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -16,11 +16,14 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: croniter>=6.0.0
|
|
19
20
|
Requires-Dist: openai-agents>=0.1.0
|
|
20
21
|
Requires-Dist: pydantic-settings>=2.5.0
|
|
21
22
|
Requires-Dist: pydantic>=2.12.0
|
|
22
23
|
Requires-Dist: redis>=7.0.1
|
|
23
|
-
Requires-Dist: sqlalchemy>=2.0.44
|
|
24
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.44
|
|
25
|
+
Provides-Extra: kafka
|
|
26
|
+
Requires-Dist: aiokafka>=0.11.0; extra == 'kafka'
|
|
24
27
|
Description-Content-Type: text/markdown
|
|
25
28
|
|
|
26
29
|
# `agentexec`
|
|
@@ -147,8 +150,8 @@ async def start_research(company: str) -> dict:
|
|
|
147
150
|
return {"agent_id": str(task.agent_id), "status": "queued"} # Return agent_id for status polling
|
|
148
151
|
|
|
149
152
|
@router.get("/research/{agent_id}")
|
|
150
|
-
def get_status(agent_id: UUID
|
|
151
|
-
return ax.activity.detail(
|
|
153
|
+
async def get_status(agent_id: UUID) -> ax.activity.ActivityDetailSchema:
|
|
154
|
+
return await ax.activity.detail(agent_id=agent_id)
|
|
152
155
|
```
|
|
153
156
|
|
|
154
157
|
### 4. Run Workers
|
|
@@ -175,8 +178,8 @@ task = await ax.enqueue(
|
|
|
175
178
|
)
|
|
176
179
|
|
|
177
180
|
# Filter activities by metadata
|
|
178
|
-
activities = ax.activity.list(
|
|
179
|
-
detail = ax.activity.detail(
|
|
181
|
+
activities = await ax.activity.list(metadata_filter={"organization_id": "org-123"})
|
|
182
|
+
detail = await ax.activity.detail(agent_id=agent_id, metadata_filter={"organization_id": "org-123"})
|
|
180
183
|
|
|
181
184
|
# Access metadata programmatically (excluded from API serialization by default)
|
|
182
185
|
org_id = detail.metadata["organization_id"]
|
|
@@ -211,7 +214,7 @@ agent = Agent(
|
|
|
211
214
|
Update progress explicitly from your task:
|
|
212
215
|
|
|
213
216
|
```python
|
|
214
|
-
ax.activity.update(agent_id, "Processing batch 3 of 10", percentage=30)
|
|
217
|
+
await ax.activity.update(agent_id, "Processing batch 3 of 10", percentage=30)
|
|
215
218
|
```
|
|
216
219
|
|
|
217
220
|
### Task Locking
|
|
@@ -227,11 +230,34 @@ async def associate(agent_id: UUID, context: ObservationContext):
|
|
|
227
230
|
pool.add_task("associate_observation", handler, lock_key="user:{user_id}")
|
|
228
231
|
```
|
|
229
232
|
|
|
230
|
-
The `lock_key` is a string template evaluated against the task context fields.
|
|
233
|
+
The `lock_key` is a string template evaluated against the task context fields. Tasks with the same evaluated lock key are routed to a dedicated partition queue (`{prefix}:{lock_key}`) where they execute one at a time. Workers skip locked partitions and move on to the next available one — no requeuing, no wasted cycles.
|
|
231
234
|
|
|
232
|
-
The lock TTL (`AGENTEXEC_LOCK_TTL`, default 1800s) is a safety net for worker process death — locks are always explicitly released
|
|
235
|
+
The lock is released automatically when a task completes or errors. The lock TTL (`AGENTEXEC_LOCK_TTL`, default 1800s) is a safety net for worker process death (OOM, SIGKILL) — under normal operation, locks are always explicitly released. Set this higher than your longest expected task duration.
|
|
233
236
|
|
|
234
|
-
|
|
237
|
+
### Scheduled Tasks
|
|
238
|
+
|
|
239
|
+
Run tasks on a recurring interval using cron expressions:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# Decorator — registers the task and schedules it in one step
|
|
243
|
+
@pool.schedule("refresh_cache", "*/5 * * * *")
|
|
244
|
+
async def refresh(agent_id: UUID, context: RefreshContext):
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
# With context and repeat limit
|
|
248
|
+
@pool.schedule("sync_users", "0 * * * *", context=SyncContext(full=True), repeat=3)
|
|
249
|
+
async def sync(agent_id: UUID, context: SyncContext):
|
|
250
|
+
...
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
For tasks registered separately, use `pool.add_schedule()`:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
pool.add_schedule("refresh_cache", "*/5 * * * *", RefreshContext(scope="all"))
|
|
257
|
+
pool.add_schedule("refresh_cache", "0 * * * *", RefreshContext(scope="users"), repeat=3)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The scheduler runs automatically inside `pool.run()`. Cron expressions are evaluated in the configured timezone (`AGENTEXEC_SCHEDULER_TIMEZONE`, default UTC) so schedules read naturally regardless of server timezone. Next-run times are computed from the intended anchor time, not wall clock, to prevent cumulative drift.
|
|
235
261
|
|
|
236
262
|
### Priority Queue
|
|
237
263
|
|
|
@@ -366,8 +392,7 @@ if __name__ == "__main__":
|
|
|
366
392
|
try:
|
|
367
393
|
pool.run()
|
|
368
394
|
except KeyboardInterrupt:
|
|
369
|
-
|
|
370
|
-
ax.activity.cancel_pending(db)
|
|
395
|
+
asyncio.run(ax.activity.cancel_pending())
|
|
371
396
|
```
|
|
372
397
|
|
|
373
398
|
### Docker Deployment
|
|
@@ -396,11 +421,10 @@ import agentexec as ax
|
|
|
396
421
|
engine = create_engine(os.environ["DATABASE_URL"])
|
|
397
422
|
pool = ax.Pool(engine=engine)
|
|
398
423
|
|
|
399
|
-
def cleanup() -> None:
|
|
400
|
-
|
|
401
|
-
ax.activity.cancel_pending(db)
|
|
424
|
+
async def cleanup() -> None:
|
|
425
|
+
await ax.activity.cancel_pending()
|
|
402
426
|
|
|
403
|
-
atexit.register(cleanup)
|
|
427
|
+
atexit.register(lambda: asyncio.run(cleanup()))
|
|
404
428
|
|
|
405
429
|
@pool.task("my_task")
|
|
406
430
|
async def my_task(agent_id: UUID, context: MyContext) -> None:
|
|
@@ -421,11 +445,13 @@ docker run -e DATABASE_URL=... -e REDIS_URL=... -e OPENAI_API_KEY=... my-worker
|
|
|
421
445
|
|
|
422
446
|
## Backend Architecture
|
|
423
447
|
|
|
424
|
-
### Redis
|
|
448
|
+
### Redis (Default)
|
|
449
|
+
|
|
450
|
+
agentexec uses Redis for task queuing, result storage, and coordination between workers. The queue uses a partitioned design where tasks with a `lock_key` go to dedicated partition queues (`{prefix}:{lock_key}`) and are serialized by a lock, while tasks without a lock key go to the default queue for concurrent processing.
|
|
425
451
|
|
|
426
|
-
|
|
452
|
+
Workers dequeue using Redis `SCAN`, which iterates keys in hash-table order — effectively random. This provides fair distribution across partitions without explicit round-robin. See `examples/queue-fairness/` for benchmarks showing uniform distribution at 1000+ partitions.
|
|
427
453
|
|
|
428
|
-
**AWS Compatible:**
|
|
454
|
+
**AWS Compatible:** Standard Redis features only — AWS ElastiCache works out of the box.
|
|
429
455
|
|
|
430
456
|
```bash
|
|
431
457
|
AGENTEXEC_REDIS_URL=redis://localhost:6379/0
|
|
@@ -433,18 +459,45 @@ AGENTEXEC_REDIS_URL=redis://localhost:6379/0
|
|
|
433
459
|
AGENTEXEC_REDIS_URL=redis://my-cluster.abc123.use1.cache.amazonaws.com:6379
|
|
434
460
|
```
|
|
435
461
|
|
|
462
|
+
### Kafka (Experimental)
|
|
463
|
+
|
|
464
|
+
Kafka can be used as an alternative backend for task queuing and schedule storage. Activity tracking always uses PostgreSQL regardless of backend — Kafka is not a KV store, so state operations (`get`/`set`, counters) are not supported and will raise `NotImplementedError`.
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
pip install agentexec[kafka]
|
|
468
|
+
|
|
469
|
+
AGENTEXEC_STATE_BACKEND=agentexec.state.kafka
|
|
470
|
+
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Kafka uses consumer groups for work distribution instead of Redis's scan-based dequeue. Topics are auto-created on first use. Schedule storage uses a compacted topic that is replayed on each poll.
|
|
474
|
+
|
|
475
|
+
**When to consider Kafka:**
|
|
476
|
+
- You already run Kafka and want to avoid adding Redis
|
|
477
|
+
- You need durable, replayable task queues with built-in replication
|
|
478
|
+
- You want partition-level ordering guarantees (tasks with the same key go to the same partition)
|
|
479
|
+
|
|
480
|
+
**Limitations:**
|
|
481
|
+
- No KV state — `backend.state.get/set/delete` and counters raise `NotImplementedError`
|
|
482
|
+
- No partition-level locking (Kafka partition assignment handles isolation instead)
|
|
483
|
+
- Schedule `get_due()` replays the entire compacted topic on every poll
|
|
484
|
+
- `lock_key` is used as a Kafka partition key (routing), not as a mutex
|
|
485
|
+
|
|
486
|
+
See [Kafka configuration](#kafka-settings) below for all available settings.
|
|
487
|
+
|
|
436
488
|
### Extensible State Backend
|
|
437
489
|
|
|
438
|
-
The state backend is pluggable.
|
|
490
|
+
The state backend is pluggable. Implement `BaseBackend` with `state`, `queue`, and `schedule` sub-backends:
|
|
439
491
|
|
|
440
492
|
```bash
|
|
441
|
-
AGENTEXEC_STATE_BACKEND=agentexec.state.
|
|
442
|
-
AGENTEXEC_STATE_BACKEND=
|
|
493
|
+
AGENTEXEC_STATE_BACKEND=agentexec.state.redis # Default
|
|
494
|
+
AGENTEXEC_STATE_BACKEND=agentexec.state.kafka # Experimental
|
|
495
|
+
AGENTEXEC_STATE_BACKEND=myapp.state.custom # Custom (must export Backend class)
|
|
443
496
|
```
|
|
444
497
|
|
|
445
498
|
### Database
|
|
446
499
|
|
|
447
|
-
Activity tracking uses SQLAlchemy with two tables:
|
|
500
|
+
Activity tracking uses SQLAlchemy with two tables (always PostgreSQL/SQLite, independent of the state backend):
|
|
448
501
|
|
|
449
502
|
**`agentexec_activity`** - Main activity records
|
|
450
503
|
- `agent_id` - Unique identifier (UUID)
|
|
@@ -478,25 +531,23 @@ from agentexec.activity.schemas import (
|
|
|
478
531
|
**List activities:**
|
|
479
532
|
|
|
480
533
|
```python
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
# }
|
|
534
|
+
result = await ax.activity.list(page=1, page_size=20)
|
|
535
|
+
# Returns ActivityListSchema:
|
|
536
|
+
# {
|
|
537
|
+
# "items": [...], # List of ActivityListItemSchema
|
|
538
|
+
# "total": 150,
|
|
539
|
+
# "page": 1,
|
|
540
|
+
# "page_size": 20,
|
|
541
|
+
# "total_pages": 8
|
|
542
|
+
# }
|
|
491
543
|
```
|
|
492
544
|
|
|
493
545
|
**Get activity detail:**
|
|
494
546
|
|
|
495
547
|
```python
|
|
496
|
-
activity = ax.activity.detail(
|
|
548
|
+
activity = await ax.activity.detail(agent_id=agent_id)
|
|
497
549
|
# Returns ActivityDetailSchema:
|
|
498
550
|
# {
|
|
499
|
-
# "id": "...",
|
|
500
551
|
# "agent_id": "...",
|
|
501
552
|
# "agent_type": "research_company",
|
|
502
553
|
# "created_at": "2024-01-15T10:30:00Z",
|
|
@@ -512,7 +563,7 @@ activity = ax.activity.detail(db, agent_id=agent_id)
|
|
|
512
563
|
**Count active agents:**
|
|
513
564
|
|
|
514
565
|
```python
|
|
515
|
-
count = ax.activity.
|
|
566
|
+
count = await ax.activity.count_active()
|
|
516
567
|
# Returns number of agents with status QUEUED or RUNNING
|
|
517
568
|
```
|
|
518
569
|
|
|
@@ -527,13 +578,15 @@ from sqlalchemy.orm import Session
|
|
|
527
578
|
import agentexec as ax
|
|
528
579
|
|
|
529
580
|
def build_table(db: Session) -> Table:
|
|
530
|
-
|
|
581
|
+
count = asyncio.run(ax.activity.count_active())
|
|
582
|
+
table = Table(title=f"Active Agents: {count}")
|
|
531
583
|
table.add_column("Status")
|
|
532
584
|
table.add_column("Task")
|
|
533
585
|
table.add_column("Message")
|
|
534
586
|
table.add_column("Progress")
|
|
535
587
|
|
|
536
|
-
|
|
588
|
+
activities = asyncio.run(ax.activity.list(page=1, page_size=10))
|
|
589
|
+
for item in activities.items:
|
|
537
590
|
table.add_row(
|
|
538
591
|
item.status,
|
|
539
592
|
item.agent_type,
|
|
@@ -642,7 +695,12 @@ async def handler(agent_id: UUID, context: MyContext) -> None: ...
|
|
|
642
695
|
@pool.task("name", lock_key="user:{user_id}") # Sequential per user
|
|
643
696
|
async def locked(agent_id: UUID, context: MyContext) -> None: ...
|
|
644
697
|
|
|
645
|
-
pool.
|
|
698
|
+
@pool.schedule("name", "*/5 * * * *") # Register + schedule in one step
|
|
699
|
+
async def scheduled(agent_id: UUID, context: MyContext) -> None: ...
|
|
700
|
+
|
|
701
|
+
pool.add_schedule("name", "0 * * * *", MyContext(), repeat=3) # Schedule separately
|
|
702
|
+
|
|
703
|
+
pool.run() # Blocking - runs workers + scheduler + retry handling
|
|
646
704
|
pool.start() # Non-blocking - starts workers in background
|
|
647
705
|
pool.shutdown() # Graceful shutdown
|
|
648
706
|
```
|
|
@@ -653,20 +711,20 @@ pool.shutdown() # Graceful shutdown
|
|
|
653
711
|
import agentexec as ax
|
|
654
712
|
|
|
655
713
|
# Create activity (returns agent_id for tracking)
|
|
656
|
-
agent_id = ax.activity.create(task_name, message="Starting...")
|
|
714
|
+
agent_id = await ax.activity.create(task_name, message="Starting...")
|
|
657
715
|
|
|
658
716
|
# Update progress
|
|
659
|
-
ax.activity.update(agent_id, message, percentage=50)
|
|
660
|
-
ax.activity.complete(agent_id, message="Done")
|
|
661
|
-
ax.activity.error(agent_id,
|
|
717
|
+
await ax.activity.update(agent_id, message, percentage=50)
|
|
718
|
+
await ax.activity.complete(agent_id, message="Done")
|
|
719
|
+
await ax.activity.error(agent_id, message="Failed: ...")
|
|
662
720
|
|
|
663
|
-
# Query activities
|
|
664
|
-
activities = ax.activity.list(
|
|
665
|
-
activity = ax.activity.detail(
|
|
666
|
-
count = ax.activity.
|
|
721
|
+
# Query activities (uses database session)
|
|
722
|
+
activities = await ax.activity.list(page=1, page_size=20)
|
|
723
|
+
activity = await ax.activity.detail(agent_id=agent_id)
|
|
724
|
+
count = await ax.activity.count_active()
|
|
667
725
|
|
|
668
726
|
# Cleanup
|
|
669
|
-
canceled = ax.activity.cancel_pending(
|
|
727
|
+
canceled = await ax.activity.cancel_pending()
|
|
670
728
|
```
|
|
671
729
|
|
|
672
730
|
### Runners
|
|
@@ -728,13 +786,16 @@ ax.Base # SQLAlchemy declarative base for activity tables
|
|
|
728
786
|
All settings via environment variables:
|
|
729
787
|
|
|
730
788
|
```bash
|
|
731
|
-
# Redis
|
|
732
|
-
AGENTEXEC_REDIS_URL=redis://localhost:6379/0
|
|
789
|
+
# Redis
|
|
790
|
+
AGENTEXEC_REDIS_URL=redis://localhost:6379/0 # Also accepts REDIS_URL
|
|
791
|
+
AGENTEXEC_REDIS_POOL_SIZE=10
|
|
792
|
+
AGENTEXEC_REDIS_POOL_TIMEOUT=5
|
|
733
793
|
|
|
734
794
|
# Workers
|
|
735
795
|
AGENTEXEC_NUM_WORKERS=4
|
|
736
|
-
|
|
796
|
+
AGENTEXEC_QUEUE_PREFIX=agentexec_tasks # Also accepts AGENTEXEC_QUEUE_NAME
|
|
737
797
|
AGENTEXEC_GRACEFUL_SHUTDOWN_TIMEOUT=300
|
|
798
|
+
AGENTEXEC_MAX_TASK_RETRIES=3 # 0 to disable retries
|
|
738
799
|
|
|
739
800
|
# Database
|
|
740
801
|
AGENTEXEC_TABLE_PREFIX=agentexec_
|
|
@@ -742,11 +803,15 @@ AGENTEXEC_TABLE_PREFIX=agentexec_
|
|
|
742
803
|
# Results
|
|
743
804
|
AGENTEXEC_RESULT_TTL=3600
|
|
744
805
|
|
|
745
|
-
# Task locking
|
|
806
|
+
# Task locking (Redis backend only)
|
|
746
807
|
AGENTEXEC_LOCK_TTL=1800
|
|
747
808
|
|
|
809
|
+
# Scheduling
|
|
810
|
+
AGENTEXEC_SCHEDULER_TIMEZONE=UTC
|
|
811
|
+
AGENTEXEC_SCHEDULER_POLL_INTERVAL=10
|
|
812
|
+
|
|
748
813
|
# State backend
|
|
749
|
-
AGENTEXEC_STATE_BACKEND=agentexec.state.
|
|
814
|
+
AGENTEXEC_STATE_BACKEND=agentexec.state.redis # or agentexec.state.kafka
|
|
750
815
|
AGENTEXEC_KEY_PREFIX=agentexec
|
|
751
816
|
|
|
752
817
|
# Activity messages (customizable)
|
|
@@ -756,6 +821,21 @@ AGENTEXEC_ACTIVITY_MESSAGE_COMPLETE="Task completed successfully."
|
|
|
756
821
|
AGENTEXEC_ACTIVITY_MESSAGE_ERROR="Task failed with error: {error}"
|
|
757
822
|
```
|
|
758
823
|
|
|
824
|
+
### Kafka Settings
|
|
825
|
+
|
|
826
|
+
These settings only apply when using the Kafka state backend (`AGENTEXEC_STATE_BACKEND=agentexec.state.kafka`):
|
|
827
|
+
|
|
828
|
+
```bash
|
|
829
|
+
KAFKA_BOOTSTRAP_SERVERS=localhost:9092 # Also accepts AGENTEXEC_KAFKA_BOOTSTRAP_SERVERS
|
|
830
|
+
AGENTEXEC_KAFKA_DEFAULT_PARTITIONS=6 # Partitions for auto-created topics
|
|
831
|
+
AGENTEXEC_KAFKA_REPLICATION_FACTOR=1 # Replication factor for auto-created topics
|
|
832
|
+
AGENTEXEC_KAFKA_MAX_BATCH_SIZE=16384 # Producer max batch size (bytes)
|
|
833
|
+
AGENTEXEC_KAFKA_LINGER_MS=5 # Producer linger time (ms)
|
|
834
|
+
AGENTEXEC_KAFKA_RETENTION_MS=-1 # Retention for compacted topics (-1 = forever)
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
For single-node development, set `KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1` on your broker or consumer groups will hang.
|
|
838
|
+
|
|
759
839
|
---
|
|
760
840
|
|
|
761
841
|
## Development
|
|
@@ -804,4 +884,5 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
804
884
|
- **Documentation**: [docs/](docs/)
|
|
805
885
|
- **Example App**: [examples/openai-agents-fastapi/](examples/openai-agents-fastapi/)
|
|
806
886
|
- **Multi-Tenancy Example**: [examples/multi-tenancy/](examples/multi-tenancy/)
|
|
887
|
+
- **Queue Fairness Benchmark**: [examples/queue-fairness/](examples/queue-fairness/)
|
|
807
888
|
- **Issues**: [GitHub Issues](https://github.com/Agent-CI/agentexec/issues)
|