added async agentic inf - Celery + Redis Streams#287
Conversation
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.
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds 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. ChangesAsync Execution Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
|
|
||
|
|
||
| # 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 |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
wavefront/server/background_jobs/celery_worker/celery_worker/main.py (1)
1-8: ⚡ Quick winMove
load_dotenv()before imports or remove redundancy.
load_dotenv()is called at line 8, after importingcelery_appat line 4. However,celery_app.pyalready callsload_dotenv()at module level, making this call redundant. Additionally, the current order means environment variables are not loaded whencelery_appis first imported (thoughcelery_apphandles 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.pyalready 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
⛔ Files ignored due to path filters (1)
wavefront/server/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
wavefront/server/apps/floware/floware/server.pywavefront/server/background_jobs/celery_worker/celery_worker/celery_app.pywavefront/server/background_jobs/celery_worker/celery_worker/main.pywavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.pywavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.pywavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.pywavefront/server/background_jobs/celery_worker/pyproject.tomlwavefront/server/modules/agents_module/agents_module/agents_container.pywavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.pywavefront/server/modules/agents_module/agents_module/models/async_agentic_execution_schemas.pywavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_result_consumer.pywavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.pywavefront/server/modules/agents_module/agents_module/utils/celery_client.pywavefront/server/modules/agents_module/pyproject.tomlwavefront/server/modules/db_repo_module/db_repo_module/alembic/versions/2026_05_12_1549-3b5b1bf90e6c_add_async_agentic_executions.pywavefront/server/modules/db_repo_module/db_repo_module/cache/cache_manager.pywavefront/server/modules/db_repo_module/db_repo_module/db_repo_container.pywavefront/server/modules/db_repo_module/db_repo_module/models/async_agentic_execution.py
| 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'), | ||
| ) |
There was a problem hiding this comment.
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.
| 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: |
There was a problem hiding this comment.
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 winPush pagination
offsetinto the repository query instead of slicing in memory.Fetching
offset + limitrows and slicing in Python degrades linearly with deeper pages and wastes database/network resources. TheSQLAlchemyRepository.find()method currently supports onlylimitbut notoffset. Adding offset support is straightforward: addoffset: int = 0to 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 winAvoid constructing a new
Celeryinstance on every call.
get_celery_client()is invoked per enqueue request (async_agentic_execution_service.pylines 210 and 279). Each call instantiates a freshCelery('async_executor', ...)app, which performs broker URL parsing and internal setup every time. Cache the instance at module scope (or withfunctools.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 winCross-module import of private helpers couples workflow_task to agent_task.
workflow_task.pyreaches intoagent_taskfor 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 winAwait 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 anyfinally/except CancelledErrorcleanup in the consumer (e.g.,xackof in-flight messages, closing the Redis connection) won't get a chance to run before the lifespan context yields control. Suggest awaiting and swallowingCancelledError:♻️ 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
📒 Files selected for processing (11)
wavefront/server/apps/floware/floware/server.pywavefront/server/background_jobs/celery_worker/celery_worker/celery_app.pywavefront/server/background_jobs/celery_worker/celery_worker/env.pywavefront/server/background_jobs/celery_worker/celery_worker/tasks/agent_task.pywavefront/server/background_jobs/celery_worker/celery_worker/tasks/workflow_task.pywavefront/server/background_jobs/celery_worker/celery_worker/worker_setup.pywavefront/server/modules/agents_module/agents_module/controllers/async_inference_controller.pywavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_result_consumer.pywavefront/server/modules/agents_module/agents_module/services/async_agentic_execution_service.pywavefront/server/modules/agents_module/agents_module/utils/celery_client.pywavefront/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
| 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': '', | ||
| }, | ||
| ) |
There was a problem hiding this comment.
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.
| @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), | ||
| }, | ||
| } | ||
| ), | ||
| ) |
There was a problem hiding this comment.
🧩 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 -20Repository: rootflo/wavefront
Length of output: 158
🏁 Script executed:
#!/bin/bash
# Find the service file
fd -t f '.*async.*agentic.*execution.*service' -e pyRepository: rootflo/wavefront
Length of output: 158
🏁 Script executed:
#!/bin/bash
# Look for AsyncAgenticExecutionService class definition
rg -n 'class AsyncAgenticExecutionService' -A 50 -m1Repository: 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.pyRepository: 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 -60Repository: 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.pyRepository: 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.pyRepository: 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 -5Repository: 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 10Repository: 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.pyRepository: 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.pyRepository: 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 -40Repository: 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 -80Repository: 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 -60Repository: 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 tooutput.json/history.json— exposing cross-tenant LLM inputs and outputs. - Any authenticated caller can
GET /v1/agentic-executionsand enumerate all tenants' executions.
Additionally, list_executions lacks the ValueError → 4xx error handling used by other endpoints.
Required fixes:
- Add user/tenant ownership tracking to
AsyncAgenticExecutionmodel. - Extract auth credentials in both endpoints and pass to service layer for scoping.
- Filter queries by calling principal's tenant/user; optionally require
entity_idownership validation. - Wrap
list_executionsservice call intry/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.
There was a problem hiding this comment.
this is okay since the floware server is going to be a separate deployment per tenant
There was a problem hiding this comment.
@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.
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:
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