What happens between a message arriving and a reply going out — the parts that change how you
configure things. The full design lives in
internal/architecture/00-overview.md.
The path a message takes
Slack · Telegram · Email · REPL
│
▼
Connector normalizes the message into a common shape
│
▼
Bridge who is this? · are they allowed? · which agent · which session
│
▼
TurnRunner concurrency cap + one turn at a time per conversation
│
▼
Executor turns that into a Claude Code invocation
│
▼
Claude Code reads the charter, skills, and tools; does the work
The one thing to internalize
The platform resolves pointers. Claude Code materializes context.
The Bridge decides which session to resume, which agent answers, and what that agent is permitted to do. It does not assemble a prompt, gather files, or build context — Claude Code does that at run time from the workspace it's pointed at.
This is why nearly everything you write to shape an agent's behavior is a Claude-native file:
| You want to change | You edit |
|---|---|
| what the agent does and how it talks | .claude/agents/<name>.md |
| what it's allowed to touch | .claude/settings.<label>.json |
| a reusable procedure it should follow | .claude/skills/<name>/SKILL.md |
| which agents exist, and their identity/executor/settings binding | .openstation/openstation.yaml |
The manifest references those files. It never restates a prompt, model, or tool list — the loader rejects the attempt rather than letting two sources of truth drift apart.
Turns are synchronous
One message in, one reply out, on the request path. There's no queue you can inspect and no job id to poll: the connector waits for the turn and delivers whatever comes back.
Two consequences you will actually hit:
A concurrency cap. Three turns run at once by default, and one permit is reserved for interactive traffic so background work (triggers, scheduled jobs) can never starve a live conversation. Over the cap a turn waits its turn (FIFO, up to 32 waiting) and then runs; only a caller arriving once that queue is full is told:
I'm at capacity right now — please try again in a minute.
One turn at a time per conversation — and a second message is refused, not queued. If a message arrives for a conversation whose turn is still running, the reply is:
Still working on your previous message — please wait.
Nothing is buffered. The user has to send it again. (Design doc 03 describes this as
serializing, which reads as the second one waits; the implementation rejects it.)
What Claude Code brings
Session continuity (--resume), the workspace charter, skills discovery, tool execution, and
permission enforcement all belong to Claude Code. OpenStation's job is to point it at the right
workspace with the right settings file and get out of the way.
That's also where the safety boundary lives: the permission gate is the settings file Claude loads, not a filter the platform applies. See permissions.
Where the pieces live
| Concern | Page |
|---|---|
| which file holds what | agents and profiles |
| where the agent's work goes | workspaces and git |
| how a conversation stays continuous | sessions and turns |
| what's allowed, and who enforces it | permissions |