app/agents.rs
1//! Agent specs and prompt packet rules.
2//!
3//! # Operator framing
4//!
5//! Use this page when you want to understand what an AI workflow is allowed to
6//! see, draft, or summarize before a person reviews it. It is for operators,
7//! managers, and implementers checking the safety gate between helpful
8//! automation and live resort actions.
9//!
10//! The next step is to read `baseline_agent_specs` for the built-in workflow
11//! list, then open `AgentPromptPacket` to see the evidence bundle each run
12//! receives. The Rust API details below remain the implementer rules; the
13//! framing here explains how to interpret those rules operationally.
14//!
15//! This module is the app-layer rules for safe workflow automation. Agent
16//! specs describe narrow read/draft capabilities for resort workflows, and
17//! prompt packets carry the source event, typed workflow input, policy language,
18//! and expected output schema an agent may use to prepare a draft or evidence
19//! bundle. They are not live authority to mutate bookings, customer messages,
20//! schedules, deposits, incident records, or policy decisions.
21//!
22//! Specs make the app gate visible to the runtime: allowed tools are narrow
23//! read/draft surfaces, forbidden actions name unsafe authority, and default
24//! review gates remain deterministic app policy.
25//!
26//! ```
27//! use app::agents;
28//!
29//! let specs = agents::baseline_agent_specs();
30//! let manager_brief = specs
31//! .iter()
32//! .find(|spec| spec.name.clone().into_inner() == "manager-daily-brief")
33//! .expect("baseline manager brief spec exists");
34//!
35//! assert!(manager_brief
36//! .allowed_tools
37//! .iter()
38//! .any(|tool| tool.clone().into_inner() == "reservation-read"));
39//! assert!(manager_brief
40//! .forbidden_actions
41//! .iter()
42//! .any(|action| action.clone().into_inner() == "change schedule"));
43//! assert!(manager_brief
44//! .forbidden_actions
45//! .iter()
46//! .any(|action| action.clone().into_inner() == "send customer message without approval"));
47//! assert!(!manager_brief.default_review_gates.is_empty());
48//! ```
49use bon::Builder;
50use serde::{Deserialize, Serialize};
51
52use domain::{agent, policy, workflow};
53
54pub use domain::agent::{OutputSchemaName, PolicyInstruction};
55
56/// App-facing alias for the domain agent specification used by workflow automation.
57///
58/// A spec is the stable rules an agent runner receives before it builds a
59/// prompt packet: the workflow identity, business purpose, read/draft tools it
60/// may use, actions it must never take directly, and deterministic review gates
61/// that keep resort staff in control of bookings, messages, schedules, and
62/// safety-sensitive decisions.
63pub type AgentSpec = agent::Spec;
64
65/// Rules implemented by app workflow agents that prepare safe prompt packets.
66///
67/// Implementors expose their immutable [`AgentSpec`], package an event and typed
68/// input into an [`AgentPromptPacket`], then validate model/tool output before it
69/// is accepted as a draft or evidence bundle. The trait is intentionally about
70/// preparing and checking workflow artifacts; it does not grant authority to
71/// write back to Gingr, send pet-parent messages, change labor schedules, or
72/// mutate reservations.
73pub trait WorkflowAgent<Input, Output> {
74 /// Returns the agent's operational specification.
75 ///
76 /// The spec names the workflow, states its labor/customer-service purpose,
77 /// lists only the read or draft tools the runner may expose, and records the
78 /// review gates that must remain outside model control.
79 fn spec(&self) -> AgentSpec;
80 /// Builds the prompt packet for one workflow event and typed input payload.
81 ///
82 /// The returned packet should contain source event context, workflow input,
83 /// policy instructions, and output schema expectations sufficient for an
84 /// agent to draft or summarize evidence without taking live operational
85 /// action.
86 fn build_prompt_packet(
87 &self,
88 event: &workflow::Event,
89 input: Input,
90 ) -> AgentPromptPacket<Input>;
91 /// Validates a proposed workflow output before downstream app code can use it.
92 ///
93 /// Implementations should preserve deterministic policy failures and reject
94 /// unsafe output rather than treating agent text as authority to mutate
95 /// bookings, messages, schedules, incident records, or customer commitments.
96 fn validate_output(&self, output: workflow::Result<Output>) -> workflow::Result<Output>;
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
100/// Safe prompt-and-evidence packet exchanged with an automation agent.
101///
102/// Operator framing: this is the checklist packet an agent receives for one
103/// reviewable workflow run. It matters to managers because every suggested
104/// action, brief, classification, or customer-message draft should trace back to
105/// the workflow name, source event, approved input facts, policy instructions,
106/// and expected output shape recorded here.
107///
108/// Next step: compare the packet fields with the workflow's `AgentSpec` and
109/// review gates before treating any generated output as usable evidence. The
110/// field-level Rustdoc below documents the API shape; it does not grant live
111/// authority to book, charge, message, schedule, or override policy.
112///
113/// An agent prompt packet bundles the workflow identity, triggering source event,
114/// typed app input, policy instructions, and expected output schema sent to an
115/// agent runner. It is a draft/evidence gate: agents can use it to prepare a
116/// briefing, follow-up draft, classification, or manager-review packet, but the
117/// packet itself is not permission to mutate live resort systems.
118pub struct AgentPromptPacket<T> {
119 /// Workflow identifier that ties the packet to a specific agent spec.
120 ///
121 /// Examples include `manager-daily-brief`, `booking-triage`, and
122 /// `grooming-rebooking`; the name lets audits connect a packet back to the
123 /// workflow rules that defined its allowed tools and forbidden actions.
124 pub workflow_name: agent::Name,
125 /// Business goal the agent should pursue while preparing only draft output.
126 ///
127 /// This describes the labor, customer-service, safety, or data-quality
128 /// outcome for the workflow, such as summarizing labor risk or drafting a
129 /// customer follow-up, without granting authority to perform the action.
130 pub goal: agent::Purpose,
131 /// Source workflow event that caused the packet to be built.
132 ///
133 /// The event provides audit correlation for the triggering reservation,
134 /// intake, document, review, incident, or scheduled briefing path so a human
135 /// reviewer can trace why this packet exists.
136 pub event: workflow::Event,
137 /// Typed app-layer input facts available to the agent for this run.
138 ///
139 /// The payload should contain the workflow-specific request or evidence the
140 /// app has already promoted from source systems; it is context for drafting,
141 /// not authorization to repair or overwrite those source records.
142 pub input: T,
143 /// Policy instructions the runner must include in the agent context.
144 ///
145 /// These instructions state review gates, safety limits, escalation rules,
146 /// and source-grounding requirements that constrain agent drafts and make
147 /// policy compliance reviewable after the run.
148 pub policies: Vec<agent::PolicyInstruction>,
149 /// Name of the output schema expected from the agent.
150 ///
151 /// The schema name tells the runner and validator which structured draft,
152 /// classification, briefing, or evidence bundle shape to expect before any
153 /// downstream workflow code accepts the output.
154 pub output_schema_name: agent::OutputSchemaName,
155}
156
157/// Returns the baseline app agent specifications for pet-resort automation.
158///
159/// The list covers bounded workflows such as inquiry intake, booking triage,
160/// vaccine document review, manager briefings, lead conversion, grooming
161/// rebooking, reputation triage, and SOP assistance. Each spec deliberately
162/// exposes read/draft tools and review gates while forbidding direct actions
163/// such as confirming bookings, changing schedules, waiving deposits, diagnosing
164/// pets, or sending customer messages without approval.
165pub fn baseline_agent_specs() -> Vec<AgentSpec> {
166 vec![
167 spec(
168 "inquiry-intake",
169 "Extract new customer/pet/service/date details, identify missing info, and draft safe follow-up replies.",
170 ["portal-read", "crm-read", "task-create"],
171 ["confirm booking", "send sensitive message without approval"],
172 [policy::ReviewGate::CustomerMessageApproval],
173 ),
174 spec(
175 "booking-triage",
176 "Evaluate booking requests against deterministic availability, eligibility, vaccine, deposit, and policy context.",
177 ["availability-read", "policy-read", "draft-message"],
178 [
179 "invent availability",
180 "override hard policy",
181 "waive deposit",
182 ],
183 [policy::ReviewGate::ManagerApproval],
184 ),
185 spec(
186 "vaccine-document",
187 "Extract vaccine names/dates from uploaded proof and route ambiguity to human review.",
188 ["document-read", "ocr", "vaccine-policy-read"],
189 ["final approve uncertain medical document"],
190 [policy::ReviewGate::MedicalDocumentReview],
191 ),
192 spec(
193 "daily-care-update",
194 "Turn staff notes/photos into warm customer-safe update drafts with risk flags.",
195 ["care-note-read", "draft-message"],
196 [
197 "diagnose",
198 "hide concerning facts",
199 "auto-send health concern",
200 ],
201 [policy::ReviewGate::CustomerMessageApproval],
202 ),
203 spec(
204 "incident-escalation",
205 "Summarize incident facts, classify possible severity, identify missing fields, and draft manager/owner review packets.",
206 ["incident-read", "task-create", "draft-message"],
207 [
208 "close incident",
209 "diagnose",
210 "send owner message without manager approval",
211 ],
212 [
213 policy::ReviewGate::ManagerApproval,
214 policy::ReviewGate::CustomerMessageApproval,
215 ],
216 ),
217 spec(
218 "manager-daily-brief",
219 "Summarize occupancy, arrivals, labor risk, pet-care watchlist, customer follow-ups, and revenue opportunities for resort leaders.",
220 [
221 "reservation-read",
222 "labor-schedule-read",
223 "care-note-read",
224 "task-create",
225 ],
226 [
227 "invent occupancy",
228 "change schedule",
229 "send customer message without approval",
230 ],
231 [policy::ReviewGate::ManagerApproval],
232 ),
233 spec(
234 "lead-conversion",
235 "Classify inquiry intent, identify missing intake requirements, and draft next-best follow-up for boarding, daycare, grooming, or training leads.",
236 ["lead-read", "customer-read", "portal-read", "draft-message"],
237 [
238 "book reservation",
239 "promise availability",
240 "override requirements",
241 ],
242 [policy::ReviewGate::CustomerMessageApproval],
243 ),
244 spec(
245 "grooming-rebooking",
246 "Find grooming cadence opportunities, low-utilization slots, and safe customer follow-up drafts without changing calendars automatically.",
247 [
248 "grooming-history-read",
249 "availability-read",
250 "draft-message",
251 ],
252 [
253 "book grooming slot",
254 "apply discount",
255 "send message without approval",
256 ],
257 [policy::ReviewGate::CustomerMessageApproval],
258 ),
259 spec(
260 "reputation-triage",
261 "Classify review themes, identify safety/legal escalations, summarize location trends, and draft public-response packets.",
262 ["review-read", "task-create", "draft-message"],
263 [
264 "delete review",
265 "deny incident facts",
266 "publish response without approval",
267 ],
268 [
269 policy::ReviewGate::ManagerApproval,
270 policy::ReviewGate::CustomerMessageApproval,
271 ],
272 ),
273 spec(
274 "sop-policy-assistant",
275 "Answer staff policy questions from approved SOP context and route medical, refund, safety, or incident ambiguity to human review.",
276 ["policy-read", "sop-read", "task-create"],
277 ["diagnose", "approve refund", "override safety policy"],
278 [policy::ReviewGate::ManagerApproval],
279 ),
280 ]
281}
282
283fn spec<const TOOLS: usize, const FORBIDDEN: usize, const GATES: usize>(
284 name: &str,
285 purpose: &str,
286 allowed_tools: [&str; TOOLS],
287 forbidden_actions: [&str; FORBIDDEN],
288 default_review_gates: [policy::ReviewGate; GATES],
289) -> AgentSpec {
290 AgentSpec::builder()
291 .name(agent::Name::try_new(name).expect("baseline agent names are non-empty"))
292 .purpose(agent::Purpose::try_new(purpose).expect("baseline purposes are non-empty"))
293 .allowed_tools(
294 allowed_tools
295 .into_iter()
296 .map(|tool| {
297 agent::ToolName::try_new(tool).expect("baseline tool names are non-empty")
298 })
299 .collect(),
300 )
301 .forbidden_actions(
302 forbidden_actions
303 .into_iter()
304 .map(|action| {
305 agent::ForbiddenAction::try_new(action)
306 .expect("baseline forbidden actions are non-empty")
307 })
308 .collect(),
309 )
310 .default_review_gates(default_review_gates.into_iter().collect())
311 .build()
312}