Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
78fb267
Route local-provider agents over a dedicated docker network
Saaketh0 Sep 4, 2026
1aa3353
Merge origin/fixes/ventis-cli-fixes into cli/local-provider-routing
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
6 changes: 3 additions & 3 deletions examples/helloworld/config/global_controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ agents:
cpu: 1
memory: 512
entrypoint: agents/example_agent.py
provider: EC2
provider: local

- name: VllmAgent
replicas: 1
Expand All @@ -19,7 +19,7 @@ agents:
cpu: 2
memory: 2048
entrypoint: agents/vllm_agent.py
provider: EC2
provider: local
instance_type: t3.micro

- name: Workflow
Expand All @@ -28,7 +28,7 @@ agents:
redis_port: 6379
api_port: 8080 # Only needed for workflows, defaults to 8080 if not filled
workflow_file: workflow/example_workflow.py
provider: EC2
provider: local
instance_type: t3.micro

poll_interval: 5
Expand Down
22 changes: 12 additions & 10 deletions tests/test_instance_manager_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def test_local_instances_keep_default_host_and_increment_host_ports(self):
"host_port": "8000",
"container_port": "50051",
"endpoint": "localhost:8000",
"redis_host": "host.docker.internal",
"redis_host": "ventis-redis-localhost",
"redis_port": "6379",
"runtime_id": "ventis-local-alpha-0",
},
Expand All @@ -154,17 +154,18 @@ def test_local_instances_keep_default_host_and_increment_host_ports(self):
"run",
"-d",
"-it",
"--add-host=host.docker.internal:host-gateway",
"--network",
"ventis-local",
"--name",
"ventis-local-alpha-0",
"-p",
"8000:50051",
"-e",
"VENTIS_AGENT_PORT=8000",
"VENTIS_AGENT_PORT=50051",
"-e",
"VENTIS_AGENT_HOST=host.docker.internal",
"VENTIS_AGENT_HOST=ventis-local-alpha-0",
"-e",
"VENTIS_REDIS_HOST=host.docker.internal",
"VENTIS_REDIS_HOST=ventis-redis-localhost",
"-e",
"VENTIS_REDIS_PORT=6379",
"-e",
Expand Down Expand Up @@ -209,17 +210,18 @@ def test_local_workflow_and_resource_flags_stay_the_same(self):
"run",
"-d",
"-it",
"--add-host=host.docker.internal:host-gateway",
"--network",
"ventis-local",
"--name",
"ventis-local-workflow-0",
"-p",
"8000:50051",
"-e",
"VENTIS_AGENT_PORT=8000",
"VENTIS_AGENT_PORT=50051",
"-e",
"VENTIS_AGENT_HOST=host.docker.internal",
"VENTIS_AGENT_HOST=ventis-local-workflow-0",
"-e",
"VENTIS_REDIS_HOST=host.docker.internal",
"VENTIS_REDIS_HOST=ventis-redis-localhost",
"-e",
"VENTIS_REDIS_PORT=6379",
"-e",
Expand Down Expand Up @@ -255,7 +257,7 @@ def test_agent_id_is_published_under_the_controller_endpoint_key(self):
alpha = manager.ensure_instances([{"name": "Alpha", "provider": "local"}])[0]

self.assertEqual(
controller.redis.get("controller:host.docker.internal:8000:agent_id"),
controller.redis.get("controller:ventis-local-alpha-0:50051:agent_id"),
alpha["agent_id"],
)

Expand Down
20 changes: 8 additions & 12 deletions ventis/controller/cloud_provider_logic/Local/_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
CONTAINER_PORT = 50051
PROVIDER = "local"
MAX_PORT_ATTEMPTS = 50
NETWORK = "ventis-local"
_controller = None


Expand All @@ -29,10 +30,6 @@ def _is_local_host(host):
return host in {"localhost", "127.0.0.1"}


def _container_routing_host(host):
return "host.docker.internal" if _is_local_host(host) else host


def validate_config():
return None

Expand All @@ -46,7 +43,7 @@ def provision_instance(spec, replica_index, next_host_port):
"provider": PROVIDER,
"host": host,
"host_port": host_port,
"redis_host": _container_routing_host(host),
"redis_host": f"ventis-redis-{host.replace('.', '-')}",
"runtime_id": f"ventis-{PROVIDER}-{agent_name.lower()}-{replica_index}",
"user": spec.get("user"),
}
Expand Down Expand Up @@ -80,15 +77,16 @@ def bootstrap_instance(provisioned, spec, replica_index, agent_id):
"run",
"-d",
"-it",
"--add-host=host.docker.internal:host-gateway",
"--network",
NETWORK,
"--name",
runtime_id,
"-p",
f"{host_port}:{CONTAINER_PORT}",
"-e",
f"VENTIS_AGENT_PORT={host_port}",
f"VENTIS_AGENT_PORT={CONTAINER_PORT}",
"-e",
f"VENTIS_AGENT_HOST={redis_host}",
f"VENTIS_AGENT_HOST={runtime_id}",
"-e",
f"VENTIS_REDIS_HOST={redis_host}",
"-e",
Expand Down Expand Up @@ -138,7 +136,7 @@ def bootstrap_instance(provisioned, spec, replica_index, agent_id):
f"{MAX_PORT_ATTEMPTS} attempts"
)

endpoint = f"{_container_routing_host(host)}:{host_port}"
endpoint = f"{runtime_id}:{CONTAINER_PORT}"
_require_controller().redis.set(f"controller:{endpoint}:agent_id", agent_id)

instance = {
Expand Down Expand Up @@ -174,6 +172,4 @@ def terminate_instance(instance):


def routing_endpoint_for(instance):
host = instance.get("host")
port = instance["host_port"]
return f"{_container_routing_host(host)}:{port}"
return f"{instance['runtime_id']}:{CONTAINER_PORT}"
44 changes: 21 additions & 23 deletions ventis/controller/global_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,13 @@
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

LOCAL_NETWORK = "ventis-local"


def _is_local_host(host):
return host in {"localhost", "127.0.0.1"}


def _container_routing_host(host):
return "host.docker.internal" if _is_local_host(host) else host


class GlobalController(object):
"""
Daemon that manages a routing table across multiple local controller instances.
Expand Down Expand Up @@ -406,12 +404,16 @@ def _launch_redis_containers(self):
logger.info("Reusing existing Redis container %s on %s", container_name, host)
self.redis_containers[host] = container_name
else:
if _is_local_host(host):
self._run_cmd(["docker", "network", "create", LOCAL_NETWORK], host, user)
network_args = ["--network", LOCAL_NETWORK] if _is_local_host(host) else []
cmd = [
"docker",
"run",
"-d",
"--name",
container_name,
*network_args,
"-p",
f"{redis_port}:6379",
"redis:alpine",
Expand Down Expand Up @@ -484,10 +486,6 @@ def _get_node_redis_for(self, host):
"""Get the Redis client for a given host, falling back to self.redis."""
return self.node_redis.get(host, self.redis)

def _agent_host_key(self, host):
"""Return the host string as seen by Docker containers (for status key matching)."""
return _container_routing_host(host)

def _wait_for_healthy(self, timeout=30, interval=2):
"""
Block until all controllers report healthy in Redis, or until timeout.
Expand All @@ -497,10 +495,7 @@ def _wait_for_healthy(self, timeout=30, interval=2):
interval: Seconds between checks.
"""
deadline = time.time() + timeout
pending = [
(instance["agent_name"], instance["host"], instance["host_port"])
for instance in self.instance_manager.list_instances()
]
pending = self.instance_manager.list_instances()

logger.info(
"Waiting for %d replica(s) to become healthy (timeout=%ds)...",
Expand All @@ -510,26 +505,29 @@ def _wait_for_healthy(self, timeout=30, interval=2):

while pending and time.time() < deadline:
still_pending = []
for name, host, port in pending:
for instance in pending:
name = instance["agent_name"]
host = instance["host"]
port = instance["host_port"]
node_redis = self._get_node_redis_for(host)
agent_host = self._agent_host_key(host)
status = node_redis.get(f"controller:{agent_host}:{port}:status")
endpoint = self.instance_manager._routing_endpoint_for(instance)
status = node_redis.get(f"controller:{endpoint}:status")
if status == "healthy":
logger.info("Controller %s (%s:%s) is ready.", name, host, port)
self._last_status[(host, port)] = "healthy"
else:
still_pending.append((name, host, port))
still_pending.append(instance)
pending = still_pending
if pending:
time.sleep(interval)

if pending:
for name, host, port in pending:
for instance in pending:
logger.warning(
"Controller %s (%s:%s) not ready after %ds.",
name,
host,
port,
instance["agent_name"],
instance["host"],
instance["host_port"],
timeout,
)

Expand Down Expand Up @@ -601,9 +599,9 @@ def _poll_one_instance(self, instance):
port,
e,
)
agent_host = self._agent_host_key(host)
status_key = f"controller:{agent_host}:{port}:status"
metrics_key = f"controller:{agent_host}:{port}:metrics"
endpoint = self.instance_manager._routing_endpoint_for(instance)
status_key = f"controller:{endpoint}:status"
metrics_key = f"controller:{endpoint}:metrics"

# Getting metrics from local controllers
# See LocalController._execute_locally
Expand Down
Loading