Standing up an OpenStation: scaffold a workspace, talk to the agent, then decide where it should be reachable. Everything below was run against the current code.
Concepts in one breath
- An OpenStation is a directory with two sibling config planes —
.openstation/(the management overlay) and.claude/(Claude-native behavior) — plus the workspace files and a git history. - An agent is an entry in
.openstation/openstation.yamlthat references the Claude-native files holding its behavior and permissions. - A connector is a channel the agent is reachable on: Slack, Telegram, Email, or a local REPL.
- The platform is Bun/TypeScript. Your agent's tools can be any language — they run as subprocesses or MCP servers.
Prerequisites
- Bun — runs the platform.
- Claude Code CLI, installed and authenticated: either
claude loginorANTHROPIC_API_KEYin the environment. OpenStation spawns it; it doesn't bring its own model access. - Any tool runtimes your agent needs — a Python venv, say. The platform doesn't need them; the agent does.
The CLI isn't published to npm yet, so run it from a clone:
git clone <this repo> && cd openstation && bun install
Workspaces live in one place — ~/openstation/<name>, one git repo per agent project — so every
command below takes a bare name. -d/--dir still points anywhere you like, and with neither the
workspace you are standing in — or anywhere under — is used, so the name is optional once you are
inside one. See the CLI reference for the precedence rules and
OPENSTATION_HOME.
1. Initialize the installation
Once per machine. It creates the home your workspaces live in and a README to catalog them:
bun packages/cli/src/index.ts init
created ~/openstation
created ~/openstation/README.md
OpenStation home: ~/openstation
Create your first workspace: openstation create <name>
2. Create a workspace
bun packages/cli/src/index.ts create support
created .openstation/openstation.yaml
created .openstation/profiles/member.md
created .claude/agents/support.md
created .claude/settings.support.json
created okf/index.md
created .env
workspace trusted in /Users/you/.claude.json — its gate stays in force
git repository initialized, scaffold committed as 6f2a1c8
OpenStation ready: ~/openstation/support/.openstation/openstation.yaml
The workspace is a git repository from its first second, with the scaffold as commit one — that is
what makes every later turn revertible. A .env for this workspace's secrets, plus a .gitignore
holding var/, .env, .env.keys, okf/scratch/inbound/, repos/, and
.openstation/openstation.local.yaml, are written quietly.
The trust line is the one thing create writes outside the workspace, and it is what keeps the
gate below from being ignored: Claude drops an untrusted workspace's whole allow list from every
turn, warning on stderr without failing. --no-trust skips it; every boot re-checks it. What the
five announced files are for:
.openstation/openstation.yaml — the manifest. It declares the agent and points at the
Claude-native files; it never restates them.
# .openstation/openstation.yaml — management overlay; references Claude-native files, never restates them.
agents:
support:
identity: agent:support
executor: claude-cli
claudeSettings: support
channels: []
default: true
claudeSettings names the enforced gate (.claude/settings.support.json); channels: [] is
where you'd add a channel this agent answers on; default: true makes it answer anything no
other agent claims — dev needs that to boot at all. Minimal on purpose: no budget:, no
comments. See "what you can add" below.
.claude/agents/support.md — the charter, read by Claude Code, not by OpenStation:
---
name: support
description: support agent.
---
You are the support agent for this workspace. Answer from what you can read here.
Deliberately no tools: frontmatter. Claude intersects that field with the settings gate, so a
stub list here could silently narrow it — the settings file is the sole narrowing.
.claude/settings.support.json — the enforced permission gate:
{
"permissions": {
"allow": ["Read", "Glob", "Grep", "Skill", "Edit(okf/**)", "Write(okf/**)"],
"deny": [
"Read(.env)", "Read(**/.env)", "Read(.env.keys)", "Read(**/.env.keys)",
"Read(**/secrets/**)", "Bash(rm:*)",
"Read(var/**)", "Bash(cat var/*)", "Read(logs/**)", "Bash(cat logs/*)"
]
}
}
Reads everywhere, writes scoped to okf/ — the default writable, git-committed zone — and
.env, .env.keys, secrets/, var/, logs/, and rm denied.
.openstation/profiles/member.md — the addendum for the member role:
---
tools: ["Read", "Glob", "Grep", "Skill", "Edit", "Write"]
---
You are talking to a member. Be concise and factual.
tools: here is a ceiling, not a grant — bare verbs, matching the gate's by name. A role with no
profile file is refused, so if you plan to use --as admin, write profiles/admin.md first.
okf/index.md — a stub, deliberately without frontmatter (bookkeeping must never match an
artifactType: trigger). It makes the bundle exist in git from commit one, since an empty
directory doesn't survive a clone. Everything the agent grows underneath — issues/, notes/,
whatever it invents — is already watched, committed, and covered by the write grant above; only
the bundle root itself is a scaffolded file.
create already made this a repository and committed the scaffold, which is what makes the
agent's work reversible — git log in the workspace shows commit one. If create reported that
git could not run, fix that now, before the agent writes anything.
What you can add
budget: isn't scaffolded — it's optional, and any default is an opinion. Add one to
agents.support in the manifest to cap what a turn may spend:
budget:
maxUsd: 0.50 # Claude: native ceiling; Pi: observed post-request threshold
maxTurns: 8 # claude-sdk and pi-sdk only; claude-cli refuses it
See permissions for what each field does and which executors honor it.
3. Meet the roster (optional)
.openstation/people.yaml isn't scaffolded either — without it, identity is passthrough and
every caller gets the member role. Add a person with members add:
bun packages/cli/src/index.ts support members add alice --name Alice --role owner
added member "alice" to ~/openstation/support/.openstation/people.yaml
First use creates the file; a later call appends to it. alice has no channel handle yet — she
resolves nothing until you add one by hand. See people.yaml.
4. Talk to it
bun packages/cli/src/index.ts support dev
openstation dev — agent "support"
openstation repl — Ctrl-D to exit, /new to reset the session
you>
Type a message and you get a reply. /new starts a fresh session; Ctrl-D exits. No credentials
beyond Claude auth, no daemon.
The scaffolded agent can read anything and write only under okf/ — ask it about the
workspace, or have it file something there. To reach further — other paths, Bash, a custom
tool — that's your first agent.
5. Add another agent
bun packages/cli/src/index.ts support agents add reviewer --channel review
added agent "reviewer" to ~/openstation/support/.openstation/openstation.yaml
Config only. It appends a manifest entry and writes the .claude/agents/reviewer.md and
.claude/settings.reviewer.json that entry references.
One thing to know now rather than later: channels: does not route messages yet. Agent
selection resolves once at startup, so one process serves one agent. Two agents means two
processes with different -a values. See internal/roadmap.md under
"Known gaps to v1".
A whole pipeline can be scaffolded the same way: support loops add copies in the
eval → learn → improve loop — two trigger-fired agents that apply approved improvements, their
charters, gates and stores, and the rules that fire them — out of a template bundle the CLI
ships. It is content you then own and have to finish (each charter has a ## Workspace knowledge section to fill in); see loops.
6. See what happened
bun packages/cli/src/index.ts support events -n 20
On a fresh workspace:
no events recorded yet
That's expected: chat turns publish no events. The log records commits, triggers, scheduled runs, and approvals — not conversations. Once an agent with write access changes something, you'll see its commit:
2026-07-25T07:00:02.256Z workspace.CommitMade sha=1ff8221… filesChanged=2 message=agent: workspace update
See the events reference. For now rather than history — is the daemon
up, what's in flight, what's queued — there's status.
7. Put it on a real channel
By default connectors start from credentials in the environment — there's no flag:
export SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-...
bun packages/cli/src/index.ts support serve
With no credentials present, serve tells you so and exits. Per-connector setup:
Slack · Telegram ·
Email.
To name your connectors instead — two bots of one type, credentials by reference — declare a
connections: block. For the
whole configuration in one pass, see end to end; for running it as a
long-lived service, deploying.
Secrets
Never in openstation.yaml, never in plaintext in the repo. Supply them by environment — a
systemd EnvironmentFile, container env, or your shell — and keep the agent's settings denying
Read(.env), as the scaffold does. A workspace can also declare where its own variables come
from, with an
env: block:
a .env by default, or one AWS Secrets Manager secret — or commit an encrypted .env with the
dotenvx provider, so the secrets travel with the repo and only the private key stays out; see
sharing secrets with dotenvx. See
environment variables.
Managing more than one
Every command above named its workspace first — openstation support dev, not
openstation dev support. Three commands work on the set instead of one member of it:
bun packages/cli/src/index.ts list # what is here, and whether each would boot
bun packages/cli/src/index.ts support disable # stop serving it, keep it
bun packages/cli/src/index.ts support enable # serve it again
list scans ~/openstation for directories holding .openstation/openstation.yaml, so nothing
has to enrol a workspace for it to appear. A disabled workspace is refused by serve and merely
warned about by dev — the local loop is where you debug one you have taken off its channels.
Next
- Your first agent — build one that actually does work
- Permissions — what the agent may do, and who enforces it
- CLI reference — every command and flag
examples/notes-agent— a complete, runnable workspace