Thanks to visit codestin.com
Credit goes to github.com

Skip to content

added async agentic inf - Celery + Redis Streams#287

Merged
vizsatiz merged 3 commits into
developfrom
CU-86d306kfn-async-agentic-inference-celery
May 18, 2026
Merged

added async agentic inf - Celery + Redis Streams#287
vizsatiz merged 3 commits into
developfrom
CU-86d306kfn-async-agentic-inference-celery

Conversation

@rootflo-hardik

@rootflo-hardik rootflo-hardik commented May 14, 2026

Copy link
Copy Markdown
Contributor

Introduces v3 async inference endpoints for agents and workflows that return immediately with an execution_id, offloading LLM execution to a Celery worker.

Key design decisions:

  • Binary inputs (documents/images) are pre-saved to cloud storage before enqueuing so Celery task messages stay small (no base64 in Redis)
  • Worker has zero DB access — status updates (in_progress, completed, failed) are published to a Redis Stream (async_agentic_exec:results)
  • AsyncAgenticExecutionResultConsumer runs as a background asyncio task inside floware, reads the stream via consumer group, and writes to DB — resilient to floware restarts via consumer group offset tracking
  • Presigned URLs for output.json and history.json are generated at response time, never stored

New endpoints:
POST /floware/v3/agents/{agent_id}/inference → 202 + execution_id
POST /floware/v3/workflows/{workflow_id}/inference → 202 + execution_id
GET /floware/v1/agentic-executions/{id} → status + presigned URLs
GET /floware/v1/agentic-executions → list with filters

New table: async_agentic_executions
New package: background_jobs/celery_worker

Retries disabled by default; enable via CELERY_TASK_MAX_RETRIES env var.

Summary by CodeRabbit

  • New Features
    • End-to-end async execution for agents and workflows (enqueue, background processing, status updates).
    • New async REST endpoints to enqueue executions, fetch status, and list executions with filters/pagination.
    • Background worker and task processing with persisted outputs and presigned download links.
    • Real-time status consumption and caching for execution updates.
    • Database migration and schema to store async execution metadata.
  • Chore
    • Packaging/configuration added for the async worker.

Review Change Stack

Introduces v3 async inference endpoints for agents and workflows that return
immediately with an execution_id, offloading LLM execution to a Celery worker.

Key design decisions:
- Binary inputs (documents/images) are pre-saved to cloud storage before
  enqueuing so Celery task messages stay small (no base64 in Redis)
- Worker has zero DB access — status updates (in_progress, completed, failed)
  are published to a Redis Stream (async_agentic_exec:results)
- AsyncAgenticExecutionResultConsumer runs as a background asyncio task inside
  floware, reads the stream via consumer group, and writes to DB — resilient
  to floware restarts via consumer group offset tracking
- Presigned URLs for output.json and history.json are generated at response
  time, never stored

New endpoints:
  POST /floware/v3/agents/{agent_id}/inference     → 202 + execution_id
  POST /floware/v3/workflows/{workflow_id}/inference → 202 + execution_id
  GET  /floware/v1/agentic-executions/{id}          → status + presigned URLs
  GET  /floware/v1/agentic-executions               → list with filters

New table: async_agentic_executions
New package: background_jobs/celery_worker

Retries disabled by default; enable via CELERY_TASK_MAX_RETRIES env var.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@rootflo-hardik has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ba3775a-48c3-423d-a7f5-74e174e7b453

📥 Commits

Reviewing files that changed from the base of the PR and between 03e3b67 and c4a9357.

📒 Files selected for processing (5)
  • wavefront/server/apps/floware/floware/config.ini
  • wavefront/server/apps/floware/floware/server.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/env.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.py
  • wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py
📝 Walkthrough

Walkthrough

Adds end-to-end asynchronous agent/workflow execution: DB migration and ORM, execution service with binary input preprocessing and enqueueing, Celery worker app and tasks, Redis Streams result primitives and a FastAPI background consumer, API endpoints and wiring into the Floware app.

Changes

Async Execution Implementation

Layer / File(s) Summary
Database schema and Redis Streams primitives
wavefront/server/modules/db_repo_module/db_repo_module/alembic/versions/*.py, wavefront/server/modules/db_repo_module/db_repo_module/models/async_agentic_execution.py, wavefront/server/modules/db_repo_module/db_repo_module/db_repo_container.py, wavefront/server/modules/db_repo_module/db_repo_module/cache/cache_manager.py
New async_agentic_executions migration and ORM model; repository provider registered; CacheManager gains xadd, xgroup_create, xread_group, and xack for Redis Streams.
Async execution service and schemas
wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py, wavefront/server/modules/agents_module/agents_module/models/async_agentic_execution_schemas.py, wavefront/server/modules/agents_module/agents_module/utils/celery_client.py, wavefront/server/modules/agents_module/agents_module/agents_container.py, wavefront/server/modules/agents_module/pyproject.toml
AsyncAgenticExecutionService handles base64/document/image detection, uploads binary inputs to cloud storage, creates pending DB records, enqueues Celery tasks, updates records with task IDs, caches status with TTLs, returns presigned URLs, and lists executions. Adds Pydantic response schemas and wires DI providers.
Celery worker config and worker setup
wavefront/server/background_jobs/celery_worker/celery_worker/celery_app.py, wavefront/server/background_jobs/celery_worker/celery_worker/env.py, wavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.py, wavefront/server/background_jobs/celery_worker/celery_worker/main.py, wavefront/server/background_jobs/celery_worker/pyproject.toml
Defines Celery app (async_executor) and env constants; JSON-only serialization and reliability settings; worker singleton get_services() builds DB/cloud/cache/inference services per process; worker packaging and entrypoint added.
Agent and workflow Celery tasks
wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py, wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py
Tasks reconstruct stored inputs, call inference, save output.json and history.json to cloud storage, emit in_progress/completed/failed stream events, and run inside dedicated asyncio event loops that drain pending tasks before closing.
Result consumer (FastAPI background)
wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_result_consumer.py
Background consumer creates/uses a Redis consumer group, polls stream messages, parses execution updates (timestamps/error/output/history), updates DB records, and invalidates or populates cache based on terminal vs active status; exposes start/stop for FastAPI lifespan.
FastAPI controller and server wiring
wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py, wavefront/server/apps/floware/floware/server.py
Adds async_router with endpoints to enqueue agent/workflow executions, fetch execution status, and list executions; registers router under /floware; starts/stops the ResultConsumer during FastAPI lifespan with asyncio task management.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • vizsatiz
  • vishnurk6247

Poem

🐰 Hops of async glory, queues and streams arise,

Celery hums softly beneath wide-open skies.
Redis whispers status, storage keeps the tale,
Background consumer listens, ensures no update fails.
A rabbit cheers the pipeline — swift, dependable trails.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title references the main architectural change (async agentic inference via Celery + Redis Streams) but uses abbreviations ('agentic inf') that reduce clarity.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch CU-86d306kfn-async-agentic-inference-celery

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.



# revision identifiers, used by Alembic.
revision: str = '3b5b1bf90e6c'

# revision identifiers, used by Alembic.
revision: str = '3b5b1bf90e6c'
down_revision: Union[str, None] = 'e8f2a1c3b5d9'
# revision identifiers, used by Alembic.
revision: str = '3b5b1bf90e6c'
down_revision: Union[str, None] = 'e8f2a1c3b5d9'
branch_labels: Union[str, Sequence[str], None] = None
revision: str = '3b5b1bf90e6c'
down_revision: Union[str, None] = 'e8f2a1c3b5d9'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


from celery_worker.celery_app import app # noqa: E402, F401 — triggers autodiscover
import celery_worker.tasks.agent_task # noqa: F401

from celery_worker.celery_app import app # noqa: E402, F401 — triggers autodiscover
import celery_worker.tasks.agent_task # noqa: F401
import celery_worker.tasks.workflow_task # noqa: F401

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (1)
wavefront/server/background_jobs/celery_worker/celery_worker/main.py (1)

1-8: ⚡ Quick win

Move load_dotenv() before imports or remove redundancy.

load_dotenv() is called at line 8, after importing celery_app at line 4. However, celery_app.py already calls load_dotenv() at module level, making this call redundant. Additionally, the current order means environment variables are not loaded when celery_app is first imported (though celery_app handles this internally).

♻️ Proposed fix to remove redundancy
 from dotenv import load_dotenv
+load_dotenv()
 
 from celery_worker.celery_app import app  # noqa: E402, F401 — triggers autodiscover
 import celery_worker.tasks.agent_task  # noqa: F401
 import celery_worker.tasks.workflow_task  # noqa: F401
 
-load_dotenv()
-
 if __name__ == '__main__':

Or remove it entirely since celery_app.py already loads the environment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wavefront/server/background_jobs/celery_worker/celery_worker/main.py` around
lines 1 - 8, The file calls load_dotenv() after importing
celery_worker.celery_app (which itself already calls load_dotenv()), causing
redundancy and a potential ordering issue; fix by either moving the standalone
load_dotenv() call to the very top of this module before any imports (so
environment variables are guaranteed before importing celery_worker.celery_app)
or simply remove the load_dotenv() line since celery_worker.celery_app already
performs dotenv loading—update main.py accordingly and keep the imports to
reference app and tasks (celery_worker.celery_app,
celery_worker.tasks.agent_task, celery_worker.tasks.workflow_task).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wavefront/server/apps/floware/floware/server.py`:
- Around line 270-275: The consumer task is being fire-and-forgeted and not shut
down properly; keep a Task reference for
AsyncAgenticExecutionResultConsumer.start() (and similarly for
start_redis_listener) on the server instance, and on application shutdown call
the consumer.stop() method and await the stored Task to ensure cooperative
shutdown; update the wiring where AsyncAgenticExecutionResultConsumer is
instantiated to store the returned asyncio.Task (instead of discarding it) and
add shutdown logic that calls AsyncAgenticExecutionResultConsumer.stop() and
awaits the Task to avoid abrupt cancellation and RUF006.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py`:
- Line 114: The variable `namespace` is unpacked but never used; rename it to
`_namespace` at the unpacking site to mark it intentionally unused (i.e., change
the tuple assignment that currently includes `namespace` to use `_namespace`),
and update any local references if present to use the new name; keep the rest of
the logic in the same function (the unpacking location in agent_task.py)
unchanged.
- Around line 19-20: MAX_RETRIES and RETRY_DELAY are parsed with
int(os.getenv(...)) which raises ValueError on non-integer env values and will
break module import; update the parsing logic for the constants (MAX_RETRIES,
RETRY_DELAY) to validate and safely convert the env values by checking/os.getenv
return and wrapping int(...) in a try/except (catch ValueError) or using a
helper parse_int_with_default that returns the provided default on error, and
ensure you still convert valid numeric strings to int while falling back to '0'
and '30' respectively and optionally log a warning when a malformed value is
detected.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py`:
- Around line 19-20: Handle possible ValueError when parsing
CELERY_TASK_MAX_RETRIES and CELERY_TASK_RETRY_DELAY_SECONDS by wrapping the
int() conversions for MAX_RETRIES and RETRY_DELAY in a safe-parse routine: catch
ValueError (and TypeError) and fall back to the default values (0 and 30
respectively), and emit a warning via the module logger (or print) indicating
the invalid env var and the fallback used; implement this logic replacing the
direct int(os.getenv(...)) calls so imports won't fail on malformed env values.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.py`:
- Around line 80-82: The plain print statement exposing ADC project and
GOOGLE_CLOUD_PROJECT should be removed or guarded; replace the print in
worker_setup.py with a logger.debug call (e.g., logger =
logging.getLogger(__name__); logger.debug(...)) and ensure it only emits when
debug is enabled (wrap with a check like if settings.DEBUG or an explicit
ENV_DEBUG flag or rely on logger level configuration) so sensitive project
identifiers are not printed in production logs.

In
`@wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_result_consumer.py`:
- Around line 52-85: The comment and error-handling around Redis Streams in
AsyncAgenticExecutionResultConsumer are incorrect and allow failed messages to
remain stuck in the PEL; update the comments around _cache.xgroup_create and the
xread_group call to state that id='0' only sets the initial offset for new
groups and that XREADGROUP with '>' only returns new, never-delivered messages
(pending entries must be explicitly handled). Modify the consumer to explicitly
ack-and-skip permanently unprocessable payloads (e.g. when parsing execution_id
fails inside the _process(fields) flow) instead of letting them remain pending:
catch the specific parse/validation error, call _cache.xack for the msg_id, log
the problem, and continue. As an improvement add a startup or periodic
pending-entry drain (call XAUTOCLAIM/XCLAIM or an initial XREADGROUP with a
numeric id) to reclaim stale PEL entries for retry; keep existing general
retry/backoff for transient processing errors but do not rely on process restart
to redeliver pending messages.

In
`@wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py`:
- Around line 380-382: The pagination is applied after limiting (records = await
self.repo.find(**filters, limit=limit) then records = records[offset:]) which
breaks pages; update the call to self.repo.find to pass both offset and limit
(i.e., include offset=offset alongside limit=limit) and remove the in-memory
slice, so pagination is done by the repository; locate this change in the
AsyncAgenticExecutionService method where self.repo.find is called and adjust
parameters accordingly (or if repository lacks support, fetch all then slice,
but prefer adding offset to repo.find).
- Around line 203-210: The DB record is created before calling
get_celery_client().send_task, so if send_task raises we leave a permanent
"pending" row; wrap the send_task call in a try/except and on failure perform a
compensating update via the repository (e.g., use self.repo.find_one_and_update
on the execution id to set status='failed' and record the exception message in
an error field, or delete the execution row if that is preferred) and re-raise
or return an appropriate error; apply the same try/except+compensation pattern
to the other enqueue path that uses get_celery_client().send_task (the block
referenced around lines 265-272) so no orphaned pending records remain.
- Around line 129-134: Replace the naked base64 decoding with a guarded decode
that catches invalid input: wrap the base64.b64decode(raw_b64, validate=True)
call in a try/except that catches binascii.Error/ValueError and returns or
raises a controlled validation error (e.g., raise a ValidationError or return a
4xx response) instead of letting the exception bubble, then only call
self.cloud_storage.save_small_file(file_content=file_bytes,
bucket_name=self.bucket, key=key) after a successful decode; reference the
raw_b64 variable, the file_bytes result, and the
self.cloud_storage.save_small_file call to locate where to add the try/except
and error mapping.

In `@wavefront/server/modules/agents_module/agents_module/utils/celery_client.py`:
- Around line 6-7: get_celery_client currently uses
os.environ['CELERY_BROKER_URL'] which will raise a raw KeyError on missing
config; change it to read the broker via os.getenv('CELERY_BROKER_URL') (or
os.getenv with a sensible default used elsewhere) and explicitly validate the
result in get_celery_client: if the value is None or empty, raise a clear
RuntimeError (or ValueError) with a descriptive message about the missing
CELERY_BROKER_URL, otherwise pass the value into Celery('async_executor',
broker=<validated_value>); reference the get_celery_client function and Celery
call when making this change.

In
`@wavefront/server/modules/db_repo_module/db_repo_module/alembic/versions/2026_05_12_1549-3b5b1bf90e6c_add_async_agentic_executions.py`:
- Around line 24-57: The migration creates the async_agentic_executions table
with free-form entity_type and status columns which need DB-level constraints;
update the migration that defines table async_agentic_executions to add
constraints for entity_type and status (either replace those sa.String columns
with a database enum type or add sa.CheckConstraint checks) and ensure the
chosen allowed values (e.g., the lifecycle statuses used by your application)
are enforced and created/dropped in upgrade()/downgrade() so the constraint is
present for status and entity_type validation at the DB level.

---

Nitpick comments:
In `@wavefront/server/background_jobs/celery_worker/celery_worker/main.py`:
- Around line 1-8: The file calls load_dotenv() after importing
celery_worker.celery_app (which itself already calls load_dotenv()), causing
redundancy and a potential ordering issue; fix by either moving the standalone
load_dotenv() call to the very top of this module before any imports (so
environment variables are guaranteed before importing celery_worker.celery_app)
or simply remove the load_dotenv() line since celery_worker.celery_app already
performs dotenv loading—update main.py accordingly and keep the imports to
reference app and tasks (celery_worker.celery_app,
celery_worker.tasks.agent_task, celery_worker.tasks.workflow_task).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1dd2e00c-ae0d-4344-b536-262aecd431bd

📥 Commits

Reviewing files that changed from the base of the PR and between 3c61d57 and 328dfb9.

⛔ Files ignored due to path filters (1)
  • wavefront/server/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • wavefront/server/apps/floware/floware/server.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/celery_app.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/main.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.py
  • wavefront/server/background_jobs/celery_worker/pyproject.toml
  • wavefront/server/modules/agents_module/agents_module/agents_container.py
  • wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py
  • wavefront/server/modules/agents_module/agents_module/models/async_agentic_execution_schemas.py
  • wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_result_consumer.py
  • wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py
  • wavefront/server/modules/agents_module/agents_module/utils/celery_client.py
  • wavefront/server/modules/agents_module/pyproject.toml
  • wavefront/server/modules/db_repo_module/db_repo_module/alembic/versions/2026_05_12_1549-3b5b1bf90e6c_add_async_agentic_executions.py
  • wavefront/server/modules/db_repo_module/db_repo_module/cache/cache_manager.py
  • wavefront/server/modules/db_repo_module/db_repo_module/db_repo_container.py
  • wavefront/server/modules/db_repo_module/db_repo_module/models/async_agentic_execution.py

Comment thread wavefront/server/apps/floware/floware/server.py Outdated
Comment thread wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py Outdated
Comment thread wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py Outdated
Comment thread wavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.py Outdated
Comment thread wavefront/server/modules/agents_module/agents_module/utils/celery_client.py Outdated
Comment on lines +24 to +57
op.create_table(
'async_agentic_executions',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('entity_type', sa.String(length=32), nullable=False),
sa.Column('entity_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('celery_task_id', sa.String(length=255), nullable=True),
sa.Column(
'status',
sa.String(length=32),
nullable=False,
server_default=sa.text("'pending'"),
),
sa.Column('input_bucket', sa.String(length=255), nullable=True),
sa.Column('inputs', sa.Text(), nullable=True),
sa.Column('input_files', sa.Text(), nullable=True),
sa.Column('output_file', sa.String(length=1024), nullable=True),
sa.Column('history_file', sa.String(length=1024), nullable=True),
sa.Column('error', sa.Text(), nullable=True),
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
sa.Column(
'created_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text('now()'),
),
sa.Column(
'updated_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text('now()'),
),
sa.PrimaryKeyConstraint('id'),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add DB constraints for entity_type and status.

Line 24 onward creates free-form string columns for lifecycle-critical fields. Without DB-level constraints, invalid values can be persisted and break status filtering/consumer logic.

Suggested migration adjustment
 op.create_table(
     'async_agentic_executions',
@@
-    sa.Column('entity_type', sa.String(length=32), nullable=False),
+    sa.Column('entity_type', sa.String(length=32), nullable=False),
@@
-    sa.Column(
-        'status',
-        sa.String(length=32),
-        nullable=False,
-        server_default=sa.text("'pending'"),
-    ),
+    sa.Column(
+        'status',
+        sa.String(length=32),
+        nullable=False,
+        server_default=sa.text("'pending'"),
+    ),
@@
-    sa.PrimaryKeyConstraint('id'),
+    sa.CheckConstraint(
+        "entity_type IN ('agent', 'workflow')",
+        name='ck_async_agentic_executions_entity_type',
+    ),
+    sa.CheckConstraint(
+        "status IN ('pending', 'in_progress', 'completed', 'failed')",
+        name='ck_async_agentic_executions_status',
+    ),
+    sa.PrimaryKeyConstraint('id'),
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
op.create_table(
'async_agentic_executions',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('entity_type', sa.String(length=32), nullable=False),
sa.Column('entity_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('celery_task_id', sa.String(length=255), nullable=True),
sa.Column(
'status',
sa.String(length=32),
nullable=False,
server_default=sa.text("'pending'"),
),
sa.Column('input_bucket', sa.String(length=255), nullable=True),
sa.Column('inputs', sa.Text(), nullable=True),
sa.Column('input_files', sa.Text(), nullable=True),
sa.Column('output_file', sa.String(length=1024), nullable=True),
sa.Column('history_file', sa.String(length=1024), nullable=True),
sa.Column('error', sa.Text(), nullable=True),
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
sa.Column(
'created_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text('now()'),
),
sa.Column(
'updated_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text('now()'),
),
sa.PrimaryKeyConstraint('id'),
)
op.create_table(
'async_agentic_executions',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('entity_type', sa.String(length=32), nullable=False),
sa.Column('entity_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('celery_task_id', sa.String(length=255), nullable=True),
sa.Column(
'status',
sa.String(length=32),
nullable=False,
server_default=sa.text("'pending'"),
),
sa.Column('input_bucket', sa.String(length=255), nullable=True),
sa.Column('inputs', sa.Text(), nullable=True),
sa.Column('input_files', sa.Text(), nullable=True),
sa.Column('output_file', sa.String(length=1024), nullable=True),
sa.Column('history_file', sa.String(length=1024), nullable=True),
sa.Column('error', sa.Text(), nullable=True),
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
sa.Column(
'created_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text('now()'),
),
sa.Column(
'updated_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text('now()'),
),
sa.CheckConstraint(
"entity_type IN ('agent', 'workflow')",
name='ck_async_agentic_executions_entity_type',
),
sa.CheckConstraint(
"status IN ('pending', 'in_progress', 'completed', 'failed')",
name='ck_async_agentic_executions_status',
),
sa.PrimaryKeyConstraint('id'),
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/db_repo_module/db_repo_module/alembic/versions/2026_05_12_1549-3b5b1bf90e6c_add_async_agentic_executions.py`
around lines 24 - 57, The migration creates the async_agentic_executions table
with free-form entity_type and status columns which need DB-level constraints;
update the migration that defines table async_agentic_executions to add
constraints for entity_type and status (either replace those sa.String columns
with a database enum type or add sa.CheckConstraint checks) and ensure the
chosen allowed values (e.g., the lifecycle statuses used by your application)
are enforced and created/dropped in upgrade()/downgrade() so the constraint is
present for status and entity_type validation at the DB level.

- server.py: store AsyncAgenticExecutionResultConsumer task reference and call consumer.stop() + await task on shutdown instead of fire-and-forget
- celery_worker/env.py: centralise all env var reads; remove scattered os.environ/os.getenv calls from celery_app.py, worker_setup.py, and both task files
async_agentic_execution_result_consumer.py: fix xgroup_create comment (id='0' sets initial cursor only, not PEL redelivery); fix error message from "redelivered on next restart" to "remains in PEL until a claim/drain runs"
- async_agentic_execution_service.py: guard base64.b64decode with validate=True and catch binascii.Error to reject malformed input as a ValueError; wrap both send_task calls with compensating status='failed' DB update on broker failure; fix list_executions pagination by fetching offset + limit rows instead of limit then slicing
- async_inference_controller.py: catch ValueError from create_and_enqueue_agent/workflow and return 422 instead of 500
- celery_client.py: replace os.environ['CELERY_BROKER_URL'] with explicit RuntimeError on missing env var
- cache_manager.py: add @Retry decorator to xread_group consistent with xadd and xack
if record_dict.get('input_files'):
try:
input_files = json.loads(record_dict['input_files'])
except Exception:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py (1)

384-403: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Push pagination offset into the repository query instead of slicing in memory.

Fetching offset + limit rows and slicing in Python degrades linearly with deeper pages and wastes database/network resources. The SQLAlchemyRepository.find() method currently supports only limit but not offset. Adding offset support is straightforward: add offset: int = 0 to the method signature and chain .offset(offset) before .limit(limit) in both code paths of the query builder. This change at the repository layer benefits all paginated endpoints.

♻️ Proposed fix
-        records = await self.repo.find(**filters, limit=offset + limit)
-        records = records[offset:]
+        records = await self.repo.find(**filters, offset=offset, limit=limit)

And update the repository signature from:

async def find(self, limit: int = 100, **filters) -> list[T]:

To:

async def find(self, offset: int = 0, limit: int = 100, **filters) -> list[T]:

Then add .offset(offset) to the query before .limit(limit).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py`
around lines 384 - 403, The list_executions implementation is doing in-memory
pagination by calling repo.find(limit=offset+limit) and slicing
records[offset:], which is inefficient; update the repository and call sites to
support SQL-level offset. Change SQLAlchemyRepository.find signature from def
find(self, limit: int = 100, **filters) to def find(self, offset: int = 0,
limit: int = 100, **filters) and in the query builder chain .offset(offset)
before .limit(limit) in both code paths; then update
AsyncAgenticExecutionService.list_executions to call
self.repo.find(offset=offset, limit=limit, **filters) and remove the Python
slice so it returns [self._build_status_response(r.to_dict()) for r in records].
🧹 Nitpick comments (3)
wavefront/server/modules/agents_module/agents_module/utils/celery_client.py (1)

6-10: ⚡ Quick win

Avoid constructing a new Celery instance on every call.

get_celery_client() is invoked per enqueue request (async_agentic_execution_service.py lines 210 and 279). Each call instantiates a fresh Celery('async_executor', ...) app, which performs broker URL parsing and internal setup every time. Cache the instance at module scope (or with functools.lru_cache) so it is built once per process.

♻️ Proposed fix
 import os
+from functools import lru_cache

 from celery import Celery


+@lru_cache(maxsize=1)
 def get_celery_client() -> Celery:
     broker_url = os.getenv('CELERY_BROKER_URL')
     if not broker_url:
         raise RuntimeError('Missing required env var: CELERY_BROKER_URL')
     return Celery('async_executor', broker=broker_url)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wavefront/server/modules/agents_module/agents_module/utils/celery_client.py`
around lines 6 - 10, get_celery_client currently constructs a new
Celery('async_executor', broker=...) on every call; change it to build the
Celery app once and return the cached instance (e.g., create a module-level
variable like _celery_app or decorate get_celery_client with
functools.lru_cache(maxsize=1)) so repeated calls reuse the same Celery object;
keep the existing env var check for CELERY_BROKER_URL and ensure the cached
instance is created using that value and returned by get_celery_client.
wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py (1)

8-13: ⚡ Quick win

Cross-module import of private helpers couples workflow_task to agent_task.

workflow_task.py reaches into agent_task for four leading-underscore helpers (_build_history, _now, _reconstruct_inputs, _save_json). Importing private symbols across modules signals these are shared utilities — promote them to a dedicated helpers module (e.g., celery_worker/tasks/_helpers.py) so both tasks import from a neutral location and the contract is explicit.

♻️ Suggested layout
# celery_worker/tasks/_helpers.py
def now() -> str: ...
def reconstruct_inputs(payload, cloud_storage): ...
def save_json(cloud_storage, bucket, key, payload) -> None: ...
def build_history(payload, result, exec_time): ...
-from celery_worker.tasks.agent_task import (
-    _build_history,
-    _now,
-    _reconstruct_inputs,
-    _save_json,
-)
+from celery_worker.tasks._helpers import (
+    build_history,
+    now,
+    reconstruct_inputs,
+    save_json,
+)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py`
around lines 8 - 13, Refactor the four private helpers (_build_history, _now,
_reconstruct_inputs, _save_json) out of agent_task into a new neutral module
(e.g., celery_worker/tasks/_helpers.py) with public names (build_history →
build_history, _now → now, _reconstruct_inputs → reconstruct_inputs, _save_json
→ save_json); move the implementations into that file, update both agent_task
and workflow_task to import these helpers from celery_worker.tasks._helpers
instead of importing leading-underscore symbols, and update any call sites in
workflow_task.py and agent_task.py to use the new public function names to
remove the cross-module private import coupling.
wavefront/server/apps/floware/floware/server.py (1)

286-296: ⚡ Quick win

Await the cancelled task so cleanup completes before lifespan exits.

After async_agentic_exec_consumer_task.cancel() you return immediately without awaiting it. The task may still be mid-xread_group/DB-update when cancellation is requested, and any finally/except CancelledError cleanup in the consumer (e.g., xack of in-flight messages, closing the Redis connection) won't get a chance to run before the lifespan context yields control. Suggest awaiting and swallowing CancelledError:

♻️ Proposed shutdown tweak
         scheduler_manager.shutdown()
         async_agentic_exec_consumer.stop()
         try:
             await asyncio.wait_for(async_agentic_exec_consumer_task, timeout=5)
         except asyncio.TimeoutError:
             async_agentic_exec_consumer_task.cancel()
             logger.warning(
                 'AsyncAgenticExecutionResultConsumer did not stop within 5s; cancelled'
             )
+            try:
+                await async_agentic_exec_consumer_task
+            except asyncio.CancelledError:
+                pass
         logger.info('Shutting down application...')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wavefront/server/apps/floware/floware/server.py` around lines 286 - 296, The
shutdown currently cancels async_agentic_exec_consumer_task on TimeoutError but
returns without awaiting it, which can prevent the consumer's cleanup from
running; after calling async_agentic_exec_consumer_task.cancel() (and
logger.warning) await the task and catch and swallow asyncio.CancelledError (or
use asyncio.shield/asyncio.wait_for as appropriate) so the consumer's
finally/cleanup (e.g., xack/closing Redis) runs before lifespan exits; keep the
existing scheduler_manager.shutdown() and async_agentic_exec_consumer.stop()
calls and only proceed after the awaited cancellation completes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wavefront/server/apps/floware/floware/server.py`:
- Around line 186-188: Replace the reuse of agent_yaml_bucket for execution
artifacts by adding a new config key agents.executions_bucket and wiring it
through where the bucket is passed: change the call site that currently uses
executions_bucket=config['agents']['agent_yaml_bucket'] in floware.server (same
invocation that passes async_agentic_execution_repository) to use
executions_bucket=config['agents']['executions_bucket'], and make the equivalent
change in celery_worker.worker_setup (where the worker reads agent YAML bucket)
so both files read agents.executions_bucket; also update the
config/schema/defaults to define agents.executions_bucket and ensure any code
that constructs S3/GS clients or performs lifecycle/ACL operations references
the new key instead of agent_yaml_bucket.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py`:
- Around line 17-30: The current services.cache.xadd call that emits the
'in_progress' event in _run is outside the try/except so a Redis error can
bubble out and prevent the workflow from publishing a 'failed' event; wrap the
services.cache.xadd(STREAM_NAME, {...}) call either inside the existing try
block that surrounds the workflow execution or in its own small try/except that
catches/logs the xadd failure (using execution_id) and continues so the main run
proceeds and the existing failure-handling path can still publish the 'failed'
event; ensure you reference the same STREAM_NAME, the execution_id from payload,
and keep the error field consistent with the other event emissions.

In
`@wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py`:
- Around line 157-229: get_execution_status and list_executions lack tenant/user
scoping and error handling, allowing cross-tenant reads; add ownership fields to
the AsyncAgenticExecution model (e.g., tenant_id and user_id), call
extract_auth_credentials(request) in both
async_inference_controller.get_execution_status and .list_executions to obtain
the caller principal, pass those credentials into AsyncAgenticExecutionService
methods (get_execution_status and list_executions) so the service filters by
tenant_id/user_id (and validates entity_id ownership when entity_id is
provided), and wrap the list_executions service call in a try/except ValueError
to return the same 4xx JSONResponse pattern used in get_execution_status.
- Around line 50-68: The controller currently builds an incomplete llm_config
dict (only 'type','llm_model','api_key','base_url') which drops required fields
like display_name, parameters and model_type and will cause
LlmInferenceConfig(**llm_config_dict) to fail; instead pass the full
llm_config_dict returned from
llm_inference_config_service.get_config(payload.llm_inference_config_id) into
the Celery task (or serialize the full LlmInferenceConfig object) rather than
whitelisting fields here—update async_inference_controller.py to remove the
field subset logic and forward the complete dict (or a serialized
LlmInferenceConfig) so agent_task.py receives the full config.

---

Duplicate comments:
In
`@wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py`:
- Around line 384-403: The list_executions implementation is doing in-memory
pagination by calling repo.find(limit=offset+limit) and slicing
records[offset:], which is inefficient; update the repository and call sites to
support SQL-level offset. Change SQLAlchemyRepository.find signature from def
find(self, limit: int = 100, **filters) to def find(self, offset: int = 0,
limit: int = 100, **filters) and in the query builder chain .offset(offset)
before .limit(limit) in both code paths; then update
AsyncAgenticExecutionService.list_executions to call
self.repo.find(offset=offset, limit=limit, **filters) and remove the Python
slice so it returns [self._build_status_response(r.to_dict()) for r in records].

---

Nitpick comments:
In `@wavefront/server/apps/floware/floware/server.py`:
- Around line 286-296: The shutdown currently cancels
async_agentic_exec_consumer_task on TimeoutError but returns without awaiting
it, which can prevent the consumer's cleanup from running; after calling
async_agentic_exec_consumer_task.cancel() (and logger.warning) await the task
and catch and swallow asyncio.CancelledError (or use
asyncio.shield/asyncio.wait_for as appropriate) so the consumer's
finally/cleanup (e.g., xack/closing Redis) runs before lifespan exits; keep the
existing scheduler_manager.shutdown() and async_agentic_exec_consumer.stop()
calls and only proceed after the awaited cancellation completes.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py`:
- Around line 8-13: Refactor the four private helpers (_build_history, _now,
_reconstruct_inputs, _save_json) out of agent_task into a new neutral module
(e.g., celery_worker/tasks/_helpers.py) with public names (build_history →
build_history, _now → now, _reconstruct_inputs → reconstruct_inputs, _save_json
→ save_json); move the implementations into that file, update both agent_task
and workflow_task to import these helpers from celery_worker.tasks._helpers
instead of importing leading-underscore symbols, and update any call sites in
workflow_task.py and agent_task.py to use the new public function names to
remove the cross-module private import coupling.

In `@wavefront/server/modules/agents_module/agents_module/utils/celery_client.py`:
- Around line 6-10: get_celery_client currently constructs a new
Celery('async_executor', broker=...) on every call; change it to build the
Celery app once and return the cached instance (e.g., create a module-level
variable like _celery_app or decorate get_celery_client with
functools.lru_cache(maxsize=1)) so repeated calls reuse the same Celery object;
keep the existing env var check for CELERY_BROKER_URL and ensure the cached
instance is created using that value and returned by get_celery_client.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7500d73d-ba58-4490-ae3c-e943ce3e2558

📥 Commits

Reviewing files that changed from the base of the PR and between 328dfb9 and 03e3b67.

📒 Files selected for processing (11)
  • wavefront/server/apps/floware/floware/server.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/celery_app.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/env.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py
  • wavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.py
  • wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py
  • wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_result_consumer.py
  • wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py
  • wavefront/server/modules/agents_module/agents_module/utils/celery_client.py
  • wavefront/server/modules/db_repo_module/db_repo_module/cache/cache_manager.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • wavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.py

Comment thread wavefront/server/apps/floware/floware/server.py
Comment on lines +17 to +30
async def _run(task, payload: Dict) -> None:
services = get_services()
execution_id = payload['execution_id']

# Signal in_progress — floware consumer updates DB
services.cache.xadd(
STREAM_NAME,
{
'execution_id': execution_id,
'status': 'in_progress',
'started_at': _now(),
'error': '',
},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

in_progress emit is outside the try/except.

If xadd for the in_progress event raises (transient Redis hiccup, network blip), the exception bubbles to the Celery wrapper without a corresponding failed event being published. The consumer then never updates the row from pending and the execution stays orphaned in DB while Celery may or may not retry based on MAX_RETRIES (default 0). Move the in_progress emit inside the same try/except, or wrap it in its own try that logs+continues so the actual workflow run still executes.

🛡️ Proposed fix
-    # Signal in_progress — floware consumer updates DB
-    services.cache.xadd(
-        STREAM_NAME,
-        {
-            'execution_id': execution_id,
-            'status': 'in_progress',
-            'started_at': _now(),
-            'error': '',
-        },
-    )
-
     try:
+        # Signal in_progress — floware consumer updates DB
+        services.cache.xadd(
+            STREAM_NAME,
+            {
+                'execution_id': execution_id,
+                'status': 'in_progress',
+                'started_at': _now(),
+                'error': '',
+            },
+        )
         inputs = _reconstruct_inputs(payload, services.cloud_storage)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.py`
around lines 17 - 30, The current services.cache.xadd call that emits the
'in_progress' event in _run is outside the try/except so a Redis error can
bubble out and prevent the workflow from publishing a 'failed' event; wrap the
services.cache.xadd(STREAM_NAME, {...}) call either inside the existing try
block that surrounds the workflow execution or in its own small try/except that
catches/logs the xadd failure (using execution_id) and continues so the main run
proceeds and the existing failure-handling path can still publish the 'failed'
event; ensure you reference the same STREAM_NAME, the execution_id from payload,
and keep the error field consistent with the other event emissions.

Comment on lines +157 to +229
@async_router.get('/v1/agentic-executions/{execution_id}')
@inject
async def get_execution_status(
execution_id: UUID,
async_agentic_execution_service: AsyncAgenticExecutionService = Depends(
Provide[AgentsContainer.async_agentic_execution_service]
),
response_formatter: ResponseFormatter = Depends(
Provide[CommonContainer.response_formatter]
),
):
try:
result = await async_agentic_execution_service.get_execution_status(
execution_id
)
except ValueError as e:
return JSONResponse(
status_code=status.HTTP_404_NOT_FOUND,
content=response_formatter.buildErrorResponse(str(e)),
)

return JSONResponse(
status_code=status.HTTP_200_OK,
content=response_formatter.buildSuccessResponse(
{
'message': 'Execution status retrieved successfully',
'data': result.model_dump(mode='json'),
}
),
)


@async_router.get('/v1/agentic-executions')
@inject
async def list_executions(
entity_id: Optional[UUID] = Query(
None, description='Filter by agent or workflow UUID'
),
entity_type: Optional[str] = Query(
None, description='Filter by entity type: agent or workflow'
),
execution_status: Optional[str] = Query(
None, alias='status', description='Filter by status'
),
offset: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
async_agentic_execution_service: AsyncAgenticExecutionService = Depends(
Provide[AgentsContainer.async_agentic_execution_service]
),
response_formatter: ResponseFormatter = Depends(
Provide[CommonContainer.response_formatter]
),
):
results = await async_agentic_execution_service.list_executions(
entity_id=entity_id,
entity_type=entity_type,
status=execution_status,
offset=offset,
limit=limit,
)

return JSONResponse(
status_code=status.HTTP_200_OK,
content=response_formatter.buildSuccessResponse(
{
'message': 'Executions retrieved successfully',
'data': {
'executions': [r.model_dump(mode='json') for r in results],
'count': len(results),
},
}
),
)

@coderabbitai coderabbitai Bot May 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm AsyncAgenticExecutionService does not perform implicit auth/tenant scoping
fd -t f 'async_agentic_execution_service' --exec rg -nP -C4 'def\s+(get_execution_status|list_executions)\b' {}
# Confirm RequireAuthMiddleware only authenticates (does not authorize specific resources)
fd -t f 'require_auth' --exec rg -nP -C3 'class\s+RequireAuthMiddleware|def\s+(dispatch|__call__)' {}
# Cross-check the async_agentic_executions ORM/table for user_id / tenant columns
fd -t f 'async_agentic_execution' --exec rg -nP -C2 'user_id|tenant_id|owner|workspace_id' {}

Repository: rootflo/wavefront

Length of output: 1181


🏁 Script executed:

#!/bin/bash
# Get full implementation of AsyncAgenticExecutionService methods
fd -t f -i 'async_agentic_execution_service' | head -20

Repository: rootflo/wavefront

Length of output: 158


🏁 Script executed:

#!/bin/bash
# Find the service file
fd -t f '.*async.*agentic.*execution.*service' -e py

Repository: rootflo/wavefront

Length of output: 158


🏁 Script executed:

#!/bin/bash
# Look for AsyncAgenticExecutionService class definition
rg -n 'class AsyncAgenticExecutionService' -A 50 -m1

Repository: rootflo/wavefront

Length of output: 7052


🏁 Script executed:

#!/bin/bash
# Check the controller file for extract_auth_credentials usage
rg -n 'extract_auth_credentials' wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py

Repository: rootflo/wavefront

Length of output: 263


🏁 Script executed:

#!/bin/bash
# Find all extract_auth_credentials calls in the controller to see which endpoints use it
rg -B10 'extract_auth_credentials' wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py | head -60

Repository: rootflo/wavefront

Length of output: 1491


🏁 Script executed:

#!/bin/bash
# Get full implementation of get_execution_status and list_executions methods
rg -n 'async def get_execution_status' -A 30 wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py

Repository: rootflo/wavefront

Length of output: 1301


🏁 Script executed:

#!/bin/bash
# Get full implementation of list_executions
rg -n 'async def list_executions' -A 50 wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py

Repository: rootflo/wavefront

Length of output: 825


🏁 Script executed:

#!/bin/bash
# Find the ORM model for AsyncAgenticExecution
fd -t f 'async.*agentic.*execution' -e py | xargs rg -l 'class AsyncAgenticExecution' | head -5

Repository: rootflo/wavefront

Length of output: 352


🏁 Script executed:

#!/bin/bash
# Get the RequireAuthMiddleware implementation
rg -n 'class RequireAuthMiddleware' -A 20 wavefront/server/modules/agents_module/agents_module/

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check what extract_auth_credentials does in auth_utils
rg -n 'def extract_auth_credentials' -A 10

Repository: rootflo/wavefront

Length of output: 1250


🏁 Script executed:

#!/bin/bash
# Get the AsyncAgenticExecution ORM model
cat wavefront/server/modules/db_repo_module/db_repo_module/models/async_agentic_execution.py

Repository: rootflo/wavefront

Length of output: 2286


🏁 Script executed:

#!/bin/bash
# Get full RequireAuthMiddleware implementation from common_module
rg -n 'class RequireAuthMiddleware' -A 30 wavefront/server/modules/

Repository: rootflo/wavefront

Length of output: 4699


🏁 Script executed:

#!/bin/bash
# Get full extract_auth_credentials implementation
cat wavefront/server/modules/agents_module/agents_module/utils/auth_utils.py

Repository: rootflo/wavefront

Length of output: 827


🏁 Script executed:

#!/bin/bash
# Check how the two inference endpoints (that DO call extract_auth_credentials) use the credentials
rg -n 'access_token, app_key = extract_auth_credentials' -A 15 wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py | head -40

Repository: rootflo/wavefront

Length of output: 1438


🏁 Script executed:

#!/bin/bash
# Check how create_and_enqueue_agent and create_and_enqueue_workflow use extracted credentials
rg -n 'async def create_and_enqueue' -A 25 wavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.py | head -80

Repository: rootflo/wavefront

Length of output: 2049


🏁 Script executed:

#!/bin/bash
# Check if credentials are passed to service methods in the controller
rg -n 'create_and_enqueue' wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py -B 5 -A 10 | head -60

Repository: rootflo/wavefront

Length of output: 1322


🏁 Script executed:

#!/bin/bash
# Verify what RootfloHeaders.CLIENT_KEY is
rg -n 'CLIENT_KEY' wavefront/server/modules/user_management_module/

Repository: rootflo/wavefront

Length of output: 701


Missing per-user/tenant authorization on execution read endpoints.

get_execution_status and list_executions never call extract_auth_credentials(request) and do not scope queries by user or tenant. The ORM model AsyncAgenticExecution contains no user_id, tenant_id, or similar ownership columns. The other two endpoints in this controller do extract credentials and pass them to their service methods (though for downstream executor authentication, not resource scoping). With RequireAuthMiddleware enforcing only authentication, not authorization, the practical effect is:

  • Any authenticated caller can GET /v1/agentic-executions/{execution_id} for any execution and receive presigned URLs to output.json / history.json — exposing cross-tenant LLM inputs and outputs.
  • Any authenticated caller can GET /v1/agentic-executions and enumerate all tenants' executions.

Additionally, list_executions lacks the ValueError → 4xx error handling used by other endpoints.

Required fixes:

  1. Add user/tenant ownership tracking to AsyncAgenticExecution model.
  2. Extract auth credentials in both endpoints and pass to service layer for scoping.
  3. Filter queries by calling principal's tenant/user; optionally require entity_id ownership validation.
  4. Wrap list_executions service call in try/except ValueError.
🧰 Tools
🪛 Ruff (0.15.12)

[warning] 161-163: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


[warning] 164-166: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


[warning] 192-194: Do not perform function call Query in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


[warning] 203-205: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


[warning] 206-208: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py`
around lines 157 - 229, get_execution_status and list_executions lack
tenant/user scoping and error handling, allowing cross-tenant reads; add
ownership fields to the AsyncAgenticExecution model (e.g., tenant_id and
user_id), call extract_auth_credentials(request) in both
async_inference_controller.get_execution_status and .list_executions to obtain
the caller principal, pass those credentials into AsyncAgenticExecutionService
methods (get_execution_status and list_executions) so the service filters by
tenant_id/user_id (and validates entity_id ownership when entity_id is
provided), and wrap the list_executions service call in a try/except ValueError
to return the same 4xx JSONResponse pattern used in get_execution_status.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is okay since the floware server is going to be a separate deployment per tenant

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rootflo-hardik, understood — with a dedicated floware server instance per tenant, the execution records in async_agentic_executions are already isolated at the infrastructure level, so cross-tenant reads are not possible. That's a valid architectural justification. I'll note this for future reviews.


✏️ Learnings added
Learnt from: rootflo-hardik
Repo: rootflo/wavefront PR: 287
File: wavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.py:157-229
Timestamp: 2026-05-15T08:21:18.955Z
Learning: The `floware` server (wavefront/server/apps/floware) is deployed as a separate instance per tenant. There is no multi-tenancy within a single deployment, so `async_agentic_executions` records (and other execution/agent data) do not require user_id/tenant_id ownership columns or per-request tenant scoping — isolation is enforced at the infrastructure level.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@vizsatiz vizsatiz merged commit e6a251d into develop May 18, 2026
9 checks passed
@vizsatiz vizsatiz deleted the CU-86d306kfn-async-agentic-inference-celery branch May 18, 2026 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants