Static Multi-Node (Without Kubernetes)
Run AgentENV across multiple physical or virtual machines without Kubernetes. This deployment uses the Go Gateway and Scheduler with a statically configured runtime-node list.
Static discovery is appropriate when node membership changes infrequently. The
Scheduler does not automatically register an unknown node from its heartbeat:
each runtime node must appear in scheduler.nodes, and changing that list
requires a Scheduler restart.
Architecture
This example co-locates the Gateway and Scheduler on 10.0.0.10 and runs two
AgentENV runtime nodes:
| Component | Address | Purpose |
|---|---|---|
| Gateway | 10.0.0.10:8080 | Client-facing HTTP and WebSocket entry point |
| Scheduler | 10.0.0.10:9090 | gRPC placement, heartbeat, and sandbox binding service |
| Runtime node A | 10.0.0.21:8000 | Runs Firecracker sandboxes as node-a |
| Runtime node B | 10.0.0.22:8000 | Runs Firecracker sandboxes as node-b |
Use private addresses or an otherwise trusted network. AgentENV does not currently provide an authentication boundary suitable for exposing these services directly to the public Internet.
Prerequisites
On every runtime node:
- Ubuntu 24.04 with Linux kernel 6.8 or later
/dev/kvmaccess- root access for the AgentENV installation
- network reachability to the Scheduler
On the control-plane host:
- Go 1.21 or later
- network reachability to every runtime node
- a checkout of the AgentENV repository
Allow the following TCP flows:
| Source | Destination | Port |
|---|---|---|
| Clients | Gateway | 8080 |
| Gateway | Scheduler | 9090 |
| Runtime nodes | Scheduler | 9090 |
| Gateway | Runtime nodes | 8000 |
The examples keep metrics listeners on loopback. Open their ports separately if an external metrics collector needs them.
1. Install the runtime nodes
Run the installation on each runtime node:
curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/install.sh \
| sudo bash
Edit /etc/default/aenv on each machine without removing the paths written by
the installer. Node A uses:
API_ADDR="0.0.0.0:8000"
AENV_NODE_ID="node-a"
AENV_OBSERVABILITY_SCHEDULER_REPORT_ENABLED="true"
AENV_OBSERVABILITY_SCHEDULER_ENDPOINT="http://10.0.0.10:9090"
Node B uses the same values except for its unique node ID:
API_ADDR="0.0.0.0:8000"
AENV_NODE_ID="node-b"
AENV_OBSERVABILITY_SCHEDULER_REPORT_ENABLED="true"
AENV_OBSERVABILITY_SCHEDULER_ENDPOINT="http://10.0.0.10:9090"
The AENV_NODE_ID values must exactly match the corresponding IDs in the
Scheduler configuration below. Restart and verify each runtime:
sudo systemctl restart aenv
sudo systemctl status aenv
curl http://127.0.0.1:8000/health
2. Build and install the control-plane binaries
On the control-plane host:
git clone https://github.com/kvcache-ai/AgentENV.git
cd AgentENV
make -C services build
sudo install -m 0755 services/bin/scheduler /usr/local/bin/agentenv-scheduler
sudo install -m 0755 services/bin/gateway /usr/local/bin/agentenv-gateway
sudo useradd --system --no-create-home --shell /usr/sbin/nologin agentenv-control
sudo install -d -o root -g agentenv-control -m 0750 /etc/agentenv
If the agentenv-control account already exists, the useradd command reports
that fact and can be skipped.
3. Configure static discovery
Create /etc/agentenv/control-plane.json:
{
"log_level": "info",
"log_format": "json",
"scheduler": {
"grpc_listen_addr": "0.0.0.0:9090",
"metrics_listen_addr": "127.0.0.1:9101",
"strategy": "round_robin",
"report_ttl": "30s",
"binding_ttl": "30s",
"discovery": {
"mode": "static"
},
"nodes": [
{
"id": "node-a",
"endpoint": "http://10.0.0.21:8000"
},
{
"id": "node-b",
"endpoint": "http://10.0.0.22:8000"
}
]
},
"gateway": {
"http_listen_addr": "0.0.0.0:8080",
"metrics_listen_addr": "127.0.0.1:9102",
"scheduler_addr": "10.0.0.10:9090",
"request_timeout": "90s",
"forward_response_size": 4194304,
"sandbox_proxy_domains": []
}
}
Protect the configuration after editing it:
sudo chown root:agentenv-control /etc/agentenv/control-plane.json
sudo chmod 0640 /etc/agentenv/control-plane.json
Each node endpoint must be reachable from the Gateway. The Scheduler returns that endpoint to the Gateway when it selects a node.
4. Run the Scheduler and Gateway with systemd
Create /etc/systemd/system/agentenv-scheduler.service:
[Unit]
Description=AgentENV Scheduler
Wants=network-online.target
After=network-online.target
[Service]
User=agentenv-control
Group=agentenv-control
ExecStart=/usr/local/bin/agentenv-scheduler -config /etc/agentenv/control-plane.json
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Create /etc/systemd/system/agentenv-gateway.service:
[Unit]
Description=AgentENV Gateway
Wants=network-online.target
After=network-online.target agentenv-scheduler.service
[Service]
User=agentenv-control
Group=agentenv-control
ExecStart=/usr/local/bin/agentenv-gateway -config /etc/agentenv/control-plane.json
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Start both services:
sudo systemctl daemon-reload
sudo systemctl enable --now agentenv-scheduler agentenv-gateway
5. Verify the cluster
Check every network hop before creating a sandbox:
# On the control-plane host
curl http://10.0.0.21:8000/health
curl http://10.0.0.22:8000/health
curl http://127.0.0.1:8080/health
# Wait for node heartbeats, then inspect the cluster through the Gateway
curl http://127.0.0.1:8080/nodes
The node list should contain node-a and node-b. Point clients at the
Gateway, not directly at a runtime node:
aenv auth
# AENV server URL: http://10.0.0.10:8080
# API key: dummy
Sandbox create, list, lifecycle, and data-plane requests can then be routed through the Gateway.
Operations
Follow service logs:
sudo journalctl -u agentenv-scheduler -f
sudo journalctl -u agentenv-gateway -f
# On a runtime node
sudo journalctl -u aenv -f
To add, remove, rename, or change the endpoint of a static node:
- Update
scheduler.nodesin/etc/agentenv/control-plane.json. - Ensure the runtime’s
AENV_NODE_IDmatches its configured ID. - Restart the Scheduler.
sudo systemctl restart agentenv-scheduler
Restarting the Scheduler temporarily interrupts routing that depends on its in-memory state. Runtime heartbeats repopulate observed sandbox assignments after the Scheduler comes back.
Troubleshooting
A runtime is healthy but absent from /nodes
- Confirm that
AENV_OBSERVABILITY_SCHEDULER_REPORT_ENABLEDistrue. - Include the
http://scheme inAENV_OBSERVABILITY_SCHEDULER_ENDPOINT. - Verify that
AENV_NODE_IDexactly matches a configuredscheduler.nodes[].id. - Check that the runtime can reach Scheduler port
9090. - Inspect
journalctl -u aenvfor heartbeat rejection or connection errors.
Heartbeats from IDs not present in the static node list are rejected; they do not register new nodes.
The Gateway returns no available nodes
- Verify both runtime health endpoints from the control-plane host.
- Verify the static endpoint addresses are reachable from the Gateway.
- Check Scheduler logs for expired heartbeat reports.
- Check that host firewalls allow runtime-to-Scheduler and Gateway-to-runtime traffic.
The Gateway cannot connect to the Scheduler
The Gateway’s scheduler_addr is a gRPC target and does not use an http://
prefix. Runtime heartbeat configuration uses a URL and does require that
prefix:
gateway.scheduler_addr = 10.0.0.10:9090
AENV_OBSERVABILITY_SCHEDULER_ENDPOINT = http://10.0.0.10:9090