Skip to content

43 · Reply safety

With replies: {rules: ...} set, every turn a person sends is screened for personal data before the router or the model reads it, and every reply the model writes is screened again before it is shown, whether or not an action came before it. Your rules say what each class of personal data gets, which phrases are never said, which offers a reply may make and which sentences must go with a write. No model judges any of it: the runtime on your machine decides each rule.

Language support

The selector reads turns written in English and in Brazilian Portuguese (pt-BR). The language is chosen, never guessed from the turn: the console's Settings page (Language the selector reads), language on a plan request, or the settings.language of your vocabulary file; with none of them, English. A Portuguese turn may be typed with its accents or without them. European Portuguese is not a grammar the selector has, and a turn in any other language is unread.

Personal data

The classes the runtime finds, how each is found, what it gets where your rules say nothing, and how it is masked, generated from the runtime's detectors:

Class How it is found Default Masked as
email an address of the form name@domain allow j***@example.com
iban an account number of the length its country gives, whose check digits hold redact GB** **** 2681
national_id a US social security number, a UK national insurance number, or a Brazilian CPF whose check digits hold redact ***6789
card 13 to 19 digits whose checksum (Luhn) holds: a number that fails it is left as it is redact **** 1111
phone an international number of 8 to 15 digits, or a national one of 10 or 11 allow ***0123

A card number is found by its checksum, not by its shape: sixteen digits that fail the Luhn check are left as they are, because they are not a card number. Each class takes one of three actions:

Action In a turn sent to the model In a reply the model wrote
redact masked before the router and the model read the turn masked in the sentence
withhold the whole turn is held back: nothing reads it, and the person is asked to send it again without it the sentence is removed
allow left as it is left as it is

A value an outcome sentence may repeat. A sentence reporting a write may name a value the write was sent -- the card a refund went to -- and that value is masked in it rather than removed, whatever the class's action, unless the class is allowed.

Wording

  • never_say: phrases never said. A sentence holding one, whole words and any case, is removed.
  • offers: the offers a reply may make. A sentence holding one of the markers (such as discount or voucher) and none of the allowed offers is removed.
  • must_accompany: sentences that must go with something: a disclaimer added when a tool's call ran in this turn (when_done), or when an answer cites a passage with a tag (when_cited). It is added once, where the reply does not already say it.

The reply rules file

The rules are one file, YAML or JSON, named in agent.yaml as replies: {rules: reply-rules.yaml} or in Python as Agent(..., replies={"rules": "reply-rules.yaml"}). The shop's:

# The shop's reply rules, as the console's Reply rules page saves them.
personal_data:
  card: redact            # the default, written out
  national_id: withhold   # a turn holding one reaches no model; a reply holding one loses the sentence
  email: allow
never_say:
  - "guaranteed refund"
must_accompany:
  - when_done: refund_card
    add: "Refunds reach the card in three to five working days."

Every key the runtime reads, generated from the runtime:

Key What it says
personal_data for each class of personal data, redact, withhold or allow
must_accompany sentences that must go with a write done or a passage cited
must_accompany[].add the sentence added
must_accompany[].when_done added when this tool's call ran in this turn
must_accompany[].when_cited added when an answer cites a passage with this tag
reply_exactly replies given word for word in place of any other reply, with no model and nothing added
reply_exactly[].when_cited given when an answer cites a passage with this tag
reply_exactly[].when_asked given when the turn says one of the topic's phrases: its words, else the topic's name with _ and - read as spaces
reply_exactly[].say the exact reply, with nothing added
reply_exactly[].words with when_asked: the phrases that ask about the topic, matched as whole words in any case, e.g. allergy, allergic, nut
never_say phrases never to say: a sentence holding one is removed
offers the offers a reply may make
offers.allowed the offers allowed
offers.markers the words that make a sentence an offer: one with a marker and no allowed offer is removed

The same rules are kept on the console's Reply rules page, as a form over the same file, and at /v1/reply-rules, in versions. A file the runtime cannot read -- a class it does not know, an action that is not one of the three -- is refused with the key that is wrong.

The shop's replies, screened

The shop runs on an Enterprise licence. Its orders hold the card on file and the customer, which is personal data, and a model reads personal data a tool returns on Enterprise only, with your own key; on another tier the reply says it was written without a model.

The model below stands in for yours, and records what it was given:

SEEN = []          # what the model was given, turn by turn

# Stands in for your model: what it is asked, and the reply it writes.
SCRIPT = {
    "zork": (None, ""),
    "my card is **** 1111, is it on file?": (
        None,
        "Your card 4111 1111 1111 1111 is on file; the card 4111 1111 1111 1112 is not."),
    "the parcel for 4412 vanished": (
        {"name": "refund_card", "arguments": {"order_id": "4412", "amount": 12.50}},
        "I've refunded 12.50 pounds for order 4412. It is a guaranteed refund."),
}


def model(conversation):
    turn = [m for m in conversation if m["role"] == "user"][-1]["content"]
    SEEN.append(turn)
    call, reply = SCRIPT[turn]
    if conversation[-1]["role"] == "user" and call:
        return {"content": "", "tool_calls": [call]}
    return reply
desk = Agent("desk", tools=[get_order, refund_card], model=model, on_unread="model",
             replies={"rules": "reply-rules.yaml"})

for text in ("zork",
             "my card is 4111 1111 1111 1111, is it on file?",
             "my social security number is 123-45-6789",
             "the parcel for 4412 vanished"):
    reply = desk.chat(text, user="u-17")
    if reply.waiting:
        reply = desk.confirm("u-17", "approve")
    print(f"{text}\n  -> {reply.text}")

print("The model was given:", SEEN)
zork
  -> I didn't recognise anything in that. I can get order or refund card. Which do you mean?
my card is 4111 1111 1111 1111, is it on file?
  -> Your card **** 1111 is on file; the card 4111 1111 1111 1112 is not.
my social security number is 123-45-6789
  -> That message held details I can't take here, so I haven't read it or done anything. Please send it again without them.
the parcel for 4412 vanished
  -> I've refunded 12.50 pounds for order 4412. Refunds reach the card in three to five working days.
The model was given: ['my card is **** 1111, is it on file?', 'the parcel for 4412 vanished', 'the parcel for 4412 vanished']
  • The card number the person typed was masked before anything read the turn: the model was given **** 1111.
  • The model's reply named the same valid card, masked, and a sixteen-digit number that fails the checksum, left as it is.
  • The turn with a social security number was held back whole, under national_id: withhold: nothing read it, and the person was asked to send it again without it. It is not in what the model was given.
  • After the refund ran, It is a guaranteed refund. was removed (never_say), and the disclaimer the rules require for a refund was added.

The counts

Each check is counted, by the meter it is recorded under, with your licence's other meters. A count is a number: it carries no text, no value and no turn.

Count What it counts
reply_temperature_kept a varied wording that read back with another mood or other locked words, so the template's own sentence was sent
reply_temperature_varied a template sentence worded another way, with reply temperature on
reply_exact_reply a reply given word for word in place of any other, by reply_exactly
reply_pd_detected_<class> a value of that class of personal data found
reply_pd_masked a value a write was sent, repeated in its own outcome sentence, masked
reply_pd_withheld a turn held back, or a sentence removed, for a value the rules withhold
reply_pd_redacted a value masked because the rules redact its class
reply_assertion_refused a sentence saying a write was done that nothing in the session attempted, replaced
reply_outcome_replaced a sentence its action's outcome does not allow, replaced by the phrasing
reply_value_highlighted a sentence in staff work stating a value no record holds, kept and highlighted
reply_value_replaced a sentence stating a value its action was neither sent nor returned, replaced
reply_outcome_added a write of this turn the reply left out, added from its phrasing
reply_wording_removed a sentence holding a phrase never to say, removed
reply_offer_removed a sentence making an offer outside the allowed ones, removed
reply_wording_added a sentence the rules require, added
reply_facts_untraced a value the reply states that no call's record holds
reply_facts_traced a value the reply states, traced to the call and the field it came from
reply_facts_conflict a traced value another source this turn gives differently, for the same thing
reply_facts_stale a traced value read longer ago than its source says a result holds
reply_facts_out_of_range a traced value outside the range its tool's output schema allows

The limits

  • A detector finds the classes it names, and no others. A name, a postal address, a date of birth or an identifier of another country is not detected, and is left as it is.
  • A detector finds a value written as the detector reads it. A card number spelt out in words, or split across two messages, is not found.
  • No model judges a reply. A rule matches the words it is given: a phrase said in other words is not a phrase never_say lists.
  • The screen holds the turns the agent reads and the replies its model writes. A reply no model wrote -- a fixed question, a refusal, a phrasing -- is the product's own wording.