Rule relationships
Declaring how rules affect one another
A relationship is a declared link between two of your agent's rules, so that one rule's status can change what another does. One rule can wait until another has applied. One rule can bring another with it. One rule can win when two disagree. And a rule can be judged again after a tool runs. You use relationships when a rule's own wording is not enough.
- The four relationships and the order Parlant applies them in
- How a dependency keeps a rule out until another has matched
- How entailment brings a general rule along with a specific one
- How pairwise and numeric priority decide which rule the agent follows
Every rule you write has a condition, and that condition decides, on its own wording, whether the rule applies. Most of the time, that is all you need.
Sometimes, though, it is not enough. One rule should only count once another has come up. A general rule should ride along with a specific one. Or two rules both apply, and your agent must not follow both.
Parlant supports four relationships:
| Relationship | Declared with | What it does |
|---|---|---|
| Dependency | depend_on, depend_on_any | Keeps a rule out until another rule has matched. |
| Entailment | entail | Activates a second rule whenever the first is active. |
| Priority | prioritize_over, priority= | Decides which of two matched rules the agent follows this turn. |
| Reevaluation | reevaluate_after | Judges a rule again right after a tool runs. |
The first three connect rules to other rules. Parlant settles them together during resolution (the last step of rule selection on every turn). The fourth connects a rule to a tool, and it acts later, while your agent is calling tools.
The rest of this page walks through each one, and then how they combine.
The order relationships are applied in
Parlant applies the relationships in a fixed order, and that order is what makes their combinations predictable.
Resolution runs after Parlant has judged which rules match this turn, and it always runs in the same order. Each step sees the output of the one before it.
Entailment runs first. This means an entailed rule is still subject to the dependency gate, and it can also satisfy other rules' dependencies.
Priority runs last. This means a rule that loses on priority has already counted as matched for every other purpose.
Dependency: a rule waits for another
A dependency makes one rule conditional on another. Parlant only considers the dependent rule once its prerequisite has matched, however applicable the dependent rule's own wording looks.
offer_discount = await agent.create_policy(
content="When the customer hesitates about the price, offer the seasonal discount",
)
apply_discount = await agent.create_policy(
content="When the customer accepts the discount, apply it to the order",
)
await apply_discount.depend_on(offer_discount)
Consider what could happen without the dependency here. A customer merely mentions a discount your agent never offered, and "the customer accepts the discount" matches anyway. The dependency keeps that from happening.
What counts as a match
Since a dependency waits for its prerequisite to match, it is worth being precise about what Parlant counts as one.
- A match on the same turn. Chains resolve in order within one turn. So if
offer_discountmatches now,apply_discountcan match now as well. - A match earlier in the session. Parlant keeps a ledger (a record of every rule that has matched at least once in the session). Once a prerequisite is on it, its dependents stay available for the rest of the conversation, even if Parlant has since pruned the prerequisite from the working set.
- Not discovery. A prerequisite that was merely discovered as relevant, without a match verdict, satisfies nothing.
Why doesn't discovery count? Because discovery is deliberately generous. If its false positives could unlock dependents, the dependency would be decorative.
Until the prerequisite has matched, the dependent rule stays out entirely, including out of the working set. Parlant judges it fresh each turn, so it enters the moment its prerequisite matches.
Depending on several rules
A rule can depend on more than one target. depend_on(a, b) requires all of them, while depend_on_any(a, b) requires at least one.
A group can also stand in for a set of rules. A bare group or p.AllOf(group) requires every member, and p.AnyOf(group) requires just one.
You can also declare dependencies at creation time, using the dependencies= parameter.
The most common prerequisite is an observation, a rule that recognizes a situation without instructing the agent. This lets a whole family of policies hang off one precise judgment.
Entailment: one rule brings another with it
Entailment works in the opposite direction from a dependency. It says that whenever one rule is active, a second one is too. The entailed rule no longer needs to be judged applicable by its own wording.
suggest = await agent.create_policy(
content="When the customer asks for topping recommendations, suggest toppings",
)
check_stock = await agent.create_policy(
content="When suggesting products, only suggest what is in stock",
)
await suggest.entail(check_stock)
Entailment closes a discovery gap. A general policy like check_stock uses category words such as "products", but a conversation about toppings never uses them. So the general policy can miss exactly the turns it was written for.
The link between toppings and products is a fact of your business, not of either policy's text. That is why adding more signals does not fix it, and a declaration does.
How an entailed rule behaves
- It takes the same status as its strongest entailer. A turn match begets a turn match, and mere admission to the working set begets admission.
- Entailment chains. If A entails B and B entails C, activating A activates C. A group as the target activates all of its members.
- It is still dependency-gated, and it can satisfy the dependencies of other rules on the same turn.
- It is exempt from numeric priority arbitration, so it can never suppress the rule that activated it.
Note that each entailment activates its target unconditionally, which costs attention and makes your agent more rigid. We recommend adding entailments only after real conversations show the general rule missing turns where it clearly applied.
Priority between two matched rules
Priority handles turns where two rules both apply and give conflicting instructions. Your agent follows the prioritized rule, and Parlant tells it to set the other aside.
freeze = await agent.create_policy(
title="Freeze on suspected fraud",
content="When fraud is suspected on the account, freeze all transactions and say why",
)
refunds = await agent.create_policy(
title="Process refunds",
content="When the customer requests a refund on a delivered order, process it",
)
await freeze.prioritize_over(refunds)
How priority resolves
- Only among this turn's matches. Parlant drops a rule when a direct priority relationship targets it and the source also matched this turn. If the source did not match, the target is unaffected.
- Direct links only. Priority does not propagate through a rule that is not itself active. If A outranks B and B outranks C, but B did not match, A does nothing to C.
- Chains still collapse in one pass. Parlant judges sources against the full set of matches before any drops. So a rule that loses to one rule still suppresses the rules it outranks.
- The loser is set aside, not forgotten. It stays in the working set, and it still counts as matched for dependencies.
That last point deserves a closer look. "Set aside" is literal: the losing rule's text stays in the agent's instructions. Parlant adds a note naming it and saying it is revoked for this response, even when the rule is worded absolutely ("always", "in all circumstances").
Removing it from the directives alone would not be enough, because the model could still follow its standing text.
The target of prioritize_over can also be a group, in which case the source outranks every member.
Before reaching for priority, check whether the two rules are just too broad. A collision is often the "comes up too much" problem in disguise, and narrowing both conditions is the better fix. When two rules are different ways to handle a similar request, write a clarifying rule that owns the unclear moment instead.
Numeric priority across the catalog
Pairwise priority works well for a specific pair of rules. But it scales badly when a whole tier of rules should beat another tier.
For that, every rule carries a numeric priority, which is zero by default:
await agent.create_policy(
title="Account under legal hold",
content="When the account is under legal hold, take no action and explain that it is frozen",
priority=10,
)
After Parlant has applied relational priority (your prioritize_over links), only the matches sharing the highest number stay matched for the turn. Parlant demotes the others to the working set, exactly like the loser of a prioritize_over, and lists them in the same do-not-act note. This includes rules that have nothing to do with the situation the high-priority rule describes.
That note also names every rendered rule with a lower number. This includes rules Parlant admitted on discovery alone, since a number is declared data and needs no verdict.
Note that entailed matches are exempt. They owe their activation to their entailer, so they never raise the bar against it.
Use numbers for broad tiers, such as safety and legal rules over ordinary procedure, and prioritize_over for a specific pair.
Reevaluation after a tool runs
The three relationships above all connect rules to rules. The last one is different: it connects a rule to a tool.
Reevaluation handles conditions that cannot be judged from the conversation alone. Consider "When a luxury room is not available, offer the standard room." Whether it applies depends on what an availability check returns, so judging it before the tool runs would be a guess.
await offer_alternative.reevaluate_after(check_availability) # a @p.tool
After check_availability runs, Parlant judges offer_alternative again, in the same turn, with the tool's result in view. A rule can match for the first time this way, mid-turn, and its tools and settings then apply for the rest of the turn.
Calling reevaluate_after on a group reevaluates every member when any of the tools runs.
Note that reevaluation is a rule-to-tool link rather than a rule-to-rule one, so it takes no part in resolution. It also needs an individual model verdict for the rule, so it only runs at effort levels where Parlant would check the rule individually.
See Adaptive effort for which effort levels give a rule an individual verdict.
How the relationships combine
The order above settles most questions about mixing relationships. Here are the cases that come up in practice:
- An entailed rule with an unmet dependency stays out. Entailment adds it, then the dependency gate drops it.
- An entailed rule can unlock dependents. It counts as matched when the dependency gate runs, so a rule depending on it is admitted on the same turn.
- A rule that loses on priority still satisfies dependencies. The ledger records matches before priority is applied, and the loser stays in the working set.
- Losing on priority doesn't cascade. Rules depending on the loser are not dropped with it.
- Entailment doesn't carry priority. An entailed rule has its own
prioritynumber and its ownprioritize_overlinks. Its only special treatment is the exemption from numeric arbitration against its entailer. - Pruning the working set never breaks a dependency. Parlant never erases the ledger, so a prerequisite matched early in a long session keeps its dependents unlocked after it has been evicted.
Relationships and effort
Relationships change how much attention a rule gets.
Any rule that is a dependency target, or that sits on either side of a priority relationship, always receives an individual model verdict, whatever the turn's effort level. This is because something mechanical depends on that verdict being right.
Note that depending on another rule does not, by itself, earn a rule an individual verdict.
The full matrix of which rules get an individual verdict at each effort level is under Adaptive effort.
Where to go next
- Designing policies covers when each relationship is the right fix for a policy that misbehaves.
- Scaling complexity shows observations anchoring families of dependent policies.
- When a rule matched but was outranked, or never came up because its prerequisite had not matched, the turn's diagnostic report says so. Debugging shows where to look.