A second persona & scoping
Some of The Scoop's work isn't customer-facing: reordering stock, chasing suppliers, internal reports. Running that under Sundae's public support identity would be wrong — different audience, different powers, different trust boundary. So we hire a second agent: Scout, on the internal ops desk.
This chapter introduces a new role (the class), a new agent (the record), and the two ways Protege bounds what an agent can do.
A new role and agent
Same split as before — the class is the role, the record is the agent. Scaffold the Operations role:
bin/rails g protege:persona operations# app/personas/operations_persona.rb
class OperationsPersona < Protege::Persona
self.display_name = "Operations"
# Scout's grant — deliberately narrower than Customer Service (see below).
tools :check_flavor_stock, :create_order, :send_email
message_resolvers do |chain|
chain.use(Protege::LoadTextResolver, role: :system) { |ctx| ctx.persona.instructions }
chain.use Protege::ThreadHistoryResolver
end
responsibility_resolvers do |chain|
chain.use(Protege::LoadTextResolver, role: :system) { |ctx| ctx.persona.instructions }
chain.use(Protege::LoadTextResolver, role: :user) { |ctx| ctx.responsibility.instructions }
end
endCreate Scout as a record on The Scoop's already-registered domain (Sundae is a CustomerServicePersona; Scout is an OperationsPersona — same domain, different role):
OperationsPersona.create!(
name: "Scout",
email_address: "ops@thescoop.com",
instructions: "You are Scout, The Scoop's internal ops agent. Keep the shop stocked and " \
"flag anything that needs a human."
)Because email_address is unique across all personas, Scout is a singleton on ops@thescoop.com — one agent per inbox.
Scoping tools: the grant
Notice the tools line in OperationsPersona. By default a persona may use every registered tool — that's what Sundae does. Scout shouldn't; an ops agent has no business issuing customer refunds. The tools macro declares a grant — the code-side ceiling of what the role may use:
tools :check_flavor_stock, :create_order, :send_emailScout can check stock, create orders, and send mail — and nothing else. ProcessRefund and LookupOrder aren't in her grant, so they're never even offered to her model.
Two things about the grant worth remembering:
- It inherits down the STI tree. Unlike the resolver chains (which are per-class), a base role's grant is inherited by subclasses — so you can set a family-wide ceiling and let subclasses tighten it.
- Omitting it means "all tools." That's why Sundae, with no grant, has the full catalog.
An operator can narrow the grant further at runtime by disabling specific tools on the agent's record in the dashboard — but they can never widen past the grant. See Personas.
Scoping senders: access rules
Sundae should hear from anyone — she's support. Scout should only be reachable from inside the company. That's the inbound access guardrail, which is built from two layers that only ever narrow:
- The global ceiling (
config.inbound_access) — the org-wide boundary. - Per-persona rules — editable on each agent's record in the dashboard.
Leave the ceiling permissive (so customers reach Sundae), and add an allow-rule on Scout for *@thescoop.com:
# config/initializers/protege.rb — a permissive ceiling; customers must reach Sundae
config.inbound_access = Protege::Gateway.build_access_policy # permit-allThen on Scout's record, in the dashboard, add an allow access rule with pattern *@thescoop.com. That single rule flips Scout into allow-list mode: only company senders get through, everyone else is bounced with an access-denied notice — while Sundae, with no rules of her own, stays open to the public. An external sender can never reach Scout, no matter what they put in From.
Next
Scout exists and is locked down — but so far she only acts when emailed. Her real value is proactive work.
→ Scheduled work — give Scout a standing duty that runs on its own.

