Steward
All docs

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):

  1. In the studio, open Settings. Under Your personal agents, press New token (or add an agent).
  2. 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:

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

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

WhatWhere
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

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

1. Get a token

  1. Open Settings.
  2. Under Your personal agents, either press New token next to an agent you have, or type a new handle and press Add an agent.
  3. 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.

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:

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.

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):

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

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:

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 jobRailwayGitHubNetwork
You askedas configuredas configuredas configured
Delegated: someone you allowed, @steward or a loop askedrailway status and railway logs only; railway ssh if you allow it for that businessno gh pr merge, gh api, secrets, releases or workflow runsas configured
Carries outside text: a support email, public feedback, a client call, a web pagenonethe same as delegatedno curl, wget or WebFetch

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):

  1. 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 (the origin of the copy in repos or STUDIO_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.
  2. 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, .bun and Library/Caches/ms-playwright, plus the connector's own code: its connector/ folder, package.json and tsconfig.json when 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), and CFFIXED_USER_HOME points there too, so Xcode's DerivedData does as well. So does its temp folder (TMPDIR, and TMPPREFIX for zsh here-documents). The shared temp folders are denied.
    • No sign-ins. gh's and railway's sign-ins, ~/.gitconfig and 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 status and logs don'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 sandboxRead folder covers it.
    • Your ~/.claude, ~/.ssh, Documents, Mail and Messages are out of reach.
  3. The allowlist, nothing wider. The job gets its own empty Claude Code settings folder (CLAUDE_CONFIG_DIR in 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.
  4. No owner tools. No MCP servers load (--strict-mcp-config with 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:

ToolWhat it does
publish_pagepublish or update the business's landing page
publish_extra_pagepublish or update an extra page, like /p/<business>/pricing
record_metricrecord a number
get_signups, get_metricsread the waitlist and the numbers
create_task, update_taskwork the board
add_prospects, list_prospectssave prospects found in research to the business's outreach list, and read it (contacts masked)
report_gapsay what Steward couldn't do
list_data, query_data, stage_data_changethe business's data
request_read_command, railway_readask an owner to approve a read-only command in production, and run an approved one (see Read commands above)
legal_textread the terms, privacy policy or refunds text an owner approved, to put it on the business's own site
read_messageread 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_fieldschange 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

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):

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.