An agent in OpenStation is spread across a few small files on purpose. Knowing which file holds what saves you from the most common mistake: putting behavior in the manifest, where it will be rejected.
Which file holds what
| File | Owner | Holds |
|---|---|---|
.openstation/openstation.yaml |
OpenStation | which agents exist; each one's identity, executor, settings label, channel claim |
.claude/agents/<name>.md |
Claude Code | the charter: system prompt, model, tool list |
.claude/settings.<label>.json |
Claude Code | the enforced permission gate |
.openstation/profiles/<name>.md |
OpenStation | an explicitly selected behavioral addendum layered onto the charter |
.claude/skills/<name>/SKILL.md |
Claude Code | a reusable procedure Claude discovers on its own |
The manifest references the Claude-native files by name; it never restates their contents.
This isn't a style preference — the schema is strict, so a model: or tools: key in
openstation.yaml fails at load:
agents.a: Unrecognized key(s) in object: 'model'
The point is that there's exactly one source of truth for behavior and exactly one for enforcement, and neither can silently drift from a copy.
The charter
.claude/agents/<name>.md is an ordinary Claude Code agent file — frontmatter plus a system
prompt:
---
name: notes
description: Keeps a durable, searchable record of decisions and open questions.
tools: Read, Glob, Grep, Skill, Edit, Bash
---
You keep this workspace's written record.
When someone tells you something worth remembering — a decision, a problem, an open
question — write it down as a note under `okf/issues/` using the `note` skill…
Watch the tools: dialect. In a charter it's a comma-separated string — Claude's own
format. In a profile file it's a YAML array. They look similar and are not interchangeable:
tools: Read, Glob, Grep # .claude/agents/<name>.md — comma string
tools: ["Read", "Glob", "Grep"] # .openstation/profiles/<role>.md — YAML array
Note also that tools: in the charter is not the security boundary. The settings file is. See
permissions.
Profiles
A profile is a behavioral addendum layered on top of the charter. It lives at
.openstation/profiles/<name>.md and can be selected for a person, a space, or an agent:
---
tools: ["Read", "Glob", "Grep", "Skill", "Edit", "Bash"]
---
You are talking to a teammate. Be brief. Prefer filing a note over a long reply — a
sentence saying what you filed and where beats three paragraphs they have to re-read.
Frontmatter takes tools (required, at least one pattern) and an optional model. The body is
the addendum.
Selection is explicit: the person's profile in people.yaml, then the matching space's
profile, then the agent's profile. If the agent declares none, the CLI uses its startup
role (--as, default member) as the default profile name. A caller's admission role does
not automatically change the profile on each message.
Missing named profiles are refused, not downgraded. Person roles named in a channel's
roles: list also need corresponding profile files at load time; agent-only roles that compose
permission gates do not. See roles and people for the distinction.
Profiles carry no authority. claudeSettings resolves independently from person to space to
agent; putting it in a profile is a load error.
Which agent answers
Selection reads the manifest: an exact channels: match wins, otherwise the agent declared
default: true, otherwise nothing (which is a boot failure).
It resolves once, at startup. dev selects with the channel repl; serve passes no
channel at all and therefore always lands on the default agent. One process serves one agent
for its whole lifetime, no matter how many agents the manifest declares or what channels they
claim.
So channels: today is a claim — validated for conflicts at load, stored, and not consulted
per message. To run two agents, run two processes with different -a values. Per-message
routing is tracked in internal/roadmap.md under "Known gaps to v1".
Adding an agent
bun packages/cli/src/index.ts my-space agents add reviewer --channel review
Config only — no platform code changes. It writes the manifest entry plus the Claude-native files that entry references, which is the whole point of the overlay: a new agent is a declaration, not a deployment.
Which conversations that agent answers in is a separate question: see spaces and channels. Handing work to a second agent mid-turn is subagents.