Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4d28f95
includes all the files when ventis build
Saaketh0 Aug 26, 2026
23e3928
fixed some bugs
Saaketh0 Aug 26, 2026
95240ca
ventis build: sweep project .py files into Docker build contexts
Saaketh0 Aug 26, 2026
69d5405
Fix missing os import in metrics_agent.py
Saaketh0 Aug 26, 2026
653bee8
WIP: OTel exporter testing + portfolio merge-conflict fix (pre-pull c…
Saaketh0 Aug 26, 2026
d861795
Merge origin/main into feature/otel-exporter
Saaketh0 Aug 26, 2026
4af43ae
[Feature] Pass env / secrets into agent containers
nickhuo Aug 26, 2026
087ee15
Harden the remote env file copy against a hostile /tmp
nickhuo Aug 26, 2026
db0ba26
WIP: OTel multi-destination fan-out (Railway+Langfuse+Grafana) + clea…
Saaketh0 Aug 27, 2026
1d319a5
Merge PR #51 (feature/all-the-files) into feature/otel-exporter
Saaketh0 Aug 27, 2026
098548c
Dedupe 'import os' from PR #51 merge (both sides added it independently)
Saaketh0 Aug 27, 2026
77231ec
Merge PR #53 (env_file secret injection) into feature/otel-exporter
Saaketh0 Aug 27, 2026
0b9546c
Fix PR #51 regression: disable entrypoint-based stub relocation
Saaketh0 Aug 27, 2026
7b3b167
Parallelize per-instance polling in _poll_controllers
Saaketh0 Aug 27, 2026
b5d6e4d
rough draft
Saaketh0 Aug 28, 2026
9bbc35d
rough draft
Saaketh0 Aug 28, 2026
8baed6c
Parallelize per-instance polling in _poll_controllers
Saaketh0 Aug 31, 2026
cd3a521
cleaned up OTel Exporter
Saaketh0 Aug 31, 2026
50733b8
Merge branch 'main' into feature/otel-exporter
Saaketh0 Aug 31, 2026
03d0a56
Merge feature/otel-exporter into improvement/global-polling
Saaketh0 Aug 31, 2026
1e6a6d8
Align with feature/otel-exporter: use updated langfuse config example…
Saaketh0 Aug 31, 2026
ede3fac
Restore parallelized polling implementation
Saaketh0 Aug 31, 2026
ea91ff9
added concurrent polling
Saaketh0 Aug 31, 2026
b4e45e8
Redis-backed otel destination reload: config change no longer needs f…
Saaketh0 Sep 3, 2026
9f630fe
Simplify: assume otel_exporter's Redis is always localhost:6379
Saaketh0 Sep 3, 2026
c085dbc
Trim explanatory comments off simple/obvious functions
Saaketh0 Sep 3, 2026
53a96c7
Ventis fixes extracted from the CLI packaging work
Saaketh0 Sep 4, 2026
0508422
Point example workflow imports at the stub's entrypoint path
Saaketh0 Sep 4, 2026
3e6a177
Redis-backed otel destination reload: config change no longer needs f…
Saaketh0 Sep 3, 2026
4cb9a7e
Simplify: assume otel_exporter's Redis is always localhost:6379
Saaketh0 Sep 3, 2026
32f615f
Trim explanatory comments off simple/obvious functions
Saaketh0 Sep 3, 2026
e2f049e
Merge origin/feature/config-reloading into fixes/ventis-cli-fixes
Saaketh0 Sep 9, 2026
011761c
Merge origin/main into fixes/ventis-cli-fixes
Saaketh0 Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/finance/workflow/example_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "grpc_stubs"))

from deploy import deploy
from finance_agent import FinanceAgent
from market_agent import MarketResearchAgent
from agents.finance_agent import FinanceAgent
from agents.market_agent import MarketResearchAgent


def main(ticker: str = "AAPL"):
Expand Down
2 changes: 1 addition & 1 deletion examples/helloworld/workflow/example_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "grpc_stubs"))

from deploy import deploy
from example_agent import ExampleAgent
from agents.example_agent import ExampleAgent


def main(name: str = "World"):
Expand Down
8 changes: 4 additions & 4 deletions examples/portfolio/workflow/portfolio_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "grpc_stubs"))

from deploy import deploy
from intent_agent import IntentAgent
from metrics_agent import MetricsAgent
from risk_agent import RiskAgent
from advisor_agent import AdvisorAgent
from agents.intent_agent import IntentAgent
from agents.metrics_agent import MetricsAgent
from agents.risk_agent import RiskAgent
from agents.advisor_agent import AdvisorAgent


def main(
Expand Down
10 changes: 5 additions & 5 deletions examples/text2sql/workflow/text2sql_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "grpc_stubs"))

from deploy import deploy
from schema_agent import SchemaRetrievalAgent
from sql_generator_agent import SQLGeneratorAgent
from sql_validator_agent import SQLValidatorAgent
from sandbox_agent import SandboxExecutorAgent
from production_agent import ProductionExecutorAgent
from agents.schema_agent import SchemaRetrievalAgent
from agents.sql_generator_agent import SQLGeneratorAgent
from agents.sql_validator_agent import SQLValidatorAgent
from agents.sandbox_agent import SandboxExecutorAgent
from agents.production_agent import ProductionExecutorAgent


def main(question: str = "total order amount per customer region", n_candidates: int = 3):
Expand Down
5 changes: 1 addition & 4 deletions tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ cd $PROJECT_NAME
grep -v 'gpu:' .car/config/global_controller.yaml > .car/config/global_controller.yaml.tmp
mv .car/config/global_controller.yaml.tmp .car/config/global_controller.yaml

echo ">> 2. Building agents (ventis build)..."
ventis build

echo ">> 3. Deploying workflow (ventis deploy)..."
echo ">> 2. Building and deploying workflow (ventis deploy)..."
ventis deploy &
DEPLOY_PID=$!

Expand Down
12 changes: 8 additions & 4 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ def _fake_controller_module(self, controller):

@patch("atexit.register")
@patch("signal.signal")
@patch("ventis.cli._run_build")
@patch("ventis.cli._ensure_grpc_stubs_importable")
@patch("ventis.cli._preflight_ec2_deploy")
def test_deploy_skips_ec2_preflight_for_local_config(
self,
preflight,
ensure_grpc,
_run_build,
_signal_patch,
_atexit_patch,
):
Expand All @@ -54,12 +56,14 @@ def test_deploy_skips_ec2_preflight_for_local_config(

@patch("atexit.register")
@patch("signal.signal")
@patch("ventis.cli._run_build")
@patch("ventis.cli._ensure_grpc_stubs_importable")
@patch("ventis.cli._preflight_ec2_deploy")
def test_deploy_runs_ec2_preflight_for_ec2_config(
self,
preflight,
ensure_grpc,
_run_build,
_signal_patch,
_atexit_patch,
):
Expand All @@ -83,10 +87,11 @@ def test_deploy_runs_ec2_preflight_for_ec2_config(

@patch("atexit.register")
@patch("signal.signal")
@patch("ventis.cli._run_build")
@patch("ventis.cli._ensure_grpc_stubs_importable")
@patch("ventis.cli._preflight_ec2_deploy")
def test_deploy_uses_car_when_present(
self, preflight, ensure_grpc, _signal_patch, _atexit_patch
self, preflight, ensure_grpc, _run_build, _signal_patch, _atexit_patch
):
controller = MagicMock()
controller_module = self._fake_controller_module(controller)
Expand Down Expand Up @@ -132,15 +137,14 @@ class CliBuildTests(unittest.TestCase):
def _run_build(
self, project_dir, agent_yaml_paths, buildx_available, platform="linux/amd64"
):
"""Run cmd_build against project_dir with docker/subprocess calls mocked.
"""Run _run_build against project_dir with docker/subprocess calls mocked.

Returns (docker_calls, generate_docker_mock, generate_workflow_docker_mock).
"""
artifact_root = (
project_dir / ".car" if (project_dir / ".car").is_dir() else project_dir
)
config_path = artifact_root / "config" / "global_controller.yaml"
args = SimpleNamespace(config=str(config_path))
docker_calls = []

def fake_run(cmd, check):
Expand Down Expand Up @@ -181,7 +185,7 @@ def fake_generate_stub(yaml_path, _output_path):
cwd = os.getcwd()
os.chdir(project_dir)
try:
cli.cmd_build(args)
cli._run_build(str(config_path))
finally:
os.chdir(cwd)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

import ventis.deploy as deploy_module
import ventis.controller.deploy as deploy_module


class _FakeRedis:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_error_propagation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from ventis.controller.local_controller import LocalController
from ventis.controller.local_controller_frontend import LocalControllerServicer
from ventis.future import Future
from ventis.controller.future import Future
import local_controler_pb2


Expand Down
4 changes: 2 additions & 2 deletions tests/test_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
),
)

import ventis.future as future_module
import ventis.ventis_context as ventis_context
import ventis.controller.future as future_module
import ventis.controller.ventis_context as ventis_context


class _FakeRedis:
Expand Down
8 changes: 5 additions & 3 deletions tests/test_global_controller_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,16 @@ def test_a_second_call_with_a_new_config_overwrites_the_published_value(self):
},
)

def test_missing_project_id_or_database_publishes_safe_defaults(self):
controller = _bare_controller({})
def test_missing_database_publishes_safe_default(self):
# project_id is always populated by _load_config() by the time _write_identity()
# runs -- only database_url has a real "unset" case to default here.
controller = _bare_controller({"project_id": "11111111-1111-1111-1111-111111111111"})

controller._write_identity()

self.assertEqual(
controller.redis.hgetall(GlobalController.IDENTITY_KEY),
{"project_id": "0", "database_url": ""},
{"project_id": "11111111-1111-1111-1111-111111111111", "database_url": ""},
)


Expand Down
70 changes: 70 additions & 0 deletions tests/test_global_controller_project_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""_load_config() must mint a project_id when a config file omits one, and persist it back
to the file so the same value survives a reload_config() or process restart -- not a fresh
uuid on every load.
"""

import os
import re
import sys
import tempfile
import unittest

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

import yaml

from ventis.controller.global_controller import GlobalController

UUID_HEX_RE = re.compile(r"^[0-9a-f]{32}$")


def _write_config(body):
f = tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False)
f.write(body)
f.close()
return f.name


class LoadConfigProjectIdTests(unittest.TestCase):
def test_generates_and_persists_project_id_when_missing(self):
config_path = _write_config("agents: []\npoll_interval: 5\n")
try:
config = GlobalController._load_config(config_path)

self.assertTrue(UUID_HEX_RE.match(config["project_id"]))

with open(config_path) as f:
on_disk = yaml.safe_load(f)
self.assertEqual(on_disk["project_id"], config["project_id"])
finally:
os.unlink(config_path)

def test_reload_reuses_the_persisted_project_id_instead_of_minting_a_new_one(self):
config_path = _write_config("agents: []\npoll_interval: 5\n")
try:
first = GlobalController._load_config(config_path)
second = GlobalController._load_config(config_path)

self.assertEqual(first["project_id"], second["project_id"])
finally:
os.unlink(config_path)

def test_existing_project_id_is_left_untouched(self):
config_path = _write_config(
'agents: []\nproject_id: "11111111-1111-1111-1111-111111111111"\n'
)
try:
config = GlobalController._load_config(config_path)

self.assertEqual(config["project_id"], "11111111-1111-1111-1111-111111111111")

with open(config_path) as f:
contents = f.read()
# No second project_id line got appended alongside the existing one.
self.assertEqual(contents.count("project_id"), 1)
finally:
os.unlink(config_path)


if __name__ == "__main__":
unittest.main()
4 changes: 2 additions & 2 deletions tests/test_otel_exporter_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_init_db_creates_waiting_table_with_full_schema(self):
def test_fields_are_normalized_and_added_to_span(self):
db.init_db(self.db_path)
raw = {
"future_id": "00112233445566778899aabbccddeeff",
"future_id": "0011223344556677", # 64-bit (16 hex chars), matches Future.id's format
"request_id": "ffeeddccbbaa99887766554433221100",
"service": "PriceAgent",
"method": "get_history",
Expand Down Expand Up @@ -61,7 +61,7 @@ def test_fields_are_normalized_and_added_to_span(self):
def test_error_message_is_wired_from_redis_error_field(self):
db.init_db(self.db_path)
raw = {
"future_id": "11112222333344445555666677778888",
"future_id": "1111222233334444", # 64-bit (16 hex chars), matches Future.id's format
"request_id": "88887777666655554444333322221111",
"service": "AdvisorAgent",
"method": "summarize",
Expand Down
53 changes: 53 additions & 0 deletions tests/test_stub_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ventis.stub_generator import (
BASE_AGENT_REQUIREMENTS,
BASE_WORKFLOW_REQUIREMENTS,
_stub_destination,
generate_docker,
generate_workflow_docker,
)
Expand Down Expand Up @@ -86,5 +87,57 @@ def test_per_workflow_requirements_are_appended_to_base(self):
self.assertEqual(requirements, BASE_WORKFLOW_REQUIREMENTS + ["yfinance"])


class StubDestinationTests(unittest.TestCase):
"""A stub replaces the real module at its entrypoint path, so it is written
to exactly that one location. Flat is only a fallback for a stub with no
entrypoint mapping, or one whose mapping escapes the build context.
"""

def test_unmapped_stub_falls_back_to_flat(self):
self.assertEqual(_stub_destination("/stubs/split_agent.py", {}), "split_agent.py")

def test_entrypoint_mapping_is_the_only_destination(self):
destination = _stub_destination(
"/stubs/split_agent.py", {"split_agent.py": "agents/split_agent.py"}
)
self.assertEqual(destination, "agents/split_agent.py")

def test_flat_entrypoint_stays_flat(self):
destination = _stub_destination(
"/stubs/split_agent.py", {"split_agent.py": "split_agent.py"}
)
self.assertEqual(destination, "split_agent.py")

def test_unsafe_entrypoint_falls_back_to_flat(self):
destination = _stub_destination(
"/stubs/split_agent.py", {"split_agent.py": "../../etc/passwd"}
)
self.assertEqual(destination, "split_agent.py")


class GenerateWorkflowDockerStubPlacementTests(unittest.TestCase):
def test_stub_lands_only_at_its_entrypoint_path(self):
with tempfile.TemporaryDirectory() as tmpdir:
workflow_file = Path(tmpdir) / "workflow.py"
workflow_file.write_text("from agents.split_agent import SplitAgent\n")

stub_file = Path(tmpdir) / "stubs" / "split_agent.py"
stub_file.parent.mkdir()
stub_file.write_text("class SplitAgent:\n pass\n")

output_dir = os.path.join(tmpdir, "out")
generate_workflow_docker(
str(workflow_file),
[str(stub_file)],
output_dir=output_dir,
stub_entrypoints={"split_agent.py": "agents/split_agent.py"},
)

nested_path = Path(output_dir) / "agents" / "split_agent.py"
flat_path = Path(output_dir) / "split_agent.py"
self.assertIn("class SplitAgent", nested_path.read_text())
self.assertFalse(flat_path.exists(), "stub must not be duplicated flat")


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion tests/test_ventis_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

import ventis.ventis_context as ventis_context
import ventis.controller.ventis_context as ventis_context


class VentisContextTests(unittest.TestCase):
Expand Down
20 changes: 20 additions & 0 deletions ventis/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.11-slim

RUN apt-get update && apt-get install -y docker.io && rm -rf /var/lib/apt/lists/*

COPY . /ventis
RUN pip install /ventis

# global_controller.py bare-imports these; pip install only ships the .proto source.
RUN python -m grpc_tools.protoc \
-I/ventis/ventis/controller/proto \
--python_out=/usr/local/lib/python3.11/site-packages \
--grpc_python_out=/usr/local/lib/python3.11/site-packages \
/ventis/ventis/controller/proto/local_controler.proto

EXPOSE 8000

ENTRYPOINT ["python", "-m", "ventis.server"]


# to run: docker build -f ventis/Dockerfile -t saakeths/canyonos:latest .
9 changes: 5 additions & 4 deletions ventis/OTLP_Exporter/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ def waiting_row_to_span(row):
row = dict(row)

trace_id = int(row["session_id"], 16)
span_id = int.from_bytes(bytes.fromhex(row["future_id"])[:8], "big")
# future_id/parent_id are already 64-bit (Future.id is generated at that
# width directly -- see ventis/controller/future.py), matching OTel's
# span_id, so no truncation is needed here.
span_id = int(row["future_id"], 16)
parent_id = row.get("parent_id")
parent_span_id = (
int.from_bytes(bytes.fromhex(parent_id)[:8], "big") if parent_id else None
)
parent_span_id = int(parent_id, 16) if parent_id else None

context = SpanContext(
trace_id=trace_id, span_id=span_id, is_remote=False, trace_flags=_SAMPLED
Expand Down
Loading
Loading