
Safe Remote Automation With AxiOwl
Safe remote automation is not just "can this machine make another machine do work?" It is also "who selected that remote machine, how was it verified, what path carried the command, what did the remote side prove, and what does the local receipt actually mean?" AxiOwl's current C++ implementation treats those questions as product boundaries rather than afterthoughts.
That matters because AxiOwl sits between AI provider sessions. A local MCP host or CLI command can ask AxiOwl to send a message, but AxiOwl still has to resolve sender identity, resolve the target, choose one provider edge, record evidence, and avoid turning an unsupported path into a silent fallback. Remote automation adds another risk: a second machine can become part of the route. The codebase handles that by making remote behavior explicit, SSH-based, registry-backed, and intentionally limited.
What AxiOwl Means By Remote
The repository is split into product targets. The root README lists apps/windows-desktop as the Windows AxiOwl executable and installer line, apps/linux-desktop as Linux desktop provider-target work, and apps/linux-remote as the reserved package location for a remote Linux node package. The apps/linux-remote README is deliberately narrow: durable Linux remote source is expected to move there over time, while the Windows installer payload files that deploy to remote Linux nodes currently remain under apps/windows-desktop/installer.
The Windows desktop README gives the clearest runtime split:
Windows build: local coordinator, registry owner, outbound SSH remote provider.
Linux build: remote Codex endpoint, relay-session receiver, Codex CLI delivery only.
The Linux-side command surface is also narrow:
axiowl relay-session --stdio
The same README states that the Linux remote app supports Codex CLI delivery only. It does not support VS Code, Antigravity, desktop IPC, GUI, tray behavior, or remote relay chaining. That limitation is part of the safety model. A remote Linux node is not treated as a magic extension of the local desktop. It is a Codex-focused endpoint with a small relay interface.
SSH And Stdio Instead Of A Public Control Plane
The remote provider plan and implementation use outbound SSH from the local coordinator to the remote node. The conceptual shape is:
ssh -i "<key path>" <ssh_user>@<host> "axiowl relay-session --stdio"
The local side owns the connection. The remote side runs axiowl relay-session --stdio. There is no AxiOwl HTTP listener to expose publicly for remote delivery.
In remote_relay.cpp, the relay protocol is newline-delimited JSON over standard input and output. It speaks a small set of frame types: hello, deliver, deliver_result, and error. A deliver frame must include a target and body. Missing targets and empty bodies return explicit error frames. A successful delivery path returns a deliver_result frame with fields such as request id, message id, state, target, provider, error, and detail.
That small frame protocol is important. The remote side is not accepting arbitrary shell text from AxiOwl and pretending it is a product API. The local side sends structured frames to a known command after SSH authentication. The remote command then routes through the normal AxiOwl message pipeline on the remote node.
Remote Nodes Are Registry Records
AxiOwl stores remote node information in a node registry, separate from agent records. app_paths.cpp places the node registry under:
registry/nodes.tsv
An agent registry row stores provider session information; a node registry row stores node identity. In models.hpp, a NodeRecord includes:
node_id
display_name
aliases
host
ssh_user
ssh_key_path
enabled
last_seen_at
last_verified_at
last_error
The CLI exposes both newer node commands and registry-level node commands:
axiowl node add --id <node-id> --host <host> --ssh-user <ssh-user> [--key <ssh-key-path>] [--name <display-name>] [--alias <alias>]
axiowl node verify --id <node-id>
axiowl registry add-node --id <node-id> --host <host> --user <ssh-user> [--key <ssh-key-path>] [--name <display-name>]
Verification is not vague. axiowl node verify looks up the node, opens SSH with BatchMode=yes and ConnectTimeout=10, and runs:
hostname; whoami; pwd; hostname -I
If SSH succeeds, AxiOwl writes last_verified_at and clears last_error. If it fails, the node stays in the registry with a loud failure reason. That gives the operator a clear difference between "I typed a host into a file" and "AxiOwl verified that this node is reachable through the configured SSH identity."
Remote targets also remain explicit. A remote agent row uses provider remote, a node id, and a provider session value that identifies the remote target. The README example is:
axiowl registry add-agent --name "Remote Test" --provider remote --node "front" --session "<remote agent name>"
axiowl send --to "Remote Test" --body "hello"
The implementation keeps remote node identity and remote target identity separate. That matters operationally because aliases are useful for humans, but routing should depend on stable node ids and provider session ids.
The Installer Does Not Select Remote By Default
The Windows installer treats each provider as a separable feature, even though the product ships as one MSI. Remote is part of that feature system, but it is remote-only and unchecked by default.
The installer provider contract defines a remote provider with:
AXIOWL_PROVIDER_REMOTE
FeatureRemoteEnroll
FeatureRemoteDeploy
FeatureRemoteDiscovery
RunInstallRemoteEnroll
RunInstallRemoteDeploy
RunInstallRemoteDiscovery
remote_only = true
The build and safety scripts check for the same behavior. check-installer-safety.ps1 verifies that the Remote checkbox exists, that AXIOWL_PROVIDER_REMOTE defaults to 0, and that Remote selects separate enroll, deploy, and discovery features only when chosen. It also checks that unchecked Remote does not schedule removal actions for those remote features.
The active remote-nodes.json payload in the installer directory is an empty array:
[]
So the package does not ship with hidden remote nodes. Remote enrollment has to come from explicit configuration or operator action, not from a preloaded inventory.
The Linux remote installer is also strict. install-axiowl-remote-linux.sh requires a payload directory, verifies expected payload files, requires system commands such as sudo, install, and codex, installs /usr/local/bin/axiowl, checks that axiowl --version reports axiowl 0.1.0, installs the Codex plugin payload under the remote user's AxiOwl Codex plugin root, registers the Codex plugin marketplace, and probes the MCP server to confirm that axiowl_send_message is exposed. If any required piece is missing, it fails.
That is the right shape for remote automation: selected, installed, checked, and logged.
Receipts Are Not Overclaimed
AxiOwl's runtime also avoids overclaiming. In message_pipeline.cpp, a send receipt starts as a pipeline receipt, not as proof that a provider received a message. The pipeline validates the target and body, resolves sender identity, performs one targeted discovery repair when appropriate, rejects non-sendable targets, and records delivery stages.
The CLI prints the boundary plainly. When AxiOwl accepts a request but provider delivery is not yet proven, the message says:
Receipt boundary: provider delivery, provider wake-up, and provider reply are not implied.
When a path explicitly reports provider status, the CLI says that provider delivery status is shown only because that path reported it. This distinction is especially important for remote automation. SSH transport success is not the same as provider delivery. The remote provider plan states the correct acceptance bar: local remote-provider acceptance means the remote AxiOwl node returned a successful deliver_result; if remote AxiOwl accepted the relay frame but its local provider failed, local AxiOwl must report provider failure, not transport success.
The logs support that model. app_paths.cpp separates:
logs/events.jsonl
logs/delivery.jsonl
runtime/
registry/
delivery_worker.cpp records request parsing, dispatch, provider result, delivery stages, target provider, target node, sender information, method used, evidence, and errors. evidence_log.cpp writes JSONL events with timestamps. This gives operators a way to inspect what AxiOwl accepted, what route it selected, and where a failure occurred.
Current Scope: Remote Is Guarded, Not Broadly Supported
The current source has an important nuance: remote relay pieces exist, but the current provider support matrix marks remote as unsupported for local-provider remediation builds. provider_edges.cpp currently returns excluded_remote_out_of_scope for provider remote, with the method remote_out_of_scope. The CLI discover remote path also returns an excluded report stating that remote discovery is out of scope for the local-provider remediation build.
That is not a contradiction to the safety story. It is the safety story. The codebase contains a remote relay receiver, a remote provider implementation file, node registry commands, installer remote feature contracts, a Linux remote installer, and MCP tooling for relay delivery. At the same time, the current support matrix refuses to count remote as supported until it satisfies the product support bar. That bar requires discovery, selected install pieces, named-session send, provider receipt, MCP reply, and correct provider-owned sender identity.
In other words, AxiOwl does not treat "we have SSH code" as "remote automation is production-supported everywhere." The code keeps unsupported remote paths loud and scoped.
Practical Operator Value
For an operator, safe remote automation with AxiOwl means a few concrete habits:
- Add remote nodes intentionally with
axiowl node addoraxiowl registry add-node. - Verify them with
axiowl node verifybefore expecting delivery. - Keep remote targets as explicit registry rows tied to a node id.
- Treat
accepted_by_axiowlas a handoff receipt, not a provider reply. - Inspect
logs/delivery.jsonlandlogs/events.jsonlwhen diagnosing remote behavior. - Do not use remote as a fallback for local provider failures.
- Keep Remote unchecked in the installer unless remote enrollment, deployment, and discovery are actually part of the selected install.
Those practices line up with the security docs. AxiOwl may read and write AxiOwl-owned registry, runtime, log, provider integration, and selected config files. It should not modify unrelated provider files, user auth tokens, unrelated settings, unrelated workspace files, unchecked provider surfaces, or remote configuration unless explicitly selected. Local provider support should remain local unless a remote feature is explicitly selected, and remote must not hide local delivery failures.
Closing
The safest remote automation is boring in the right places. AxiOwl's current implementation uses explicit node records, SSH batch mode, a small stdio relay protocol, selected-only installer features, separate logs, and clear receipt boundaries. It also marks the current remote support boundary instead of silently routing through a path that has not met the support bar.
That makes AxiOwl remote automation useful for operators precisely because it is constrained. The system can grow a richer remote node package over time, but the core rule should remain the same: no hidden nodes, no public relay service by default, no remote fallback for local failures, and no claim of delivery without evidence from the selected path.