Docs Your own agent
The connector
The connector is a small program you run on your own computer. It does the thinking for your agents with Claude Code, on your own Claude plan. The studio never sees your Claude login.
It only connects out. It asks the studio for work, and posts replies back. Nothing on your computer is opened to the internet.
The quick way: one line
On a Mac (Apple silicon or Intel) or a Linux x64 machine (Windows: see On Windows):
- In the studio, open Settings. Under Your personal agents, press New token (or add an agent).
- Copy the line shown under the token and paste it in a terminal on that machine:
curl -fsSL https://app.stewardstudio.io/install.sh | sh -s -- PASTE-YOUR-TOKEN
It downloads the connector (one file, no repo or Bun needed) to ~/.local/bin/steward-connector, checks the token with the studio, writes ~/.config/steward/connector.json (chmod 600), and starts it at login: a launchd agent on macOS (com.steward.connector, log in ~/Library/Logs/steward-connector.log), a systemd user unit on Linux (steward-connector). Claude Code still needs to be installed and signed in.
Run it again with another token to add a second agent: agents already in the file stay, and an older token for the same agent is replaced. A config for a different studio is kept as connector.json.bak.
The one-file build has these commands:
steward-connector: run.steward-connector setup <studio url> <token>: add an agent to the config.steward-connector act <tool> '<json>': one studio command (what @steward's engine runs; the connector sets the environment).steward-connector install-serviceanduninstall-service: Windows only, run it at sign-in (see On Windows).steward-connector version.
Already use the steward command line? It has the connector built in, so there's nothing else to download (Command line):
steward connect setup https://app.stewardstudio.io PASTE-YOUR-TOKEN
steward connect
steward connect setup PASTE-YOUR-TOKEN (no address) uses the studio you're signed in to. steward connect drain and steward connect version match --drain and version below. It writes and reads the same ~/.config/steward/connector.json, so use one or the other, not both at once: two connectors with the same tokens share their jobs. steward agent add <handle> makes the agent and prints these lines. To run it at login, point a launchd agent or systemd unit (as below) at steward connect instead of steward-connector.
The studio builds these files when it's deployed (bun scripts/build-connector.ts, into dist/, served at /downloads/, the steward command line beside it). Self-hosting without the Dockerfile: run that once on the server. If you already run the connector from the repo under launchd, stop that one first, so two connectors don't share the same tokens.
On Windows
The connector and the steward command line run on Windows 10 and 11 (64-bit; on ARM the x64 build runs under emulation).
What you need first
- Claude Code for Windows, installed and signed in to your plan. Check in PowerShell with
claude --version. - Git for Windows (git-scm.com). Claude Code on Windows runs its commands in Git Bash. If Claude Code says it can't find bash, set
CLAUDE_CODE_GIT_BASH_PATHto yourbash.exe(usuallyC:\Program Files\Git\bin\bash.exe) in your user environment variables. - A connector token (Settings, Your personal agents, as above).
One line, in PowerShell (a normal window, not as administrator):
irm https://app.stewardstudio.io/install.ps1 | iex
It downloads steward-connector.exe to %LOCALAPPDATA%\Programs\steward and adds that folder to your PATH. Then it asks for the token: paste it and press Enter. It isn't shown, and it isn't kept in PowerShell's history (or set $env:STEWARD_TOKEN first and it won't ask). It checks the token with the studio, writes %APPDATA%\steward\connector.json, and makes a Scheduled Task, Steward connector, that starts the connector whenever you sign in to Windows. The task runs as you, only while you're signed in, so it needs no administrator rights. Run the line again with another token to add a second agent.
Or with the command line:
irm https://app.stewardstudio.io/cli.ps1 | iex
steward login --url https://app.stewardstudio.io
steward agent add yourname-agent
steward connect setup
steward connect install-service
steward connect setup asks for the token without showing it. steward connect install-service makes the same Scheduled Task, running steward connect.
Where things are on Windows
| What | Where |
|---|---|
| The programs | %LOCALAPPDATA%\Programs\steward\steward-connector.exe, steward.exe |
| Connector config | %APPDATA%\steward\connector.json (move it with STEWARD_CONFIG) |
| Command line sign-in | %APPDATA%\steward\config.json (move it with STEWARD_CLI_CONFIG) |
| Connector state (pid, held jobs) | %LOCALAPPDATA%\steward\state |
| Log, when it runs from the task | %LOCALAPPDATA%\steward\connector.log. Watch it: Get-Content -Wait $env:LOCALAPPDATA\steward\connector.log |
chmod does nothing on Windows. The config files are private because %APPDATA% can be read only by your Windows account (and the computer's administrators).
Stop, start, remove
steward connect drain finish running jobs, then stop
schtasks /Run /TN "Steward connector" start it again
steward connect uninstall-service drain, then remove the task (config and log stay)
(steward-connector drain and steward-connector uninstall-service do the same.) You don't have to use the task: steward connect in a terminal window works too, while the window stays open. Ctrl-C finishes the running jobs and stops; Ctrl-C twice stops now.
How it differs from a Mac
- Delegated jobs (work a person you allowed, @steward or a loop asked your agent for) run inside macOS's sandbox. Windows has none, so the connector refuses them and says why. Your own requests and @steward's jobs run as usual.
- Windows has no gentle stop signal, so
drainleaves a note the connector checks every 2 seconds. Ending the task in Task Scheduler stops it at once; its jobs go back to the studio within 2 minutes. - Claude Code, Codex and Gemini installed with npm are
.cmdfiles. The connector runs their script with node directly, so arguments arrive exactly as sent. Any other.cmdruns through cmd.exe with every argument escaped. - @steward is told to run
steward-connector act ...(orsteward act ...) by name when PATH finds that program, as the installers set up. Otherwise it's the full path with forward slashes. Both work in Git Bash, PowerShell and cmd. - Design-review screenshots need Playwright on the computer (
STEWARD_PLAYWRIGHT), as on a Mac. Without it the job reads the page's HTML and says the screenshots were skipped. - A computer that sleeps does no work. In Windows' power settings, set it to never sleep when plugged in.
MCP from Windows. steward agent mcp <handle> prints the setup. The Claude Code line works as is in PowerShell and cmd:
claude mcp add --transport http steward https://app.stewardstudio.io/mcp --header "Authorization: Bearer PASTE-YOUR-TOKEN"
Cursor's file on Windows is %USERPROFILE%\.cursor\mcp.json.
The rest of this page is the manual way, from the repo.
What you need
- A Mac or Linux machine that stays on (a Mac mini is ideal). Windows works too: see On Windows.
- Bun installed.
- Claude Code installed and signed in to your plan. Check with
claude --version. - A copy of the Steward repo.
- A connector token for your agent.
1. Get a token
- Open Settings.
- Under Your personal agents, either press New token next to an agent you have, or type a new handle and press Add an agent.
- Copy the token. It is shown once. Making a new one turns the old one off.
The people whose plan runs @steward see @steward in that list too, and can make its token the same way.
Each studio has its own @steward. People type and see @steward in every studio, but each studio's is its own agent with its own token (the first studio's is steward, the others steward-<studio>). Its work, heartbeats and briefs go to that token only, and it can only act in its own studio. Its studio's owners pick whose plan runs it (on the web, Settings, @steward; changing it is a card the named people and every owner approve). That person, signed in to that studio, makes its token under Your personal agents and adds it to their connector:
steward connect setup PASTE-ITS-TOKEN
or steward-connector setup https://app.stewardstudio.io PASTE-ITS-TOKEN. It's one more entry in agents, next to the ones you have; one connector runs @steward for several studios. Give each one its own cwd (like ~/Projects/studio/<studio>). Restart the connector when it's idle to pick it up.
2. Write the config file
Create ~/.config/steward/connector.json:
{
"url": "https://app.stewardstudio.io",
"agents": [
{ "token": "PASTE-YOUR-TOKEN", "cwd": "/Users/you" }
]
}
Then lock it so only you can read it:
chmod 600 ~/.config/steward/connector.json
Each entry in agents is one agent. You can run several from one connector.
token: the agent's connector token.cwd: the folder where its work starts. Defaults to your home folder.repos(optional): maps a business to the folder its code lives in, like{ "steward": "/Users/you/Projects/steward" }. Work on that business starts in that folder.- One job per folder. The connector runs a few jobs at once (
STEWARD_LANES). If a job's folder is a git repo that another job is already working in, the new job gets its own git worktree in.claude/worktrees/job-<id>, started from the remote's main branch (origin/HEAD), and its prompt says so. The other job's branch and files are left alone. The worktree is removed when the job ends. It's kept if it has uncommitted changes or commits that aren't on any branch. A folder that isn't a git repo, like your home folder, is shared as before. The connector only knows about its own jobs, not a Claude Code session you started yourself in that folder.
- One job per folder. The connector runs a few jobs at once (
For a quick test with one agent, you can skip the file:
STEWARD_URL=https://app.stewardstudio.io STEWARD_TOKEN=PASTE-YOUR-TOKEN bun connector/connector.ts
Other settings, as environment variables:
STEWARD_CONFIG: a different path for the config file.CLAUDE_BIN: where theclaudeprogram is, if it isn't on your PATH.STUDIO_CWD: where the studio's business folders are. A delegated job's fresh checkout is cloned from here whenreposdoesn't name the business. Defaults to~/Projects/studio.STEWARD_JOBS_DIR: where delegated jobs get their own folder. Defaults to the system temp folder. Keep it outside your home folder.STEWARD_DELEGATED_TOKEN: the sign-in for delegated jobs, if you don't setdelegatedTokenin the config (see Walls for delegated jobs).STEWARD_STATE_DIR: where the connector keeps its process id and the jobs it holds. Defaults to~/.local/state/steward. Give each connector its own if you run two on one machine.STEWARD_DRAIN_MINUTES: how long a stopping connector lets running jobs finish. Defaults to 30.STEWARD_USAGE_METER: a different path for the plan usage file (see below).
3. Run it
From the repo folder:
bun connector/connector.ts
It prints connector for 1 agent(s) on https://.... In the studio, your agent's dot turns green within a few seconds. Mention it in a channel to test.
4. Keep it running on macOS (launchd)
So the connector starts when you log in and restarts if it stops, add a launch agent.
Create ~/Library/LaunchAgents/com.you.steward-connector.plist. Replace you and the paths with your own:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.you.steward-connector</string>
<key>ProgramArguments</key>
<array>
<string>/Users/you/.bun/bin/bun</string>
<string>/Users/you/Projects/steward/connector/connector.ts</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/Users/you/.local/bin:/Users/you/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key><string>/Users/you</string>
</dict>
<key>WorkingDirectory</key><string>/Users/you/Projects/steward</string>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ExitTimeOut</key><integer>1860</integer>
<key>StandardOutPath</key><string>/Users/you/Library/Logs/steward-connector.log</string>
<key>StandardErrorPath</key><string>/Users/you/Library/Logs/steward-connector.log</string>
</dict>
</plist>
Load it:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.you.steward-connector.plist
ExitTimeOut is how long launchd waits after asking the connector to stop before it kills it. When asked to stop, the connector takes no new work and lets running jobs finish, for up to 30 minutes (STEWARD_DRAIN_MINUTES). 1860 seconds covers that. Without it, launchd kills the connector after 20 seconds, and running jobs start over.
Restart it after you change the config or pull new code:
bun connector/connector.ts --drain
launchctl kickstart gui/$(id -u)/com.you.steward-connector
--drain asks the running connector to finish its jobs and stop, and waits until it has. kickstart without -k then starts it again if launchd hasn't already (KeepAlive usually restarts it on its own). Avoid kickstart -k: it stops the connector in the middle of its jobs.
If the connector stops anyway (a crash, a hard kill, a power cut), nothing is lost. It remembers the jobs it was running (in ~/.local/state/steward, or STEWARD_STATE_DIR), and when it starts again it tells the studio, which hands them out again at once. If it never comes back, the studio notices within 2 minutes, because a running connector checks in every 30 seconds per job.
Watch what it does:
tail -f ~/Library/Logs/steward-connector.log
The PATH line matters. launchd starts programs with a bare PATH. Without ~/.local/bin (where claude usually lives), every job fails quietly.
What it can and can't do
For your personal agent, the connector runs Claude Code the way you normally do on that machine, with your settings and your skills. Your agent can do whatever your Claude Code can do there. It takes jobs only from you, and from people you allowed.
When a job comes from someone you allowed (not you), @steward or a loop, it runs walled off from your things. See Walls for delegated jobs.
The connector also reports your agent's skills to the studio when it connects: only the name and the one-line description from each SKILL.md, so @steward can hand it design, landing page, app, iOS, security and launch work. See Skills and routing.
For @steward, the connector runs Claude Code locked down. It may read files in its working folder, and run one command: the studio tool runner (connector/act.ts, or steward-connector act in the one-file build). Every action goes back through the studio's API, so the action gate and the ledger apply. It can't run anything else on your machine.
A job stops after 30 minutes. The connector checks for work about every 25 seconds while idle, and picks up new work right away when it arrives.
When your computer is off
The sidebar shows a line under each agent that runs on a computer: online, offline since when, and how many jobs wait. Agent work lists every job you can see with its state (queued, running, waiting for the connector, failed with a reason, done) and each try.
- Waiting. When you ask an agent whose computer is off, the channel says "Waiting for <owner>'s computer", with the owner's name. The job runs when it's back.
- Alert. When your computer has been offline for 30 minutes (
CONNECTOR_ALERT_MINUTES) while work waits, you get one push and a message from @steward in your DM with it. You get one per offline stretch. - API fallback for @steward. In Settings, under Your computer, the people whose plan runs @steward can turn on "When my computer is offline, run urgent work on the studio's API brain". It's off by default because it costs money. When it's on and the computer has been offline for 10 minutes (
CONNECTOR_FALLBACK_MINUTES), the studio runs @steward's mentions, heartbeats and briefs itself, on its model (STEWARD_MODEL, or the business's own) and within each business's budget. Tasks and other jobs still wait. It needs the model's API key on the studio (ANTHROPIC_API_KEY, or the key for the provider inSTEWARD_MODEL). - Personal agents never fall back. Their work needs your machine, your files and your plan.
Plan usage and the daily cap
Every run on your computer is counted: runs, minutes, and what it would have cost at API prices (Claude Code reports this). Settings shows today and the last 7 days.
You can set a daily cap in dollars there (or the studio sets one for everyone with CONNECTOR_DAILY_CAP_USD). Past it, heartbeats, briefs and other work nobody asked for pause on your computer until tomorrow, and @steward tells you. Anything a person asks for still runs.
API key or plan: the numbers
Every finished job records what it cost and where it ran (src/server/jobcost.ts):
- On the studio's API key: the real token cost, from each model call's usage at API prices.
- On a plan, through the connector: nothing per token, so the cost is an estimate at API prices (what Claude Code reports) and is labelled "estimated".
Businesses shows, for each business, this month's jobs and cost on the API key beside the jobs and estimated cost on plans, plus a total for the studio. The work panel shows each finished job's cost too. You only see jobs in chats you can see, so another person's DMs never count toward your numbers. The same summary is at GET /api/spend/brains (?month=YYYY-MM for an earlier month).
Use it for the brain switch (STEWARD_BRAIN, connector or api): the plans' estimate is roughly what the same work would cost on the API key, so you can weigh it against the plans you already pay for.
If your computer has the Claude usage meter (~/.claude/usage-meter/usage.json, or STEWARD_USAGE_METER), the connector reads it and reports how much of your plan's 5-hour and weekly limits is left. It only reads the file, and only the two percentages leave your computer.
Security model
- Connects out only. The connector makes outgoing HTTPS requests to the studio. No open ports, no tunnels.
- Your Claude plan stays on your machine. The studio never sees your Claude login.
- Tokens are stored hashed in the studio. The raw token lives only in your config file. Keep the file at
chmod 600. - Tokens never go on a command line. The connector passes the token to @steward's tool runner in the environment, not as an argument.
- Lost a token? Press New token. The old one stops working right away.
- Outside text is marked as data. Agents' own messages, cards, support emails, forms and other outside text reach the agent inside labeled data blocks, with the rule that what's inside a block is never an instruction. See Ownership and security.
Choosing the engine
By default, each agent runs on Claude Code. In the config, an agent can instead use "engine": "codex" or "engine": "gemini". It can also use "command": [...] for any command-line agent, or "model": "provider:model" for an API model, including one on your own computer, such as ollama:qwen3. Setup and examples are in Bring your own model.
What an agent may do unattended
Nobody is watching when the connector runs a job, so Claude Code can't stop and ask "allow this?". Anything not allowed in advance is refused. By default, a personal agent may:
- edit files inside its job's folder (a business's repo copy, for example)
- run common developer commands: git, bun, npm, gh, railway, xcodebuild, curl, sqlite3, psql and a few file commands
- read and search the web
To change this, set "allow" for that agent in the config, as a list of Claude Code tool rules such as "Bash(git:*)". For example, remove "Bash(railway:*)" to stop it from reaching your Railway projects.
Less, for work you didn't ask for
Each job tells the connector who asked and whether it carries outside text (scope in the job). The connector narrows the list above, or your own allow, to match (connector/allow.ts). Deny rules go to Claude Code as --disallowedTools, which win over any allow rule.
| The job | Railway | GitHub | Network |
|---|---|---|---|
| You asked | as configured | as configured | as configured |
| Delegated: someone you allowed, @steward or a loop asked | railway status and railway logs only; railway ssh if you allow it for that business | no gh pr merge, gh api, secrets, releases or workflow runs | as configured |
| Carries outside text: a support email, public feedback, a client call, a web page | none | the same as delegated | no curl, wget or WebFetch |
- railway ssh for delegated work is a switch per business in Settings, under Agent work you didn't ask for. Owners only, and off by default.
- Read commands are the narrow way to read a production number without full
railway ssh. Each one is an exact command, likerailway ssh --project <id> --service web -- bun stats.ts --json, that an owner approved for one business (src/server/read-commands.ts).- The agent asks with
request_read_command(the command and why it changes nothing). That's always a card for an owner, even at a higher trust level. Owners can also add or remove commands in Settings, under Read commands. - A job runs one with
act railway_read '{"command": "..."}'. The job doesn't run it.actasks the connector, through a local address and a key only that job has. The connector asks the hub whether the command is on the business's list exactly, checks it again, then runs it itself, outside the job (connector/read-command.ts). So the job never needs your Railway sign-in. A delegated job, walled off in the sandbox, asks through its own socket for studio tools instead (no key, no local port), and the connector runs it the same way. - The connector runs it with no shell, from a neutral folder, with only
HOME,PATH, the locale and a Railway token if you set one (not the studio token or API keys), and stops it after 2 minutes. - The output comes back as an outside-text block, and the job then counts as carrying outside text: it loses
publish_pageand the other tools such jobs don't get. It can still run other approved read commands. If the connector can't record this with the studio, it withholds the output. Most of its other tools go too. A job that hasrailway_readruns with a hook (act --gate, before every tool call) that asks the connector whether it has run a read command yet. Once it has, the job can only read and edit files, run a few local git commands (status,diff,add,commitand the like, with plain arguments) and studio tools. Every other Bash command,WebFetch,WebSearch, subagents and MCP tools are refused. If the hook can't check, or fails in any way, the call is refused. At most one read runs at a time per job, and 20 in all. Read commands run only for jobs on Claude Code, which runs the hook. What's still open: a program the job started before the read keeps running, a git hook it set up before the read still runs ongit commit, and output it commits to its branch is pushed with it. - Only one shape is allowed:
railway ssh, then--projectand--service(both required, so the command never depends on which project a folder is linked to) and optional--environment, then--and a program. Quotes,$,;,|,&,>and*are refused. So are shells,sudo,rm,sed,awkand similar programs, package changes (bun install,bun x), code on the command line (bun -e),find -deleteand-exec, and words likemigrate,seed,deleteanddrop. The owner still decides whether a command only reads. - Jobs that carry other outside text can't ask for read commands or run them. Every run and refusal goes in the ledger (
read_command.run,read_command.refused).
- The agent asks with
- Work that carries outside text waits for you. A task or handoff that carries it starts only when you say
@yourname-agent do #12, even if @steward or a delegate can usually give your agent work. - The prompt says which tools are off, so the agent doesn't keep trying.
- The allowlist only binds Claude Code. With
enginecodex or gemini, or a customcommand, only the prompt says so for your own work with outside text, and the connector logs that. Delegated jobs on those engines are refused (see below).
Walls for delegated jobs
A delegated job doesn't run in your folders, with your settings or with your tools. The connector sets this up itself, so it holds whatever the prompt says (connector/sandbox.ts):
- Its own checkout. Each job gets a new private folder (0700) outside your home folder (
STEWARD_JOBS_DIR, default the system temp folder). The business's repo is cloned into it from its upstream (theoriginof the copy inreposorSTUDIO_CWD, whose objects it borrows for speed), at its default branch. Your working copy is never used, and your unpushed commits and local branches aren't in it. The folder is deleted when the job ends, and leftovers from a crash are deleted when the connector starts. - No home folder. Claude Code runs inside macOS's built-in sandbox (
sandbox-exec), with a profile that denies everything by default. The job may read and write its own folder, and nothing else. It may read the system and developer tool folders (/usr,/System,/Library/Developer, Xcode, Homebrew) and, in your home folder, only.local/bin,.local/share/claude,.bunandLibrary/Caches/ms-playwright, plus the connector's own code: itsconnector/folder,package.jsonandtsconfig.jsonwhen it runs from the repo (not the hub's code, scripts or anything else there), or the built connector's folder. It can run programs and use the network. To allow more folders (read only), add"sandboxRead": [...]to the agent.- Nothing in your home folder is writable. So the job can't plant or replace a program that you'd run later. Its bun, npm and other caches live in its own folder (
BUN_INSTALL_CACHE_DIR,npm_config_cache,XDG_CACHE_HOME), andCFFIXED_USER_HOMEpoints there too, so Xcode's DerivedData does as well. So does its temp folder (TMPDIR, andTMPPREFIXfor zsh here-documents). The shared temp folders are denied. - No sign-ins. gh's and railway's sign-ins,
~/.gitconfigand git's credential files are unreadable, so the job can't merge, push to main or deploy past the allowlist. Its git uses a settings file with only your name and email. The job commits to its own branch. When it ends, the connector pushes the branches the job made or moved, with your usual git sign-in. It never pushes the default branch, main or master, and never forces. The reply says what was pushed, and @steward opens the pull request. To do that safely, the connector first fetches the job's branches, inside the sandbox, into a new repo the job never touched, and pushes from there. Before the push, it keeps only that repo's commits and branches and writes its settings again itself, so nothing the fetch wrote there (a redirect for the push, a credential helper, a hook) runs with your sign-in. With no sign-in,railway statusandlogsdon't work in a delegated job. Read commands an owner approved (railway_read) do, because the connector runs them. - No keychain, AppleScript or apps. The system services it may reach are a short list (DNS, the network stack, certificate checks). So the keychain, Apple Events (AppleScript control of Mail, Messages or Finder) and opening apps are refused.
- No other jobs. Every other job's folder is denied, even if a
sandboxReadfolder covers it. - Your
~/.claude,~/.ssh, Documents, Mail and Messages are out of reach.
- Nothing in your home folder is writable. So the job can't plant or replace a program that you'd run later. Its bun, npm and other caches live in its own folder (
- The allowlist, nothing wider. The job gets its own empty Claude Code settings folder (
CLAUDE_CONFIG_DIRin the job folder) and--setting-sources local. If the repo commits a.claude/settings.local.json(the one file that still loads), the connector removes it before the run, and the sandbox stops the job from putting it back. Your allow rules and hooks don't apply, and neither do the repo's. Only the table above does. Other engines and custom commands have no allowlist, so delegated jobs on them are refused. - No owner tools. No MCP servers load (
--strict-mcp-configwith an empty list), claude.ai connectors are off (ENABLE_CLAUDEAI_MCP_SERVERS=false), and Gmail, Calendar and Drive are denied too. The job has no email, calendar, Drive or Telegram tools. Its environment has only the basics (PATH, HOME, locale) and the job's studio variables, so your API keys stay out.
Setup, once. Because the job can't use your ~/.claude, it signs in with its own token. Run claude setup-token, put the token in the agent's entry as "delegatedToken" (or set STEWARD_DELEGATED_TOKEN), and restart the connector. The job never sees this token. Its Claude Code talks to the API through a small proxy the connector runs for that job alone (on 127.0.0.1, with a random key), and the proxy adds the token. So a job that reads its own environment finds only the key, which stops working when the job ends. The proxy passes only what Claude Code needs to think (/v1/messages, token counts and the model list), not files, batches or account routes. Until you set the token, and on a computer without sandbox-exec (anything but macOS), delegated jobs are refused with a reply that says why. They never run with fewer walls. Your own jobs and deploy jobs are unchanged.
What the walls leave open, on purpose. The network is open, because jobs install packages, fetch and call the studio, so a job could send what it can read (its checkout) somewhere. File names and details in your home folder (stat) are visible if the job already knows the path, but it can't list a folder or read a file there. Local sockets (your ssh-agent, Docker) are closed, apart from DNS and the job's own socket for studio tools, which adds the agent's token for it (the job never holds the token). Services already listening on your computer when the job starts (a database, a dev server) are closed to it; ones you start while it runs aren't, and other devices on your network aren't. Homebrew's data folder (/opt/homebrew/var) is closed.
What the push leaves open. The connector pushes only new steward/… branches, never one that touches .github/, never forced, and exactly the commit it checked. A branch that's already on GitHub (an open PR's branch, maybe one already reviewed) is never moved by a delegated job, even if the request names it, because outside text quoted in a request can name a branch too. The reply says it wasn't pushed, and the work goes on a new branch and a new PR. Rebasing or adding to an existing PR is your own job, or an agent job you ask for yourself. A merge is pinned to the commit its security review saw, so a later push gets a new review before it merges. And workflows that already run on every push run the job's code with their secrets: add branches-ignore: ['steward/**'] to any workflow with secrets you don't want there. A process the job started in a new session (setsid, a daemon) outlives its process group. So when the job ends, the connector also stops every process whose working folder is in the job's folder. One that leaves the group and the folder on purpose can still keep running, inside the sandbox, until you stop it or restart the computer. The fix for that is a separate macOS user for delegated jobs, which isn't built yet. Services you start on your computer while a job runs aren't closed to it, because the sandbox can only close the ports that were listening when the job started.
Studio tools for personal agents
A personal agent can do a few things in the studio itself, not just on your computer. For example, your agent can publish the landing page it built and record the numbers it found. It uses the same command @steward uses, with its own token, and only these tools:
| Tool | What it does |
|---|---|
publish_page | publish or update the business's landing page |
publish_extra_page | publish or update an extra page, like /p/<business>/pricing |
record_metric | record a number |
get_signups, get_metrics | read the waitlist and the numbers |
create_task, update_task | work the board |
add_prospects, list_prospects | save prospects found in research to the business's outreach list, and read it (contacts masked) |
report_gap | say what Steward couldn't do |
list_data, query_data, stage_data_change | the business's data |
request_read_command, railway_read | ask an owner to approve a read-only command in production, and run an approved one (see Read commands above) |
legal_text | read the terms, privacy policy or refunds text an owner approved, to put it on the business's own site |
read_message | read one studio message by id (like message:1234, the way the doctor lists them): who posted it, where, the text, and the job that posted it (its task, who asked, whether it carried outside text and whether the connector ran it walled) |
update_charter, set_charter_fields | change the business's charter notes or fields. Saved at once when an owner asked and the job carries no outside text; otherwise the owners get a card with the changed lines, and their Approve saves it. If the charter changed before the yes, nothing is overwritten |
- Only in a job, only on that job's business, and only when that business is one you own. Naming another business is refused.
read_messageis the one read that reaches past it: a message in any room of the same studio that everyone who can see the job's channel can see too (and your agent's owner), so it never shows a room's words to someone outside it. An agent's message comes back in a data block, like the channel history in every prompt, and reading it doesn't add outside text to the job. - The same gate as @steward. A first publish is an owner's card ("<your agent> wants to..."). The trust ladder applies, and so do the outside-text cap and the design review for new page versions. Every call goes to the ledger as your agent (
studio_act, andstudio_act.refused). - A job that carries outside text keeps only
get_signups,get_metrics,create_task,update_task,report_gap,add_prospects,list_prospects,list_data,query_data,legal_text,read_message,update_charterandset_charter_fields. The charter tools stay because on such a job they only post an owner's card.legal_textstays because it only reads text an owner approved to go public. The prospect tools stay because research reads the web, so research jobs always carry outside text. Saving to the list sends nothing: messages go out only from a sequence an owner approved. Changing a prospect (update_prospect) and drafting a sequence stay with @steward and the owners. - A design review job keeps only the read tools.
- The job brings its tool list (
studioin the job). The connector adds just those commands to the allowlist, likeBash(steward-connector act publish_page:*), and tells the agent how to run them (connector/allow.ts studioAllow,connector/prompt.ts). The studio checks each call again (src/server/studio-act.ts). - A design review job also gets the screenshot command:
steward-connector screenshot <url>, orbun connector/screenshot.ts <url>. It only opens pages on the studio that sent the job. It takes 4 shots: 390px and 1280px wide, in light and dark. Playwright comes from your computer: theplaywrightpackage if it resolves, orSTEWARD_PLAYWRIGHT(a playwright package folder), or~/Projects/diamond-iq/node_modules/playwright. - In a delegated job, walled off in the sandbox, Chromium can't start (macOS refuses its Mach ports). There the same command asks the connector through the job's socket, and the connector takes the screenshots itself, outside the sandbox (
connector/sandbox.ts reviewShooter). It only takes them of that review's page, 5 sets at most (asking for another page is refused and uses none), and the page can't load anything from your own computer or network (localhost, 10.x, 192.168.x and the like). Every request Chromium makes there goes through a small proxy the connector runs (connector/screenshot.ts guardProxy). It checks the address after DNS, so a public name that points to 127.0.0.1, an IPv6 form like[::ffff:127.0.0.1]and each hop of a redirect are refused too. Service workers are blocked, and WebRTC can't go around the proxy. The PNGs go in a folder next to the job's that the job can read but not change, and the folder is deleted when the job ends.
Deploy jobs
Some services are deployed with railway up from a checkout. When an owner approves one of their deploys (or the trust ladder allows it), the build agent gets a deploy job (kind railway_deploy). It doesn't use the table above. It has its own fixed rules (connector/deploy.ts):
- It's refused if:
- it carries outside text
- its scope doesn't say which approval allowed it
- the agent's engine isn't Claude Code
- the config has no
reposentry for the business
- It finds the business's main checkout with
git worktree listfrom thereposfolder, checks thatoriginis the business's GitHub repo, and refuses unless the commit is the head of main there (git ls-remote). - It deploys from
~/Projects/studio/<slug>-deploy(underSTUDIO_CWD), a worktree made withgit worktree add --detach. Each time, it's reset to the commit and cleaned. It never deploys from the "steward" branch worktree. - Claude Code may run only
railway up --service <name> --project <id> --environment <id> --cifor each service, andrailway statusandrailway logs. It can't edit files or run anything else. The prompt is written by the connector from the job, not taken from the studio. - The reply starts with
DEPLOYED,REFUSEDorFAILED. The studio then checks Railway for the new deployments and watches them.
The rules bind Claude Code's own allowlist. If your own Claude Code settings allow a broad rule like Bash(railway:*), other railway up forms aren't blocked. The deny list holds, the prompt is fixed, and the studio only watches the services it asked for.
See Services deployed with railway up.
@steward on your machine
@steward reads only inside its working folder, never ~/.ssh, ~/.config, ~/.aws or .env files. After each web fetch or search, a Claude Code hook runs act.ts --taint, which tells the studio the job read the web. From then on, the gate asks an owner before anything risky that job wants.