Skip to main content

Guidelines

LLM applications struggle with a fundamental tension: provide the agent with too little context, and it becomes ungrounded and unpredictable; provide too much, and you ironically trigger the same issues.

Parlant is built to solve this problem via dynamic context delivery. Instead of one large, static prompt, Parlant lets you dynamically assemble the "perfect prompt" for every single turn of the interaction. This ensures the context is always "just right"—grounded by relevant rules but never diluted by irrelevant noise or anchored by outdated system-level biases.

Understanding the Attention Problem

Even for the most advanced models, performance degrades sharply as instructions and context accumulate. Research shows that for 10 objectively verifiable instructions, the success rate of models like GPT-4o can be as low as 15% (see Curse of Instructions). Attention Dilution shows how accuracy drops by up to 85% when prompts are cluttered with irrelevant information, and the Lost in the Middle effect, where models can actually perform worse than if they had no context at all (falling below "closed-book" performance) when critical details are buried in the middle of a long prompt.

Furthermore, static system prompts often trigger unintended scope creep and conversational drift. Research into system prompts as a Mechanism of Bias shows that instructions fixed at the start of a session make models hyper-attentive to contextually irrelevant information. This "over-weighting" of initial instructions causes the model to anchor on outdated or irrelevant directives, leading the conversation astray even when the user explicitly tries to redirect it.

The Core Primitive: Observations​

The most fundamental building block in Parlant is the Observation. An observation is triggered by a natural language Condition—a specific state or intent detected in the conversation—or custom code matchers.

Observations function as the "sensors" of your agent's context delivery system. They don't dictate behavior on their own; they simply monitor the conversation and fire when their condition is met, allowing you to get specific information into the agent's context, or keep it out when it isn't relevant.

They are the basic building blocks that let you control exactly what information enters the agent's context and when.

# NLP-based: the engine evaluates this condition against the conversation
customer_wants_deals = await agent.create_observation(
condition="Customer expresses interest in special deals or discounts"
)
# Code-based: use a custom matcher function for deterministic control
business_hours = await agent.create_observation(
matcher=check_business_hours,
)

Observations become powerful when combined with relationships. You can use them to gate other guidelines via dependencies, or to exclude guidelines that shouldn't fire in certain contexts even if their own conditions match:

# Other guidelines can depend on this observation
await offer_deals_guideline.depend_on(customer_wants_deals)

# The observation will explicitly disqualify this other guideline
# from matching whenever the observation itself matches
await customer_wants_deals.exclude(offer_premium_products_guideline)

Observations let you control what enters the agent's context: guidelines, retrievers, journeys, or tools. You can also run custom code when they fire through match handlers.

tip

By separating detection from response, you can build complex logic where one observation triggers tools, excludes other rules, or serves as a dependency for higher-level behavior.

Guidelines: Observations with Actions​

An observation only detects, whereas a guideline acts. A guideline is an observation with an action attached—and, optionally, a description that elaborates on the action's meaning when it's needed.

await agent.create_guideline(
condition="The customer wants to buy a laptop",
action="First, determine whether they prefer Mac or Windows",
# Optional description for when the action needs more context
description="Mac vs. Windows customers tend to have very different profiles "
"and are interested in completely different sets of additional products. "
"Narrowing this down first helps us understand the customer's profile "
"right from the start.",
)

The condition specifies when the guideline should apply. The engine evaluates it against the current conversation context, looking at the entire conversation, to determine relevance. You can also replace it with a custom matcher.

The action specifies what the agent should do. It gets injected into the agent's context when the condition matches. The engine also uses the action to track whether the guideline was already applied, deactivating it when it's no longer needed, to prevent unnecessary context pollution throughout the conversation.

Keep in Mind

Guidelines are evaluated before the agent composes its response. This means guidelines like "Do X immediately after you've done Y" may not work as expected—the agent decides what to do based on the conversation state at the time of evaluation.

Guidelines vs. Journeys

Use guidelines for context-specific behavioral rules and tool-calling triggers. Use journeys for multi-turn processes that require a structured flow with states and transitions.

Guideline Durations​

Correctly delivering a guideline into the agent's context requires tracking. How you phrase a guideline determines how the engine tracks and applies it.

There are three effective guideline durations, and the engine automatically infers the right one from your wording:

One-Off Actions​

"When X, do Y."

The agent applies the action once and considers it done — even if the customer doesn't respond to it. Once completed, the guideline deactivates for the rest of the interaction (unless the context shifts enough to re-trigger it, e.g., the customer starts a new order).

await agent.create_guideline(
condition="The customer orders a dish containing a known allergen",
action="Warn them that the dish contains that allergen",
)

The agent warns the customer once. If the customer ignores the warning and continues, the agent moves on.

Persistent Actions​

"When X, do Y until W happens."

The agent keeps pursuing the action across multiple turns until a resolution is reached. The guideline stays active until the customer explicitly addresses it.

await agent.create_guideline(
condition="The customer orders a dish containing a known allergen",
action="Confirm with them that they're okay with the allergen before proceeding",
)

Unlike the one-off version, the agent won't consider this done until the customer has actually confirmed or changed their order. If the customer ignores the question and asks about something else, the agent will circle back.

Continuous Actions​

"As long as X, do Y," or "When X, always do Y."

The action applies throughout the entire interaction for as long as the condition holds. These are ongoing behavioral constraints rather than tasks to complete.

await agent.create_guideline(
condition="The customer has indicated they are vegetarian",
action="Only ever suggest vegetarian dishes",
)
await agent.create_guideline(
condition="The customer hasn't completed payment yet",
action="See if there's anything else they want to add to their order",
)

Continuous guidelines stay active as long as their condition remains true. The moment the condition no longer holds (e.g., payment is completed), the guideline deactivates.

Writing Effective Guidelines​

Think of the LLM as a knowledgeable stranger who's eager to help but knows nothing about your specific business context. Guidelines channel that broad capability into focused, appropriate behavior. Getting them right is a bit of an art—just like giving clear instructions to people.

Be Specific, Not Vague​

DON'T
  • Condition: Customer is unhappy
  • Action: Make them feel better

The LLM might interpret both the condition and the action in countless ways—offering unauthorized discounts, making jokes that don't fit your brand, or giving empty platitudes.

DO
  • Condition: Customer expresses dissatisfaction with our service
  • Action: Acknowledge their frustration specifically and ask for details about their experience so we can address it properly.

This is both specific and bounded. The agent knows what to acknowledge and how to respond.

DON'T
  • Condition: Customer asks about products
  • Action: Recommend something they might like
DO
  • Condition: Customer asks for product recommendations without specifying preferences
  • Action: Ask about their specific needs, previous experience with similar products, and any particular features they're looking for before making recommendations

Find the Right Balance​

Guidelines that are too vague leave the agent guessing, but ones that are too rigid make the agent robotic.

Aim for the sweet spot and iterate based on the results you see in real conversations:

DON'T

Too vague:

  • Condition: Customer has a technical problem
  • Action: Help them fix it
DON'T

Too rigid:

  • Condition: Customer reports an error message
  • Action: First ask for their operating system version, then their browser version, then their last system update date
DO

Just right:

  • Condition: Customer reports difficulty accessing our platform
  • Action: Acknowledge their access issue and help them troubleshoot it
  • Description: Express understanding of their situation. Ask for key details about their setup (OS and browser) and check if they've tried basic troubleshooting steps like clearing cache or using a different browser.

Note here that you can also leverage the description field to provide additional context, while keeping the action concise.

Watch for Edge Cases​

LLMs take guidance literally. If you tell your agent to "always suggest premium features," it might do so even when talking to a customer who's complaining about pricing. Consider the broader context and potential edge cases when formulating guidelines—it pays off in fewer fixes down the line.

When in doubt, err on the side of vagueness over specificity. The goal isn't to script every interaction, but to provide clear, contextual guidance that shapes the agent's natural generalization abilities into reliable responses.

Keep Actions Short, Use Descriptions to Elaborate​

Write your action as a brief instruction—as if speaking to someone who already understands the context. Then use the description field to provide the detail behind it.

This isn't just a style preference. Parlant's engine uses the guideline's condition and action text in its ARQ-based compliance checks, where they appear in completion tokens. Longer conditions and actions directly increase response latency. The description, by contrast, is only loaded into context when the guideline matches—it doesn't affect the matching itself.

DON'T
await agent.create_guideline(
condition="The customer mentions a competitor product",
action="Acknowledge their experience with the competitor respectfully, "
"never disparage them directly, and highlight our key differentiators "
"such as 24/7 support, flexible pricing, and our satisfaction guarantee, "
"while focusing on what makes us uniquely valuable for their use case",
)
DO
await agent.create_guideline(
condition="The customer mentions a competitor product",
action="Acknowledge their experience and highlight our differentiators",
description="Be respectful of competitor products (BrandX, ABC Corp, etc.). "
"Never disparage them directly. Focus on our key differentiators: "
"24/7 support, flexible pricing, and satisfaction guarantee.",
)

The action tells the agent what to do; the description tells it how.

How Conditions Are Evaluated​

Conditions Must Be Observable​

The agent can only evaluate conditions based on what's apparent to it—the conversation history, customer metadata, and variables. A condition like "the customer is vegetarian" works if that information comes from a saved variable or customer tag, but won't be reliably inferred from the conversation alone.

Predictive Conditions​

When a condition refers to the agent's own intention—e.g., "You are about to recommend a product" or "You need to present data in tabular form"—Parlant evaluates it on the level of likelihood. It tries to assess what the agent is likely to do next, which naturally means it won't always match when you expect (false negative) and may sometimes match when you don't expect (false positive).

This is the inherent limitation of predicting what the agent will decide to do. If you find that a predictive condition is unreliable, consider rephrasing it to reference observable facts about the conversation instead, or use a custom matcher for deterministic control.

Chained Conditions and Entailment​

A related challenge arises when one guideline's condition depends on another guideline's action. Consider:

  • Guideline A: When the customer asks about returns, Then explain the return policy
  • Guideline B: When explaining the return policy, Then also mention the 30-day warranty

Guideline B's condition ("when explaining the return policy") is about something the agent is about to do—but at evaluation time, it hasn't done it yet. Parlant evaluates both guidelines before composing the response, so B's condition may not match even though A will cause the agent to explain the return policy.

The solution is an entailment relationship: explicitly telling Parlant that whenever A is activated, B should also be activated:

await guideline_a.entail(guideline_b)

This guarantees B fires whenever A does, without relying on predictive condition matching.

Common Pitfalls​

Phrasing a persistent need as one-off. You write "warn them about the allergen" but actually need the agent to confirm the customer is okay with it. The agent warns once and moves on, leaving a safety gap. If the action requires a response from the customer, phrase it that way: "confirm with them" rather than "warn them."

Phrasing a one-off as continuous. You write "always remind the customer about our return policy" when you only needed it mentioned once. The agent brings it up repeatedly, becoming annoying. If it's a one-time piece of information, phrase it as a one-off: "mention our return policy."

The phrasing of your guidelines directly affects how the engine matches, tracks, and applies them. When behavior doesn't match expectations, check whether the duration implied by your phrasing matches the behavior you actually want.

Features​

Expanded Descriptions​

As covered in Keep Actions Short, the description field lets you elaborate on a guideline's intent without bloating the action text. Descriptions are loaded into the agent's context only when the guideline matches, and they help both with more accurate matching and with the agent's ability to follow the guideline correctly.

Descriptions are also useful for iterative refinement—when trial and error reveals the agent isn't interpreting a guideline as intended, you can adjust the description without changing the condition or action:

await agent.create_guideline(
condition="The customer asks about delivery times",
action="Provide estimated delivery windows",
description="Standard shipping is 5-7 business days, express is 2-3. "
"For international orders, add 3-5 days. If the customer's order "
"includes backordered items, mention the delay upfront rather than "
"giving an optimistic estimate.",
)

Criticality​

Some instructions can't tolerate mistakes—compliance disclosures, action confirmations, security checks. Others are gentle nudges—style preferences, conversational prompts. Criticality levels let you tell the agent how much attention to pay when matching a guideline or observation:

LevelDescriptionWhen to Use
LOWThe agent will make an effort but won't spend significant resources conforming."Nice to have" guidelines where you want to reduce rigidity.
MEDIUMThe agent will try harder while keeping some flexibility. This is the default."Should have" guidelines within the right context.
HIGHThe agent will try its hardest, potentially to the point of being pedantic. Parlant will use more resources to comply.Absolute "must have" guidelines for critical moments.
# HIGH - Detect a critical question
await agent.create_observation(
condition="Customer asks about cancellation fees",
criticality=p.Criticality.HIGH
)

# MEDIUM - Standard business logic
await agent.create_guideline(
condition="Customer wants to return an item",
action="Suggest store credit as an alternative to a refund",
criticality=p.Criticality.MEDIUM
)

# LOW - Style preference / gentle nudge
await agent.create_guideline(
condition="The conversation has just started",
action="Inquire if they want to hear about on-sale items",
criticality=p.Criticality.LOW
)

Practical recommendations:

  • Default to MEDIUM, then adjust based on observed behavior
  • Reserve HIGH for: compliance requirements, legal obligations, security checks, safety-critical instructions
  • Use LOW for: stylistic preferences and non-critical behavioral nudges (this level also helps reduce latency and costs)

Tool Association​

You can associate tools with guidelines so that the agent only considers calling a tool when the guideline's condition is met. The guideline's action also provides context for how the tool should be called:

@p.tool
async def find_products_in_stock(context: p.ToolContext, query: str) -> p.ToolResult:
...

await agent.create_guideline(
condition="The customer asks about the newest laptops",
action="First recommend the latest Mac laptops",
# The action ensures the tool is called with the right query
tools=[find_products_in_stock],
)

This prevents the agent from calling tools eagerly or out of context—a common problem with LLMs, which tend toward false-positives when deciding whether to take action. Learn more about tools.

Tags​

You can tag guidelines at creation time to organize them into groups:

compliance = await server.create_tag("compliance")

await agent.create_guideline(
condition="The customer is discussing topics outside the realm of flight booking",
action="Avoid the topic and only ask if you can help with flight-related topics",
tags=[compliance],
)

Tags become powerful when combined with relationships — they let you apply relationships to entire groups of guidelines at once, instead of wiring them one by one.

Controlling Reapplication with track​

By default, Parlant tracks whether a guideline's action has already been applied and deactivates it to prevent repetition. This is usually what you want—you don't want the agent explaining the return policy five times in one conversation.

But some guidelines are ongoing behavioral cues (tone adjustments, empathy responses, compliance reminders) that should apply every time their condition is met. If you're struggling to find a phrasing that keeps a guideline continuous, you can explicitly set track=False to enforce reapplication by design rather than relying on semantic evaluation:

# Default: tracked, applies once per context
await agent.create_guideline(
condition="Customer asks about pricing",
action="Provide current pricing",
track=True, # default
)

# Untracked: reapplies every time the condition matches
await agent.create_guideline(
condition="Customer expresses frustration",
action="Acknowledge their frustration and offer help",
track=False,
)

Custom Code Matchers​

By default, Parlant uses LLM-based matching to determine which guidelines apply. Custom matchers let you override this with your own logic, such as regex, embeddings, external database lookups, or simple boolean checks:

async def my_matcher(
ctx: p.GuidelineMatchingContext,
guideline: p.Guideline,
) -> p.GuidelineMatch:
return p.GuidelineMatch(
id=guideline.id,
matched=True, # or False
rationale="Explanation of why it matched or didn't",
)

await agent.create_guideline(
condition=CONDITION,
action=ACTION,
matcher=my_matcher,
)

Always-On Guidelines​

For guidelines that should always be in context, use p.MATCH_ALWAYS:

await agent.create_guideline(
condition="You need to present data which lends itself to tabular form",
action="Use markdown table formatting",
matcher=p.MATCH_ALWAYS,
)
Cognitive Load

Use always-on guidelines sparingly. The more guidelines in context, the harder it is for the LLM to follow them consistently—this is exactly why dynamic matching exists.

Match Handlers​

You can register handlers that fire whenever a guideline or observation is matched. This is useful for logging, analytics, or external integrations.

on_match — fires immediately after matching, before the agent generates its response:

async def my_handler(ctx: p.EngineContext, match: p.GuidelineMatch) -> None:
...

await agent.create_guideline(
condition=CONDITION,
action=ACTION,
on_match=my_handler,
)

# Also works with observations
await agent.create_observation(condition=CONDITION, on_match=my_handler)

on_message — fires after the agent has generated and sent a message while the guideline was active:

await agent.create_guideline(
condition=CONDITION,
action=ACTION,
on_message=my_handler,
)
github Questions? Reach out!