OpenStation separates identity, admission, behavior, and tool authority. Start by deciding whether you are defining a role for a person entering a conversation or for an agent receiving a reusable set of permission rules.
Four separate decisions
| Decision | Configured in | Effect |
|---|---|---|
| Who is speaking? | people.yaml, members[].contacts |
Maps a channel handle to a stable person |
| May they start a turn here? | The person's role and channels[].roles |
Admits or refuses the caller |
| How should the agent behave? | Explicit profile on the person, space, or agent |
Selects the behavioral addendum |
| What tools may the agent use? | claudeSettings and the settings file it names |
Selects the enforced gate |
Both profile and claudeSettings resolve from person to space to agent, independently.
The CLI uses its startup role (--as, default member) as the agent's profile fallback when
the agent has no explicit profile. A caller's admission role does not automatically select a
different profile or settings file for each message.
Define roles
The optional .openstation/roles.yaml roster declares which roles exist and their scope:
roles:
member:
scope: person
description: A teammate admitted to shared conversations.
admin:
scope: person
description: An operator admitted to the administration space.
private-state:
scope: agent
required: true
description: Keep platform state and credentials out of agent reads.
deny:
- Read(var/**)
- Read(.env)
- Read(**/.env)
- Read(.env.keys)
- Read(**/.env.keys)
- Read(**/secrets/**)
notes-writer:
scope: agent
description: Read workspace context and maintain the notes bundle.
allow:
- Read
- Glob
- Grep
- Skill
- Edit(okf/**)
Use scope: both only when one named role is intentionally valid for both consumers. Scope
is required. Agent roles compose gate files; person roles are usable at admission sites.
Assign people
Map service handles in .openstation/people.yaml. These handles are examples; replace them
with the IDs reported by your connector.
members:
- id: alice
role: admin
contacts:
slack: U01234567
- id: sam
role: member
contacts:
telegram: "123456789"
With a roster present, omitting defaultRole refuses unknown contacts. With no people roster,
identity is passthrough and callers receive the process's startup role. See the
people.yaml reference before choosing that behavior.
members add can create the roster and a missing profile. It initially adds no contact handles
and creates a new roster with defaultRole: member; review that default when access should be
limited to explicitly listed people.
Configure the agent and its spaces
Merge this agent declaration into .openstation/openstation.yaml:
agents:
notes:
identity: agent:notes
executor: claude-cli
default: true
profile: member
claudeSettings: notes
roles: [private-state, notes-writer]
channels:
- connector: slack
type: dm
roles: [admin]
profile: admin
The example changes behavior in the Slack DM while retaining the notes agent's gate. To grant
different authority there, explicitly name a reviewed claudeSettings file on the space.
Person-level overrides follow that person into every space, so reserve them for behavior or
authority that should travel with them.
Provide .claude/agents/notes.md and the referenced profiles. Person roles admitted by a
channel need .openstation/profiles/<role>.md at load time; here that means admin.md, while
member.md is also required as the agent's named profile. Agent-only permission roles need no
profile file. A profile's tools: is a YAML array and describes an advisory ceiling; it is
not the permission boundary.
Compile and check gates
When adding a roles roster to an existing workspace, migrate the current allow rules, deny rules, and supported role hooks before writing gates. Review all generated changes.
bun packages/cli/src/index.ts my-space gates write
bun packages/cli/src/index.ts my-space gates check
gates write generates .claude/settings.<label>.json from the agent's roles and inline
allow/deny lists. gates check detects drift. dev and serve refuse drift at boot and do not
silently regenerate files.
Role rules are unioned, and denies win over allows. Adding an allow role can grant new capabilities that are not denied; it cannot override an existing deny. A required agent role must be held or explicitly exempted with a nonempty reason.
The roles reference covers scopes, hooks, exemptions, and invalid patterns. Read permissions for enforcement details and executor differences.