CrewAICrewAI 1.15 · Python 3.10 to 3.13
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
32 small wins to finish your pathNext lesson

The desk: its state and its guardrail

The finished desk is a flow that routes each ticket to a crew, checks every reply, and holds refunds for a manager. It starts with what a ticket carries.

One file, desk.py, holds the whole desk. It opens with the imports and the state that travels from step to step.

Exampledesk.py
import re
from crewai import Agent, Crew, Task
from crewai.flow import Flow, HumanFeedbackPending, HumanFeedbackProvider, human_feedback
from crewai.flow.flow import listen, router, start
from crewai.flow.persistence import SQLiteFlowPersistence
from pydantic import BaseModel
from shop_llm import ShopLLM
from tools import lookup_order


class Ticket(BaseModel):
    message: str = ""
    order_id: str = ""
    reply: str = ""

Ticket is the flow's state: the customer's message, the order id once it is found, and the reply as it is written. Every step reads and writes those three fields.

Exampledesk.py, continued
def no_card_numbers(output):
    if re.search(r"\d{4} ?\d{4} ?\d{4} ?\d{4}", output.raw):
        return (False, "Remove the card number. Never repeat one to a customer.")
    return (True, output.raw)


class ManagerInbox(HumanFeedbackProvider):
    def request_feedback(self, context, flow):
        print("to the manager:", context.method_output)
        raise HumanFeedbackPending(context=context)

no_card_numbers is the guardrail a reply task runs through: it returns the text when it is safe, and a sentence for the agent to fix when it is not. ManagerInbox is what pauses a refund and shows it to a person.

Exampleguard_try.py
from desk import no_card_numbers


class Output:
    """A task result has a .raw, which is what a guardrail reads."""

    def __init__(self, raw):
        self.raw = raw

print(no_card_numbers(Output("Your refund goes back to card 4111 1111 1111 1111.")))
print(no_card_numbers(Output("Dear customer, order A17 shipped on 3 March.")))
Example
print(no_card_numbers(Output("Your refund goes back to card 4111 1111 1111 1111.")))
print(no_card_numbers(Output("Dear customer, order A17 shipped on 3 March.")))

A reply with a card number comes back refused, with the reason the agent will read. A clean reply comes back unchanged, and the task keeps it.

Try it yourself
  • Add a rule that refuses a reply containing an email address.
  • Return a different message and watch what the agent is told.
  • Print Ticket().model_dump() to see the fields a new ticket starts with.

Slow is fine. Stopping is the only problem.