Content

Rho said it was running. It wasn't.

We connected Rho to Swytchcode, and a fresh install told us it was running while its agent had already quit. Here's what broke, how we fixed it, and the small Pi extension that keeps Rho's shell away from Swytchcode's rules.

Rho is an always-on personal agent built on Pi, the minimal coding agent. It runs in the background, remembers things across sessions, and checks in with you every 30 minutes by default. You can talk to it from a terminal, a web UI, Telegram or email.

An agent that wakes up on its own schedule is exactly the kind that should have limits on what it can spend. So we connected Rho to Swytchcode, which runs the agent's API calls with your keys, holds every write for your approval, and keeps rules the agent can't change.

First, though, we had to get Rho to start.

What broke on a fresh install

We installed Rho 0.1.12 the way its README says. Then we ran rho start, and it printed that Rho was running. It wasn't. Pi, the agent underneath, had already quit.

Here's what we found, in the order we found it.

1. Pi moved, and Rho didn't follow. Pi recently moved to a new npm name. Rho still installs the old one. Pi's MCP adapter, which is how Rho talks to MCP servers like Swytchcode, now needs the new Pi. So do two of Rho's default modules: subagents and web access. They fail to load, and Pi stops when any extension fails.

2. The status check didn't notice. rho start reports success when it launches Pi, not when Pi is still up a few seconds later. So the first sign of trouble was an agent that never answered.

3. One more missing package. Rho's review module needs a package called glob, which isn't installed with it.

The fix, until Rho catches up with Pi:

pi install npm:[email protected]
cd "$(npm root -g)/@rhobot-dev/rho" && npm i glob

Then turn off the two modules that need the new Pi, in ~/.rho/init.toml:

subagents = false
web-access = false

Version 2.5.4 is the last MCP adapter that works with the old Pi. One catch: rho sync puts the adapter back to the latest version, so pin it again after a sync.

None of this is Rho's fault, really. A dependency renamed itself. But if your Rho install is quietly not answering, this is probably why. We're letting the maintainer know.

Once it ran

With Pi happy, Swytchcode worked well. Rho doesn't load each MCP tool on its own. It gets one mcp tool that forwards calls to any server you've set up, so Swytchcode's 11 tools sit behind it. The skill, which teaches the agent how to use Swytchcode, is listed by name and read when it's needed.

We ran our seven checks against a mocked Stripe, with a scripted model so the results are repeatable. All seven passed:

  • A balance lookup ran.
  • A write stopped before it reached Stripe, waiting for a person.
  • A pasted API key was refused, and the agent got a command for you to run instead.
  • Asked to add an allow rule, or to replace the approval rule, the agent was refused. The rules file was unchanged.

That's the part a prompt can't do: the rules live in Swytchcode, not in Rho's instructions.

Its shell is the real risk

Installed directly on your machine, Rho's agent has four tools that run without asking: bash, an interactive shell, write and edit. In our test, bash renamed the approval rule in a copy of the rules file. Swytchcode can refuse its own tools, but it can't stop a shell from editing a file.

The usual fix would be to start Pi without those tools. Rho doesn't let you, because it starts Pi with fixed options. So we wrote a small Pi extension instead. Pi loads anything in ~/.pi/agent/extensions/, and this one blocks the four tools by name and refuses reads inside the Swytchcode folders:

import { realpathSync } from "node:fs";
import { homedir } from "node:os";
import { resolve } from "node:path";

const off = new Set(["bash", "interactive_shell", "write", "edit"]);
const home = homedir();
const hidden = [`${home}/.swytchcode`, `${home}/agent-swy/.swytchcode`];

function real(p: string, cwd: string): string {
  const abs = resolve(cwd, p.replace(/^~(?=\/|$)/, home));
  try { return realpathSync(abs); } catch { return abs; }
}

export default function (pi: any) {
  pi.on("tool_call", async (event: any, ctx: any) => {
    if (off.has(event.toolName)) return { block: true, reason: `${event.toolName} is turned off on this agent` };
    if (event.toolName === "read") {
      const p = real(String(event.input?.path ?? ""), ctx.cwd ?? process.cwd());
      if (hidden.some((h) => p === h || p.startsWith(h + "/"))) return { block: true, reason: "that folder is off limits" };
    }
  });
}

It resolves real paths, so ~, .. and symlinks don't get around it. It also held for worker agents that Rho starts with their own tool lists. With it in place, all seven checks still passed.

Or skip all of that and run Rho in a container, with Swytchcode on the host over HTTP. The container has no rules file and no keys inside, so there's nothing for its shell to find. That setup passed every check too.

Set it up

You need a Swytchcode account. Approvals need the Business or Enterprise plan. On the Rho machine:

npm install -g swytchcode
swy init --operator --editor=none --project ~/agent-swy
swy login

Add Swytchcode to Pi's MCP config, ~/.config/mcp/mcp.json, with your own home folder in the path:

{
  "mcpServers": {
    "swytchcode": {
      "command": "swytchcode",
      "args": ["mcp", "serve", "--profile", "operator", "--project", "/home/you/agent-swy"]
    }
  }
}

Copy the skill to ~/.pi/agent/skills/, add the guard extension above to ~/.pi/agent/extensions/, and restart Rho. Our docs page for any other agent covers connecting your first app.

We haven't tested a real model, Rho's Telegram or email channels, or a real approval on a phone yet.

The quiet failures

The lesson from Rho isn't about Rho. It's that the most dangerous problems are the quiet ones. A start command that says running when nothing is. A shell tool that edits a rules file without asking anyone.

An agent that checks in every 30 minutes will run a lot of calls you never see. Make sure the rules for those calls live somewhere its shell can't reach, and check that it's actually running before you trust it.

Use any AI coding agent

Give this prompt to your agent and get started.

More content