Skip to content

Orm#6

Merged
iidsample merged 17 commits into
mainfrom
orm
Jul 16, 2026
Merged

Orm#6
iidsample merged 17 commits into
mainfrom
orm

Conversation

@Saaketh0

@Saaketh0 Saaketh0 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Added the ORM, with the global controller periodically polling each local controller for its requests, gathering those requests, then pushing to a SQL database.

Current code connects to an arbitrary sqlite db

Below is the schema used:
Table runtime_information {
session_id: uuid [primary key]
workflow: varchar
agent: varchar
execution_time: integer
cpu_resource: float
gpu_resource: float
created_at: timestamp
updated_at: timestamp
}

Summary by CodeRabbit

  • New Features
    • Added SQLAlchemy-backed persistence for runtime execution data (workflow name, timing, CPU/GPU resources) from queued “future” records.
    • Runtime polling now publishes per-agent resource details alongside controller health updates.
    • Futures now store created_at; local execution/frontend record additional runtime metadata including request_id and agent details.
  • Bug Fixes
    • Improved updating of persisted runtime records when execution completes (including finished_at/updated timestamps).
  • Tests
    • Added tests covering SQLAlchemy pull/upsert behavior using a temporary SQLite database.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Runtime execution metadata is written to Redis, extracted into normalized rows, and upserted into a SQLAlchemy runtime_information table. Controller polling triggers persistence with agent resource data, while workflow metadata is stored with requests and covered by integration tests.

Changes

Runtime persistence flow

Layer / File(s) Summary
Runtime metadata capture
ventis/deploy.py, ventis/future.py, ventis/controller/local_controller_frontend.py, ventis/controller/local_controller.py
Workflow, future creation, agent, request context, and completion metadata are recorded in Redis.
SQLAlchemy runtime storage
requirements.txt, ventis/controller/utils/sqlalchemy.py, tests/test_runtime_sqlalchemy.py
SQLAlchemy persistence scans Redis futures, reads workflow metadata, and upserts runtime fields and resources into runtime_information; SQLite-backed tests validate initial insertion and updates.
Controller polling integration
ventis/controller/global_controller.py
Each controller replica’s polling cycle pulls future data and sends it to SQLAlchemy with per-agent resource specifications.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WorkflowHandler
  participant Future
  participant LocalController
  participant GlobalController
  participant Redis
  participant SQLAlchemy
  WorkflowHandler->>Redis: store request workflow
  Future->>Redis: store future created_at
  LocalController->>Redis: store execution metadata
  GlobalController->>Redis: read future hashes
  GlobalController->>SQLAlchemy: upsert runtime rows with resources
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too vague to convey the pull request's main change or scope. Use a concise, specific title that mentions the SQLAlchemy ORM/runtime persistence change, such as adding ORM-backed runtime persistence.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch orm

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

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ventis/controller/local_controller.py (1)

415-417: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear the thread-local request ID after each pooled task. ThreadPoolExecutor reuses worker threads, and ventis_context only exposes set_request_id/get_request_id, so a request ID set here can leak into the next task on the same thread and misattribute spawned futures. Wrap this in try/finally and clear or restore the prior value on exit.

🤖 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 `@ventis/controller/local_controller.py` around lines 415 - 417, Update the
worker-task flow around ventis_context.set_request_id to restore the thread’s
prior request ID or clear it in a finally block after each pooled task
completes. Preserve request_id propagation during task execution while ensuring
reused ThreadPoolExecutor workers cannot retain the previous task’s context.
🤖 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 `@tests/test_runtime_sqlalchemy.py`:
- Around line 47-54: Update the test fixture’s setUp and tearDown methods to
preserve the pre-test VENTIS_DATABASE_URL value before overriding it, then
restore that value afterward or remove the variable if it was originally unset.
Keep the existing database engine reset and temporary-file cleanup behavior
unchanged.

In `@ventis/controller/global_controller.py`:
- Around line 405-409: Update the request completion and cleanup flow around
pull_data and the cleanup thread so completed future:<id> hashes are not deleted
until persistence is acknowledged for each request. Either record persistence at
completion time or add per-request acknowledgment gating, ensuring pull_data can
always observe and persist runtime metadata before cleanup removes it.
- Around line 405-409: Update _poll_controllers around the send_data call to
catch database or persistence exceptions from pull_data/send_data, report the
failure through the existing logging mechanism, and continue into the controller
health-check flow so run() remains alive.

In `@ventis/controller/local_controller_frontend.py`:
- Around line 45-47: Validate that future_id is present and valid before the
metadata writes in the request-processing flow, rejecting the payload before
either self.redis.hset call. Update the logic around future_id and
_process_request() so malformed requests cannot create a future:None entry,
while preserving normal processing for valid IDs.

In `@ventis/controller/local_controller.py`:
- Around line 464-470: Update the duration calculation in the future completion
flow to convert the values returned by Redis hget for finished_at and created_at
into numeric timestamps before subtracting them, then persist the resulting
execution_time_(s) without changing the existing finished_at write.

In `@ventis/controller/utils/sqlalchemy.py`:
- Around line 67-68: Update send_data() around the workflow lookup so it reads
request:<session_id>:workflow from the canonical Redis client used by deploy(),
rather than the polled node’s Redis client. Alternatively, propagate the
workflow metadata into each node’s request context before send_data() runs,
ensuring distributed executions persist the actual workflow value instead of
NULL.
- Around line 32-38: Update _get_engine to ensure the runtime_information schema
is created or migrated immediately after initializing the engine, before it can
be used for polling or inserts. Reuse the project’s existing migration/schema
initialization mechanism if available, and preserve the current engine caching
and database URL behavior.

---

Outside diff comments:
In `@ventis/controller/local_controller.py`:
- Around line 415-417: Update the worker-task flow around
ventis_context.set_request_id to restore the thread’s prior request ID or clear
it in a finally block after each pooled task completes. Preserve request_id
propagation during task execution while ensuring reused ThreadPoolExecutor
workers cannot retain the previous task’s context.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c4f8e2ff-43ec-4bba-9665-5aa174fab72e

📥 Commits

Reviewing files that changed from the base of the PR and between df0fc70 and 4c3224f.

📒 Files selected for processing (8)
  • requirements.txt
  • tests/test_runtime_ec2.py
  • tests/test_runtime_sqlalchemy.py
  • ventis/controller/global_controller.py
  • ventis/controller/local_controller.py
  • ventis/controller/local_controller_frontend.py
  • ventis/controller/utils/sqlalchemy.py
  • ventis/deploy.py
💤 Files with no reviewable changes (1)
  • tests/test_runtime_ec2.py

Comment on lines +47 to +54
os.environ["VENTIS_DATABASE_URL"] = f"sqlite:///{self.db.name}"
sqlmod._engine = None
with create_engine(os.environ["VENTIS_DATABASE_URL"]).begin() as conn:
conn.execute(text(_CREATE))

def tearDown(self):
sqlmod._engine = None
os.unlink(self.db.name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore VENTIS_DATABASE_URL after the test.

The suite leaves the environment pointing to a deleted SQLite file, which can break tests that run afterward. Preserve the previous value in setUp() and restore or remove it in tearDown().

🤖 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 `@tests/test_runtime_sqlalchemy.py` around lines 47 - 54, Update the test
fixture’s setUp and tearDown methods to preserve the pre-test
VENTIS_DATABASE_URL value before overriding it, then restore that value
afterward or remove the variable if it was originally unset. Keep the existing
database engine reset and temporary-file cleanup behavior unchanged.

Comment on lines +405 to +409
send_data(
pull_data(node_redis),
{c["name"]: c.get("resources", {}) for c in self.controllers},
node_redis,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not clean up futures before persistence is acknowledged.

The cleanup thread deletes future:<id> hashes for completed requests, while this code only samples them periodically. If cleanup runs after completion but before the next poll, pull_data() never sees the row and its runtime metadata is lost. Gate cleanup on a per-request persistence acknowledgment, or persist at completion time.

🤖 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 `@ventis/controller/global_controller.py` around lines 405 - 409, Update the
request completion and cleanup flow around pull_data and the cleanup thread so
completed future:<id> hashes are not deleted until persistence is acknowledged
for each request. Either record persistence at completion time or add
per-request acknowledgment gating, ensuring pull_data can always observe and
persist runtime metadata before cleanup removes it.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep controller health polling alive when persistence fails.

send_data() runs before the health check and its database exceptions are uncaught; _poll_controllers() then escapes run(), stopping the controller daemon. Isolate this call with error handling and continue health polling while reporting the persistence failure.

🤖 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 `@ventis/controller/global_controller.py` around lines 405 - 409, Update
_poll_controllers around the send_data call to catch database or persistence
exceptions from pull_data/send_data, report the failure through the existing
logging mechanism, and continue into the controller health-check flow so run()
remains alive.

Comment on lines +45 to +47
future_id = data.get("future_id")
self.redis.hset(f"future:{future_id}", "created_at", time.time())
self.redis.hset(f"future:{future_id}", "agent", data.get("service"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate future_id before writing metadata.

A malformed payload creates future:None; pull_data() treats that as a normal future and can upsert a bogus runtime row. _process_request() rejects missing IDs only after this mutation. Reject the request before either HSET.

🤖 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 `@ventis/controller/local_controller_frontend.py` around lines 45 - 47,
Validate that future_id is present and valid before the metadata writes in the
request-processing flow, rejecting the payload before either self.redis.hset
call. Update the logic around future_id and _process_request() so malformed
requests cannot create a future:None entry, while preserving normal processing
for valid IDs.

Comment thread ventis/controller/local_controller.py Outdated
Comment on lines +464 to +470
self.redis.hset(f"future:{future_id}", "finished_at", time.time())
self.redis.hset(
f"future:{future_id}",
"execution_time_(s)",
self.redis.hget(f"future:{future_id}", "finished_at")
- self.redis.hget(f"future:{future_id}", "created_at"),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Convert Redis timestamps before calculating duration.

hget() returns decoded strings, so Line 468 subtracts two strings and raises TypeError. The result may already be written, but finished_at and execution_time_(s) are never persisted.

Proposed fix
-        self.redis.hset(f"future:{future_id}", "finished_at", time.time())
+        finished_at = time.time()
+        self.redis.hset(f"future:{future_id}", "finished_at", finished_at)
         self.redis.hset(
             f"future:{future_id}",
             "execution_time_(s)",
-            self.redis.hget(f"future:{future_id}", "finished_at")
-            - self.redis.hget(f"future:{future_id}", "created_at"),
+            finished_at - float(self.redis.hget(f"future:{future_id}", "created_at")),
         )
📝 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
self.redis.hset(f"future:{future_id}", "finished_at", time.time())
self.redis.hset(
f"future:{future_id}",
"execution_time_(s)",
self.redis.hget(f"future:{future_id}", "finished_at")
- self.redis.hget(f"future:{future_id}", "created_at"),
)
finished_at = time.time()
self.redis.hset(f"future:{future_id}", "finished_at", finished_at)
self.redis.hset(
f"future:{future_id}",
"execution_time_(s)",
finished_at - float(self.redis.hget(f"future:{future_id}", "created_at")),
)
🤖 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 `@ventis/controller/local_controller.py` around lines 464 - 470, Update the
duration calculation in the future completion flow to convert the values
returned by Redis hget for finished_at and created_at into numeric timestamps
before subtracting them, then persist the resulting execution_time_(s) without
changing the existing finished_at write.

Comment thread ventis/controller/utils/sqlalchemy.py Outdated
Comment on lines +32 to +38
def _get_engine():
global _engine
if _engine is None:
_engine = create_engine(
os.environ.get("VENTIS_DATABASE_URL", "sqlite:///ventis_runtime.db")
)
return _engine

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Create or migrate runtime_information before polling.

This only opens the database; production never creates the table. A fresh default sqlite:///ventis_runtime.db therefore fails on the first insert with “no such table: runtime_information.” The test currently masks this by creating the table itself. Add a migration or explicit bootstrap schema initialization.

🤖 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 `@ventis/controller/utils/sqlalchemy.py` around lines 32 - 38, Update
_get_engine to ensure the runtime_information schema is created or migrated
immediately after initializing the engine, before it can be used for polling or
inserts. Reuse the project’s existing migration/schema initialization mechanism
if available, and preserve the current engine caching and database URL behavior.

Comment thread ventis/controller/utils/sqlalchemy.py Outdated
Comment on lines +67 to +68
session_id = raw.get("request_id")
workflow = redis_client.get(f"request:{session_id}:workflow")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Read workflow metadata from its canonical Redis location.

send_data() receives the polled node’s Redis client, but deploy() writes request:<id>:workflow to the deploy process’s Redis. The local-controller flow copies request context to node Redis, not workflow metadata, so distributed executions persist NULL workflow values. Propagate workflow into each node’s request metadata or query the canonical Redis client here.

🤖 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 `@ventis/controller/utils/sqlalchemy.py` around lines 67 - 68, Update
send_data() around the workflow lookup so it reads request:<session_id>:workflow
from the canonical Redis client used by deploy(), rather than the polled node’s
Redis client. Alternatively, propagate the workflow metadata into each node’s
request context before send_data() runs, ensuring distributed executions persist
the actual workflow value instead of NULL.

Comment thread ventis/controller/local_controller_frontend.py Outdated
Comment thread ventis/controller/utils/sqlalchemy.py Outdated
return rows


def send_data(rows, resources_by_agent=None, redis_client=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We eventually need actual CPU/GPU utilization from here

Comment thread ventis/controller/utils/sqlalchemy.py Outdated
@Saaketh0

Copy link
Copy Markdown
Collaborator Author

In commit 42cb13e, I changed the database URL to be manually configurable from global_controller.yaml.

I also simplified the execution_time calculation, calculating it right before it gets pushed to the database instead of calculating it in the local controller and storing it in the local controller store.

fixed the lint/type checking errors
@Saaketh0

Copy link
Copy Markdown
Collaborator Author

In commit 4ebe96f, I fixed the linting/type errors the CI/test was displaying. No major/critical changes.

Other than linting and formatting:

  • Added sqlalchemy as a dependency
  • Checked that redis_client is not None before calling .get().
  • Typed the dynamically assigned _controller as Any so its runtime attributes are accepted by the type checker.

@iidsample iidsample left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved changes to push data to sql

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