External Nodes
Connect remote machines to your Animus server for distributed tool execution.
Status: Shipped. External nodes are operational — connect remote machines, execute tools, and manage access from the admin UI.
What External Nodes Are
An external node is a lightweight daemon that connects to your central Animus server and executes tool calls on the machine it runs on. The node does not load LLM providers, the chain runner, scheduler, channels, or any agent logic — it is a tool execution endpoint only.
This lets your agents run commands, access files, and interact with hardware on remote machines without installing the full Animus daemon on each one. The central server decides when and how to use each node; the node just receives calls and returns results.
Use cases:
- Execute shell commands on a developer's workstation from a server-hosted agent
- Read and write files on a home server or NAS
- Interact with hardware (cameras, sensors, IoT devices) on edge machines
- Run code in a specific environment — Docker, GPU, particular OS — from a central agent
- Distribute work across multiple machines without exposing them publicly
Architecture
The node system uses a hub-and-spoke model. The central Animus server acts as the hub; one or more node daemons connect to it as spokes.
┌─────────────────────┐ WebSocket ┌──────────────────┐
│ Central Server │◄──────────────────►│ Node Daemon │
│ │ (auth token) │ (--node mode) │
│ ┌───────────────┐ │ │ │
│ │ AgentKernel │ │ │ ┌─────────────┐ │
│ │ + ChainRunner │ │ Tool calls │ │ ToolServer │ │
│ │ + NodeManager │──┼──────────────────►│ │ (exec only) │ │
│ │ │◄─┼───────────────────│ │ │ │
│ └───────────────┘ │ Tool results │ └─────────────┘ │
└─────────────────────┘ └──────────────────┘The node daemon connects outbound via WebSocket. No inbound ports need to be opened on the node machine — NAT-friendly. The connection is authenticated with a server-generated token.
Setting Up a Node
Step 1 — Generate a Token
On the central Animus server, generate an auth token (and signing key) via the admin API or the admin UI's Nodes page:
curl -X POST http://localhost:8080/api/v1/nodes/tokens \
-H "Content-Type: application/json" \
-d '{"description": "Workstation node"}'The response returns the token and signing key. Store both securely — they are shown only once.
{
"token": "nt_abc123...",
"signing_key": "sk_def456...",
"description": "Workstation node",
"note": "Store the token and signing key securely. They will not be shown again."
}You can also generate tokens from the admin UI: navigate to Nodes in the sidebar, click Generate Token, and copy the credentials immediately.
Step 2 — Run the Node Daemon
On the remote machine, run the Animus binary in node mode. You need the same animusd binary — node mode is a command-line flag, not a separate build.
animusd --node \
--server-url ws://animus-host:8080/ws/node \
--token nt_abc123... \
--node-name workstation \
--node-tools exec,file- --node
- Enables node mode. Skips LLM providers, scheduler, channels, consolidation — loads only the tool handlers specified in --node-tools.
- --server-url <url>
- WebSocket URL of the central Animus server. Use ws:// for local or wss:// for TLS.
- --token <token>
- Auth token generated on the server. The server validates this against its token registry.
- --node-name <name>
- Name to register as. This is how the agent references the node (e.g. "workstation", "raspberry-pi"). Must be unique among connected nodes.
- --node-tools <list>
- Comma-separated list of tools to enable. Currently supported: exec, file. If omitted, the node connects but cannot execute any tool calls.
Step 3 — Run via Docker
For convenience, a Docker image is available that runs the node daemon with environment variable configuration:
docker run -d --name animus-node \
-e SERVER_URL=ws://animus-host:8080/ws/node \
-e NODE_TOKEN=nt_abc123... \
-e NODE_NAME=workstation \
-e NODE_TOOLS=exec,file \
mrsommer/animus-node:latestThe node client is stateless — no volumes needed. It makes outbound connections only; no ports are exposed. Use --network host if the Animus server is on the same machine:
docker run -d --network host \
-e SERVER_URL=ws://localhost:8080/ws/node \
-e NODE_TOKEN=nt_abc123... \
-e NODE_NAME=docker-node \
mrsommer/animus-node:latestStep 4 — Grant Agent Access
Agents don't see connected nodes by default. Configure which nodes each agent can access via the admin UI (agent edit form) or the API:
curl -X PUT http://localhost:8080/api/v1/agents/my-agent/allowed_nodes \
-H "Content-Type: application/json" \
-d '{"allowed_nodes": ["workstation", "home-server"]}'Default deny: If an agent has no allowed_nodes configured, it sees no nodes. The node tool's list action only returns nodes the agent is permitted to use.
Using Nodes from Agents
Node Discovery Tool
Agents discover available nodes via the node tool. This tool is for discovery and inspection only — not for executing tool calls.
{"action": "list"}Returns nodes visible to the calling agent:
{
"total": 2,
"nodes": [
{
"name": "workstation",
"status": "connected",
"hostname": "melvin-E33011",
"os": "Linux Mint 22",
"uptime_seconds": 3600,
"tools": ["exec", "file"]
},
{
"name": "home-server",
"status": "connected",
"hostname": "nas-01",
"os": "Ubuntu 24.04",
"uptime_seconds": 86400,
"tools": ["exec", "file"]
}
]
}{"action": "info", "name": "workstation"}Remote Tool Execution
To execute a tool call on a remote node, the agent adds a __node parameter to any tool call. The ChainRunner intercepts this before local tool lookup and forwards the call to the named node:
{"action": "shell_exec", "command": "uname -a", "__node": "workstation"}{"action": "file_read", "source_path": "/etc/hostname", "__node": "home-server"}Every tool supports remote execution automatically. No schema changes or wrappers needed. The __node parameter is stripped before the call reaches the node's local handler — the node receives the same tool call it would if running locally. This avoids nested JSON escaping problems (the same issue as SSH command quoting).
Security
- Server-generated tokens. Tokens are created on the central server and stored as hashes. Revoking a token immediately disconnects any nodes using it.
- HMAC command signing. Every tool call sent to a node is signed with HMAC-SHA256 using a per-token signing key. The node verifies the signature, timestamp, and nonce before executing. This prevents command injection, tampering, and replay attacks even if the WebSocket connection is compromised.
- Per-agent access control. Agents only see nodes in their
allowed_nodeslist. Default deny. A__nodeparameter referencing a disallowed node returns an error. - Per-node tool allowlist. The node daemon only loads tools specified in
--node-tools. A node configured with onlyfilecannot executeexeccommands even if the agent tries. - No inbound network required. Nodes connect outbound via WebSocket. NAT-friendly — no open ports on the node machine.
- Node runs with daemon process permissions. The node operator controls what the agent can do on that machine. Tool-level allowlists add a second layer of control.
Admin API Reference
- POST /api/v1/nodes/tokens
- Generate an auth token and signing key. Body: {"description": "..."}. Returns token + signing_key once.
- GET /api/v1/nodes/tokens
- List all tokens (hashes only, not plaintext). Shows id, description, created_at, revoked status.
- DELETE /api/v1/nodes/tokens/{id}
- Revoke a token by ID. Any nodes using it are immediately disconnected.
- GET /api/v1/nodes
- List all connected nodes with status, hostname, OS, uptime, and available tools.
- GET /api/v1/nodes/{name}
- Get details for a specific connected node.
- PUT /api/v1/agents/{id}/allowed_nodes
- Configure which nodes an agent can access. Body: {"allowed_nodes": ["name1", "name2"]}.
Admin UI
The Nodes page in the admin UI provides:
- Token management — generate, list, and revoke auth tokens
- Connected nodes overview — status, hostname, OS, uptime, available tools
- Per-agent node access configuration in the agent edit form
- Node details view with last-seen timestamp and tool list
Connection Protocol
Nodes connect via WebSocket with bearer token authentication. The protocol is straightforward:
- Registration
- On connect, the node sends a register message with its name, hostname, OS info, and available tools. The server validates the token and adds the node to its NodeManager.
- Tool calls
- The server sends tool_call messages with a call_id, tool name, and arguments. The node executes locally and responds with a tool_result containing the call_id, success flag, and output.
- Heartbeats
- The node sends periodic heartbeats. The server tracks last-seen timestamps and marks nodes as disconnected if heartbeats stop.
- Auto-reconnect
- If the connection drops, the node daemon reconnects automatically with exponential backoff (1s → 2s → 4s → ... → 60s cap). Backoff resets on successful reconnection.
Building the Node Docker Image
To build the node Docker image from source:
git clone https://github.com/railstracks/animus.git
cd animus
./scripts/build-animus-node-docker.shThe script builds the full binary, packages it with runtime dependencies, and produces mrsommer/animus-node:latest. Set PUSH=1 to push after building, and REGISTRY_USER to override the registry username.