OpenClaw Releases iOS and Android Companion Node Apps That Connect a Phone to a Self-Hosted AI Agent Gateway
OpenClaw's iOS and Android apps are companion nodes, not standalone chatbots. Each phone pairs to a self-hosted Gateway over WebSocket. This adds device hardware — camera, location, voice, and Canvas — to a local-first AI agent. Here is the architecture, the capabilities, and the trade-offs for builders. The post OpenClaw Releases iOS and Android Companion Node Apps That Connect a Phone to a Self-Hosted AI Agent Gateway appeared first on MarkTechPost .
OpenClaw just released native companion apps for iOS and Android. The iOS app is listed as ‘OpenClaw – AI that does things.’ Both apps are free to download. They are not standalone chatbots. Each phone becomes a node in a self-hosted agent network. The assistant itself runs on a separate Gateway. That separation is the whole design.
• OpenClaw’s iOS and Android apps are companion nodes, not standalone assistants.
• The Gateway runs the agent; phones add camera, location, voice, and Canvas.
• Nodes pair over WebSocket on port 18789 and require explicit approval.
• Privacy-heavy commands stay off until you allowlist them.
• A Gateway on macOS, Linux, or Windows (WSL2) is required.
What is OpenClaw?
OpenClaw is an open-source personal AI assistant/agent. It was created by Peter Steinberger with community contributors. The project is independent and not affiliated with Anthropic. Its core is written in TypeScript. The runtime is Node 24 (recommended) or Node 22.19+. The Gateway runs on macOS, Linux, or Windows via WSL2. You talk to it from chat apps you already use. Supported channels include WhatsApp, Telegram, Discord, Slack, Signal, and iMessage. The agent can browse the web, run shell commands, and read and write files. It works with hosted, subscription-backed, gateway, or local models. You bring an API key from your chosen provider. It keeps persistent memory and supports community skills and plugins.
How the Gateway-and-Nodes Architecture Works?
The Gateway is the single control plane. It owns sessions, routing, channels, tools, and events. You run one Gateway process on your own machine or server. Chat messages always land on the Gateway, never on a phone. A node is a companion device that connects to that Gateway. Nodes connect over a WebSocket on default port 18789. Each node registers with role: "node" during pairing. Nodes expose a command surface through node.invoke. Those command families include canvas.*, camera.*, device.*, notifications.*, and system.*. The documentation is explicit:’Nodes are peripherals, not gateways.’ On a local network, apps discover the Gateway via mDNS/Bonjour. For remote access, OpenClaw recommends Tailscale with a wss:// endpoint.
What does Mobile Apps Add?
The phone gives the agent a body. It grants device-specific hardware to your workflows. The iOS app pairs by QR code or setup code. It supports chat, realtime and background Talk mode, and approvals. You can share text, links, and media from iOS into OpenClaw. Optional capabilities include camera, screen, location, photos, contacts, calendar, and reminders. The Android app is a companion node, not a standalone gateway. It offers streaming chat replies, image attachments, and full session history. Talk Mode uses ElevenLabs or system TTS. A live Canvas surface lets the agent render dashboards and tools. Android grants permissions one by one. A foreground service keeps the Gateway connection alive.
iOS Node vs Android Node
CapabilityiOS — OpenClaw – AI that does thingsAndroid — OpenClaw’ nodeRoleCompanion nodeCompanion nodePairingQR code or setup codeSetup code or manual host/portChatChat from iPhoneStreaming replies, image attachments, full session historyVoiceRealtime and background Talk modeTalk Mode (ElevenLabs or system TTS)CanvasCanvas surfaceLive Canvas surfaceDevice capabilitiesCamera, screen, location, photos, contacts, calendar, remindersCamera, photos, screen capture, location, notifications, contacts, calendar, SMS, motion sensorsAction approvalsReviewable from the iPhoneManaged on the GatewayData collectionNone declared (App Store)None declared (Google Play)RequirementiOS 18.0+ and a running GatewayA running Gateway on macOS, Linux, or Windows (WSL2)
Use Cases With Examples
Consider field data collection on a job site. The agent uses iOS camera capture to photograph conditions. Location tags each photo with GPS coordinates. Consider a context-aware reminder. The agent triggers a task when you reach a place. Consider an incoming notification on Android. The agent reads it and drafts a reply. Consider a live dashboard. The agent pushes a Canvas surface to your screen. Consider hands-free use. Talk Mode holds a continuous voice conversation. One caveat applies to camera and screen capture. They require the app in the foreground. Background calls return an error.
Pairing a Phone: A Minimal Walk-Through
First, run the Gateway on a supported host.
Copy CodeCopiedUse a different Browser
# On the Gateway host (macOS, Linux, or Windows via WSL2)
npm install -g openclaw@latest
openclaw onboard --install-daemon
Next, open the app and select a discovered Gateway. Or enter the host and port manually. The app connects with role: "node" and sends a device pairing request. Approve it from the Gateway CLI.
Copy CodeCopiedUse a different Browser
openclaw devices list
openclaw devices approve
openclaw nodes status # confirm the node is paired and connected
Privacy-heavy commands stay off by default. Examples include camera.snap, camera.clip, and screen.record. You opt in explicitly through gateway.nodes.allowCommands in your config.
Copy CodeCopiedUse a different Browser
// ~/.openclaw/openclaw.json
allowCommands: ["camera.snap", "screen.record"],
A deny list (gateway.nodes.denyCommands) always wins over the allowlist.
Security and Approvals
Pairing credentials are stored on the device. Every node connection requires approval before it reaches the Gateway. The device pairing record is the durable role contract. Token rotation cannot upgrade a node into a different role. Camera and screen capture are permission-gated and run only in the foreground. Cleartext ws:// is limited to LAN and .local hosts. Public or Tailscale endpoints require a real wss:// TLS endpoint.
Interactive Explainer
OpenClaw Gateway & Nodes — Interactive Explainer
--bg:#fbf6f2; /* warm cream */
--card:#ffffff;
--ink:#26190f; /* warm near-black */
--muted:#7c6a5e; /* warm muted */
--line:#efe2d8; /* warm border */
--soft:#f8efe7; /* warm soft fill */
--accent:#ff5a36; /* OpenClaw coral / lobster */
--accent-deep:#df4326; /* darker coral */
--accent-soft:#ffe6dd; /* peach */
--accent-bd:#ffc9b8; /* peach border */
--teal:#0d9488; /* "completed" state — distinct from coral */
--amber:#b45309;
--amber-soft:#fdeccc;
--green:#76B900; /* Marktechpost brand */
--code-bg:#21130f;
--code-ink:#f1e4dc;
--code-coral:#ff8a6b;
--code-green:#7ee787;
--code-blue:#ffb59e;
--code-mut:#a18a7e;
--radius:14px;
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
--sans:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.5;-webkit-font-smoothing:antialiased}
.wrap{max-width:880px;margin:0 auto;padding:22px 18px 8px}
.eyebrow{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);font-weight:600}
h1{font-size:26px;line-height:1.18;margin:6px 0 6px;letter-spacing:-.01em}
.sub{color:var(--muted);font-size:14.5px;margin:0 0 6px;max-width:640px}
.note{font-family:var(--mono);font-size:11px;color:var(--muted);background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:6px 10px;display:inline-block;margin-top:8px}
.tabs{display:flex;gap:6px;margin:20px 0 14px;flex-wrap:wrap}
.tab{font-family:var(--mono);font-size:12.5px;font-weight:600;letter-spacing:.02em;border:1px solid var(--line);background:var(--card);color:var(--muted);
padding:9px 14px;border-radius:10px;cursor:pointer;transition:.15s;user-select:none}
.tab:hover{border-color:var(--accent);color:var(--accent)}
.tab.active{background:var(--accent);border-color:var(--accent);color:#fff}
.panel{display:none;animation:fade .25s ease}
.panel.active{display:block}
@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:18px}
.card+.card{margin-top:14px}
.card h3{margin:0 0 4px;font-size:15px}
.card p.lead{margin:0 0 14px;color:var(--muted);font-size:13.5px}
/* Architecture map */
.archwrap{position:relative}
.lane{display:grid;grid-template-columns:1fr;gap:14px}
.row{display:grid;gap:10px}
.row.chan{grid-template-columns:repeat(4,1fr)}
.gw-row{display:flex;justify-content:center}
.row.outs{grid-template-columns:repeat(3,1fr)}
.blk{border:1px solid var(--line);background:var(--card);border-radius:11px;padding:11px 10px;text-align:center;cursor:pointer;transition:.15s;position:relative}
.blk:hover{border-color:var(--accent);box-shadow:0 4px 14px rgba(255,90,54,.14);transform:translateY(-1px)}
.blk .t{font-size:12.5px;font-weight:600}
.blk .d{font-size:10.5px;color:var(--muted);margin-top:2px;font-family:var(--mono)}
.blk.gw{background:linear-gradient(180deg,#fff1ec,#ffe6dd);border-color:var(--accent-bd);min-width:230px;padding:14px}
.blk.gw .t{font-size:14px;color:var(--accent)}
.blk.active{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.conn{height:16px;display:flex;align-items:center;justify-content:center}
.conn svg{display:block}
.archlabel{font-family:var(--mono);font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;text-align:center;margin:2px 0 -2px}
.detail{margin-top:14px;border:1px dashed var(--line);border-radius:11px;padding:13px 14px;background:var(--soft);min-height:64px}
.detail .dt{font-family:var(--mono);font-size:11px;color:var(--accent);font-weight:600;letter-spacing:.04em}
.detail .dd{font-size:13px;color:var(--ink);margin-top:4px}
/* Simulator */
.controls{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px}
@media(max-width:560px){.controls{grid-template-columns:1fr}}
.ctl label{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);display:block;margin-bottom:6px}
.chips{display:flex;flex-wrap:wrap;gap:6px}
.chip{font-size:12px;font-family:var(--mono);border:1px solid var(--line);background:var(--card);color:var(--muted);padding:7px 11px;border-radius:8px;cursor:pointer;transition:.12s}
.chip:hover{border-color:var(--accent);color:var(--accent)}
.chip.active{background:var(--accent-soft);border-color:var(--accent);color:var(--accent);font-weight:600}
.runbtn{font-family:var(--mono);font-weight:700;font-size:13px;letter-spacing:.03em;background:var(--accent);color:#fff;border:none;
padding:11px 18px;border-radius:10px;cursor:pointer;transition:.15s;display:inline-flex;align-items:center;gap:8px}
.runbtn:hover{filter:brightness(1.06)}
.runbtn:disabled{opacity:.55;cursor:default}
.pipe{display:flex;align-items:stretch;gap:0;margin:16px 0 8px;overflow-x:auto;padding-bottom:4px}
.stage{min-width:104px;flex:1;border:1px solid var(--line);border-radius:10px;padding:9px 8px;text-align:center;background:var(--card);transition:.2s;opacity:.45}
.stage .sn{font-size:11px;font-weight:600}
.stage .sc{font-family:var(--mono);font-size:9.5px;color:var(--muted);margin-top:3px;word-break:break-word}
.stage.lit{opacity:1;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.stage.done{opacity:1;border-color:var(--teal);box-shadow:none}
.arrow{display:flex;align-items:center;color:var(--line);font-size:18px;padding:0 2px;min-width:14px}
.arrow.lit{color:var(--accent)}
.console{background:var(--code-bg);border-radius:11px;padding:14px 15px;font-family:var(--mono);font-size:12px;color:var(--code-ink);
min-height:120px;white-space:pre-wrap;overflow-x:auto;border:1px solid #3a241c}
.console .c-mut{color:var(--code-mut)}
.console .c-grn{color:var(--code-green)}
.console .c-blu{color:var(--code-blue)}
.console .c-cor{color:var(--code-coral)}
.console .c-amb{color:#e3b341}
.cursor{display:inline-block;width:7px;height:14px;background:var(--code-coral);vertical-align:-2px;animation:blink 1s steps(2) infinite}
@keyframes blink{0%,50%{opacity:1}50.01%,100%{opacity:0}}
/* Pairing */
.steps{display:flex;flex-direction:column;gap:0}
.step{display:flex;gap:12px;padding:10px 0;border-bottom:1px solid var(--line);opacity:.4;transition:.25s}
.step:last-child{border-bottom:none}
.step.lit{opacity:1}
.stepnum{flex:0 0 26px;height:26px;border-radius:50%;background:var(--soft);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;
font-family:var(--mono);font-size:12px;font-weight:700;color:var(--muted);transition:.25s}
.step.lit .stepnum{background:var(--accent);border-color:var(--accent);color:#fff}
.step.done .stepnum{background:var(--teal);border-color:var(--teal);color:#fff}
.stepbody .st{font-size:13px;font-weight:600}
.stepbody .sd{font-size:12px;color:var(--muted);margin-top:2px}
.stepbody code{font-family:var(--mono);font-size:11.5px;background:var(--soft);border:1px solid var(--line);padding:1px 5px;border-radius:5px;color:var(--accent)}
.legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:12px;font-size:11px;color:var(--muted);font-family:var(--mono)}
.legend span{display:inline-flex;align-items:center;gap:5px}
.dot{width:9px;height:9px;border-radius:3px;display:inline-block}
footer{max-width:880px;margin:18px auto 0;padding:14px 18px 22px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px}
footer .brand{font-family:var(--mono);font-size:12px;font-weight:700;color:var(--green);letter-spacing:.02em}
footer .fnote{font-size:11px;color:var(--muted);font-family:var(--mono)}
Interactive Explainer
OpenClaw: how a phone becomes a node in your AI agent network
Explore the Gateway-and-Nodes architecture, simulate a chat command flowing to a phone’s hardware, and walk through device pairing — all in your browser.
Illustrative simulation · runs locally · no real Gateway is connected
① Architecture map
② Command-flow simulator
③ Pairing walkthrough
The Gateway is the single source of truth
Click any block to see its role. Messages always land on the Gateway — never directly on a phone.
Chat apps + plugins
⬡ Gateway
sessions · routing · channels · events
Surfaces the Gateway drives
OpenClaw agent
tools · memory
CLI / Web UI
control plane
iOS / Android nodes
role: “node”
⬡ Gateway
The single control plane. Runs on macOS, Linux, or Windows (WSL2). It owns sessions, routing, channels, tools, and events. One Gateway process serves every channel and every node at once.
Simulate a command reaching your phone’s hardware
Pick a channel and a task, then run it. Watch the request flow through the Gateway to a node’s node.invoke command surface.
1 · Channel you message from
2 · Task for the agent
Photograph the shelf in front of me
Where am I right now?
Show a live dashboard on my phone
Read my latest notification and draft a reply
Run command
route + session
plan + tool
node.invoke
camera.snap
back to chat
# Select options above and press Run.
requires foreground / opt-in
Pair a phone as a node
Nodes connect over WebSocket on port 18789 and require explicit approval. Press play to step through the handshake.
Run pairing flow
Start the Gateway on a host
Run openclaw onboard --install-daemon on macOS, Linux, or Windows (WSL2).
App discovers the Gateway
On the same Wi-Fi, the app finds it via mDNS/Bonjour. Remote uses a wss:// Tailscale endpoint.
Phone sends a pairing request
The node connects to the WebSocket with role: "node" and a device identity.
Operator approves on the Gateway
openclaw devices list → openclaw devices approve
Node is paired and connected
openclaw nodes status shows paired · connected. Privacy-heavy commands stay off until allowlisted.
Marktechpost
Interactive explainer · OpenClaw Gateway & Nodes
(function(){
/* ---------- Tabs ---------- */
var tabs=document.querySelectorAll('.tab'), panels=document.querySelectorAll('.panel');
tabs.forEach(function(t){t.addEventListener('click',function(){
tabs.forEach(function(x){x.classList.remove('active')});
panels.forEach(function(p){p.classList.remove('active')});
t.classList.add('active');
document.getElementById(t.dataset.tab).classList.add('active');
/* ---------- Architecture detail ---------- */
wa:["WhatsApp · channel","An inbound channel. The user's message is delivered to the Gateway, where the session and routing live."],
tg:["Telegram · channel","An inbound channel into the same single Gateway process. Channels do not run any agent logic themselves."],
dc:["Discord · channel","A chat channel. OpenClaw also supports Slack, Signal, iMessage and many more through one Gateway."],
im:["iMessage · channel","A chat surface. All channels feed one control plane; replies are returned to the originating chat."],
gw:["⬡ Gateway","The single control plane. Runs on macOS, Linux, or Windows (WSL2). It owns sessions, routing, channels, tools, and events. One Gateway serves every channel and node."],
agent:["OpenClaw agent","The reasoning runtime with tools and persistent memory. It can browse the web, run shell commands, and read or write files."],
cli:["CLI / Web Control UI","Operator surfaces for setup, pairing approvals, sessions, and config. The CLI is where you approve nodes."],
node:["iOS / Android nodes","Companion devices that connect over WebSocket with role: \"node\". They expose canvas.*, camera.*, device.*, notifications.*, and system.* via node.invoke. Nodes are peripherals, not gateways."]
var blks=document.querySelectorAll('#arch .blk'), det=document.getElementById('archDetail');
blks.forEach(function(b){b.addEventListener('click',function(){
blks.forEach(function(x){x.classList.remove('active')});
b.classList.add('active');
var d=arch[b.dataset.k];
det.innerHTML='
/* ---------- Simulator ---------- */
var chan="WhatsApp", task="cam";
var taskMap={
cam:{cmd:"camera.snap",fam:"camera.*",label:"Photograph the shelf",fg:true,
result:'image captured · 1 attachment returned to chat'},
loc:{cmd:"location.get",fam:"location.*",label:"Get current location",fg:false,
result:'coordinates resolved · sent to agent'},
canvas:{cmd:"canvas.navigate",fam:"canvas.*",label:"Show a live dashboard",fg:true,
result:'Canvas surface loaded on phone screen'},
notif:{cmd:"notifications.list",fam:"notifications.*",label:"Read latest notification",fg:false,
result:'latest notification read · draft reply composed'}
function bindChips(id,cb){
var box=document.getElementById(id);
box.querySelectorAll('.chip').forEach(function(c){c.addEventListener('click',function(){
box.querySelectorAll('.chip').forEach(function(x){x.classList.remove('active')});
c.classList.add('active'); cb(c.dataset.v);
bindChips('chanChips',function(v){chan=v;document.getElementById('s0c').textContent=v;});
bindChips('taskChips',function(v){task=v;document.getElementById('s3c').textContent=taskMap[v].cmd;});
var stages=document.querySelectorAll('#pipe .stage'), arrows=document.querySelectorAll('#pipe .arrow');
var con=document.getElementById('console'), runBtn=document.getElementById('runBtn');
function reset(){stages.forEach(function(s){s.classList.remove('lit','done')});arrows.forEach(function(a){a.classList.remove('lit')});}
function w(html){con.innerHTML+=html;}
function run(){
var t=taskMap[task]; reset(); con.innerHTML=''; runBtn.disabled=true;
var lines=[
{d:300,s:0,t:'› user@'+chan.toLowerCase()+': "'+t.label.toLowerCase()+'"\n'},
{d:520,s:1,t:'[gateway] message received · route to agent session\n'},
{d:560,s:2,t:'[agent] plan → invoke node capability\n'},
{d:620,s:3,t:'[node.invoke] '+t.fam+' → '+t.cmd+'\n'+
(t.fg?' ⚠ requires app in foreground (background calls error)\n':'')},
{d:560,s:4,t:'[reply] '+t.result+'\n✓ delivered back to '+chan+''}
(function tick(){
if(i>=lines.length){runBtn.disabled=false;return;}
var ln=lines[i];
setTimeout(function(){
if(ln.s>0){arrows[ln.s-1].classList.add('lit');stages[ln.s-1].classList.add('done');}
stages[ln.s].classList.add('lit');
w(ln.t); con.scrollTop=con.scrollHeight; resize();
if(ln.s===4){stages[4].classList.add('done');}
i++; tick();
runBtn.addEventListener('click',run);
/* ---------- Pairing ---------- */
var steps=document.querySelectorAll('#steps .step'), pairBtn=document.getElementById('pairBtn');
function runPair(){
pairBtn.disabled=true;
steps.forEach(function(s){s.classList.remove('lit','done')});
steps[0].classList.add('lit');
(function tick(){
if(i>=steps.length){pairBtn.disabled=false;return;}
setTimeout(function(){
if(i>0){steps[i-1].classList.remove('lit');steps[i-1].classList.add('done');}
steps[i].classList.add('lit'); resize();
i++; tick();
pairBtn.addEventListener('click',runPair);
/* ---------- Self-resize for iframe embeds ---------- */
function resize(){
var h=document.body.scrollHeight+40;
if(window.parent){window.parent.postMessage({type:'openclaw-demo-height',height:h},'*');}
}catch(e){}
window.addEventListener('load',resize);
window.addEventListener('resize',resize);
setTimeout(resize,400);
Strengths and Limitations
• Local-first design keeps keys, config, and data on your own machine.
• One Gateway serves many channels and many nodes at once.
• Phones add device hardware: camera, location, voice, and Canvas.
• Both store listings report no data collection.
Limitations:
• The mobile apps need a running Gateway to do anything.
• Setup involves WebSocket pairing, mDNS, and sometimes Tailscale.
• Camera and screen capture require the app in the foreground.
• The Android listing shows 10+ downloads, an early-stage signal.
• Full system access is broad and demands careful allowlisting.
Check out the Android and the iOS app. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us
The post OpenClaw Releases iOS and Android Companion Node Apps That Connect a Phone to a Self-Hosted AI Agent Gateway appeared first on MarkTechPost.
Leia também