Turnstone personas: one agent console, different hands for different jobs
Purpose
This lab runs Turnstone, an open-source console for LLM agents, on its Docker host. Sessions default to a local 26B model served by llama.cpp, an 8B local model judges every tool call, and three Claude models are available as aliases for the days the local ones are not enough. The next question was the obvious one: how do you give a research session, a coding session and an operations session different tools, without running three consoles?
Turnstone’s answer is the persona, with tool policies, skills and prompt policies layered around it. This post is the result of reading the v1.9 docs end to end (and the source where the docs were quiet), plus a day of configuring the lab’s console. It is written so the next person does not have to repeat that reading.
What a persona is
A persona is a named bundle attached to a workstream at creation. The documentation is deliberately strict about its size: “a persona is exactly four levers, no more”.
| lever | field | what it changes |
|---|---|---|
| identity | base_prompt | replaces only the BASE module of the system prompt (the framing, the voice). The environment block, the session context, the tool guidance and the admin prompt policies still compose on top |
| tool visibility | tool_allowlist | null = every tool, [] = no tools at all, [names] = exactly these. Put tool_search in the list and the set is soft (the model can discover more tools); leave it out and the set is hard |
| MCP | mcp_enabled | whether the session talks to MCP servers at all. Session-wide: there is no per-server pick |
| memory | memory_enabled | the memory index, live pointers, nudges and the memory tool, as one switch |
Around the levers sit bookkeeping fields: an immutable slug name (the identifier agents and the CLI use), a display_name, a one-line description that agents read when they pick a persona for a sub-agent, applies_to_kinds, is_default (exactly one default per kind, enforced by storage) and enabled. There is no delete, only archive, so every old workstream keeps an explicable provenance.
Six personas ship with the product: engineer (the interactive default, everything on), orchestrator (the coordinator default), scribe (no tools, no MCP, no memory, for faithful summarising), researcher (read, search, fetch, recall and memory, soft set, MCP off), writer (no tools) and executive (a coordinator with a hard set of delegation tools). Built-ins can be customised by setting base_prompt on them; they cannot be archived.
Two kinds of workstream
There are exactly two kinds. Interactive workstreams run on a node: web chats, CLI sessions, chat-channel bots, scheduled tasks and sub-agents all are interactive. Coordinator workstreams run on the console and orchestrate other workstreams through a fixed tool set that no skill or MCP server can extend; a persona can only narrow it. “Scheduled” and “channel” are surfaces, not kinds: they select a different environment block in the prompt, nothing more.
Each kind has its own default persona, and a persona lists the kinds it applies to. Pick the wrong one and creation fails with the list of personas that would have worked.
Stamped once, kept forever
The persona is resolved when the workstream is created and copied into the workstream’s configuration: the slug, the prompt text, the tool set, the MCP and memory flags. From then on:
- editing or archiving the persona changes nothing for existing workstreams;
- a fork keeps the source’s persona and does not re-resolve it;
- children spawned by a coordinator, and sub-agents started with
task_agent, never inherit their parent’s persona. They get the interactive default unless the call names one; - a workstream can never change persona; resume ignores the flag.
This is the opposite of prompt policies, which are read live and reach existing workstreams on their next prompt composition. Guidance that must follow every session, forever, belongs in a prompt policy; guidance that should be frozen with the job belongs in the persona.
Where the persona is chosen
Every creation surface takes an optional persona, and empty always means the kind’s default: the Persona dropdown in the console launcher, the persona field of the create API, --persona on the CLI, the persona column of a scheduled task, and the persona argument of spawn_workstream, spawn_batch and task_agent when agents start agents. The live persona list is injected into those tool descriptions, so a coordinator can choose by purpose from the one-line descriptions.
Two gaps matter in practice:
- Chat-channel bots have no persona field. A Discord or Slack adapter carries a model, an auto-approve list and a skill. Bot conversations always get the interactive default, so the default persona is effectively the bot’s persona.
- A persona does not choose the model or the judge. The model comes from the
modelalias at creation or from the defaults (model.default_alias,coordinator.model_alias,model.task_aliasfor sub-agents). The judge comes fromjudge.modelor a per-workstream override. “Local model for operations, frontier model for writing” is therefore a launcher or scheduled-task setting, not a persona property.
Tools: what is shown versus what may run
The docs say it plainly: persona visibility “is behavior shaping, not a security boundary”. Enforcement happens in later layers, and the order is fixed.
flowchart TD
A[Kind filter: interactive or coordinator tool set] --> B[MCP catalog merged if the persona allows MCP]
B --> C[Persona tool_allowlist and memory lever: what the model is shown]
C --> D{Does the call need approval?}
D -- no, e.g. read_file, search, memory --> R[runs]
D -- yes --> E[Tool policies: deny stops, allow auto-approves, ask continues]
E --> F[Per-workstream grants: auto_approve_tools, a skill's allowed_tools, Approve + Always]
F --> G[Blanket: auto_approve on the workstream or skip_permissions]
G --> H[Smart Approvals by the judge, if enabled]
H --> I[Human approval card]
I --> R
The pieces, in that order:
- Visibility (persona and kind) decides what can be called at all. A hard allowlist without
bashmeans the model cannot even ask for a shell. - Tool policies are admin rules of
tool_pattern(a glob such asbash*ormcp__gitea__*),action(allow,denyorask) andpriority. Deny short-circuits, allow auto-approves, ask is the normal flow. Policies are global by tool name: there is no per-persona, per-user or per-workstream scope, so a deny on the shell also binds the coding persona. They only touch calls that need approval; built-ins such asread_file,search,memoryandrecallnever enter the gate. If the policies cannot be read, every call needing approval is refused. - Per-workstream grants: an
auto_approve_toolslist on the workstream (also on scheduled tasks and channel adapters), a skill’sallowed_toolslist, and “Approve + Always” from the approval card. These apply only when every pending tool in a batch is covered. - Blanket approval:
auto_approveon the workstream or the global skip-permissions setting. - Smart Approvals, where the judge’s high-confidence approve verdicts stand in for the human. Off in this lab, on purpose, until the verdict table shows the local judge is calibrated.
- The human.
Underneath all of it sits RBAC, which gates people and API tokens, never the model’s choices: creating workstreams, approving calls, editing policies and personas each need their own permission.
A genuinely read-only profile is therefore two things: a persona with a hard allowlist of read tools (what the model sees) plus deny policies on the write tools (what can ever run), accepting that those policies are global.
Skills and prompt policies
A skill is admin-curated task guidance a workstream applies when it names one: one per workstream, loaded by name from the launcher, the API, a scheduled task or a channel adapter, replaceable at runtime. Since 1.7 the docs call skills “capability context, not identity”: the persona is who the agent is, the skill is what it knows for this task. A skill can bundle files and pre-approve tools through allowed_tools; it cannot restrict tools and it cannot choose a model. Skills that grant approvals are operator-load-only, so a model cannot activate one to approve its own calls.
Prompt policies are global blocks of text appended to every system prompt, optionally gated on a tool being visible, edited live. Personas are frozen per workstream; policies are not. Mandatory rules go in a policy, task identity goes in a persona.
Memory
Memory is typed and scoped (global, user, workstream, project, coordinator), not per persona. Two personas used by the same person see the same user, global and project memories; the persona lever only decides whether a session gets the index, pointers, nudges and the memory tool at all. Coordinator memory is isolated from interactive sessions, and sub-agents run without memory whatever their persona says.
Recipes
What the pieces add up to for a lab like this one:
- Operations observer. A persona with a hard allowlist of read tools (
read_file,search,web_fetch,recall,memory) plus the specific read-only MCP tools you trust, MCP on, memory on; and deny policies onbash, the file-writing tools andtask_agent. The shippedresearcheris the nearest relative, but its soft set lets the model escalate. - Coder. The stock
engineer, or a copy with the shell, file, search, sub-agent and memory tools andtool_search. Approvals stay per call; pre-approve the shell only through a skill’sallowed_toolsor anauto_approve_toolslist when the task warrants it. - Researcher without a shell. The shipped
researcherwithtool_searchremoved, which makes the set hard. Web search needs a configured backend, otherwise the tool is gated out of the prompt. - Scheduled jobs. The one surface where persona, skill, model and approvals are pinned together, which makes it the natural home for recurring agent work.
- Judge and utility models stay out of agents’ model lists through the hide-utility-aliases setting, on by default.
Notes
- The docs are explicit that the four levers are all a persona will ever be. Anything else (model, judge, sampling, per-server MCP choice, per-user defaults) is configured elsewhere, and asking for it per persona is a feature request, not a setting to find.
- Everything the model discovers through
tool_searchis per process: a restart narrows visibility back to the stamped allowlist, which the source calls the fail-safe direction. - Managing personas and policies needs the
persona.read,persona.writeandadmin.policiespermissions on the acting role. A service token with model-admin rights only cannot see them, which is how the lab discovered the permission model is finer than the three scopes suggest. - Facts here were checked against the v1.9.3 documentation and the admin API of the lab’s own console on 2026-11-08; the seed personas, field names and precedence rules are quoted from the project’s documentation.