Slot Best Practices

Six slot anti-patterns that break in production and how to fix them.

View as MarkdownOpen in Claude

Slots look simple in the UI. Pick a name, write a description, choose a type. But the wrong choices here cascade through every action and conversation that touches the slot. These six patterns are the ones that break in production, not in testing.

Bloated Descriptions

Slot descriptions tell the reasoning engine what a slot represents. They’re semantic labels, not rule engines. When you stuff validation logic, formatting rules, or behavioral instructions into a description, none of it gets enforced.

What it looks like

Bloated slot description
1- name: due_date
2 description: |
3 The date when the task is due. MUST be in YYYY-MM-DD format.
4 The date CANNOT be in the past. If user provides a past date,
5 reject it and ask again. The date should be within the next
6 90 days. If the user says "tomorrow", calculate the date.
7 data_type: string

Why it breaks

The reasoning engine reads slot descriptions to understand what a slot represents, not how to handle it. When you pack rules into descriptions:

  1. Rules aren’t enforced. The engine might ignore them or hallucinate compliance.
  2. Context bloat. Every character in the description consumes tokens in every reasoning call.
  3. Maintenance nightmare. Rules scattered across descriptions instead of centralized in policies.

Real consequence

User submits “yesterday” as due date. The engine sees the rule in the description but has no enforcement mechanism. Bad data hits your API, which returns a cryptic error like "Invalid date range". User sees a generic failure message. Support ticket filed.

The fix

Clean description + validation policy
1- name: due_date
2 description: "The target completion date for this task. Expected format: YYYY-MM-DD."
3 data_type: string
4 validation_policy:
5 rule: $PARSE_TIME(value) >= $TIME()
6 error_message: "Due date must be today or in the future"

Description says what the slot is and what format to expect. Validation policy enforces the rules. The engine shows the error_message to the user and re-collects the slot automatically.

Formatting hints like “Expected format: YYYY-MM-DD” are fine in descriptions — they help the engine collect the right shape of data. What doesn’t belong is behavioral logic: “if the user says X, do Y” or “reject and ask again.”

Rule of thumb: Descriptions should say what, not how. Format hints are fine. Behavioral rules are not. If your description has instructions for the engine rather than context about the data, move those rules to validation policies, decision policies, or resolver strategies. Know the trade-off between deterministic enforcement (policies, DSL) and non-deterministic prompting (descriptions) — use the platform’s built-in tools wherever the behavior should be consistent and reliable.


Wrong Slot Type for People

Using string to collect people is the single most common slot mistake. It forces the reasoning engine to parse structured data from free text, which is unreliable and skips the platform’s built-in identity resolution.

What it looks like

String slot for people
1- name: attendees
2 description: "Comma-separated list of attendee emails like john@company.com, jane@company.com"
3 data_type: string

Why it breaks

You’re asking the engine to parse a comma-separated string into individual identities. This requires:

  1. An LLM call to parse the string into a list
  2. Another call to resolve each email to a user
  3. Error handling for malformed input (“john at company dot com”)
  4. No fuzzy matching (“John” won’t find “john.smith@company.com”)

Real consequence

User says “invite John and the marketing team”. Your string slot captures "John and the marketing team". The engine tries to parse this as emails. Fails. User tries "john@company.com, marketing-team@company.com". Engine parses it. But marketing-team is a distribution list, not a user. API call fails with "Invalid attendee".

The fix

Correct slot type for people
1- name: attendees
2 description: "People to invite to the meeting"
3 data_type: list[User]

The platform’s User resolver:

  • Fuzzy matches “John” to “john.smith@company.com
  • Shows disambiguation UI if multiple matches
  • Validates users exist before proceeding
  • Handles display names, partial matches, and typos

Rule of thumb: If you’re collecting people, use User or list[User]. Always.


Missing Validation Policy

Free text slots without validation accept whatever the user provides. “Priority 99” passes through. “Start date: last Tuesday” passes through. Your API receives garbage, returns errors, and the user sees a generic failure.

What it looks like

Slots without validation
1- name: priority
2 description: "Priority level 1-5 where 1 is highest"
3 data_type: number
4
5- name: start_date
6 description: "When the PTO should start. Must be today or later."
7 data_type: string

Why it breaks

Without a validation policy, the engine accepts whatever the user provides. The “1-5” constraint lives in the description only — which means it’s a suggestion, not a rule.

Real consequence

HR system only accepts priority 1-5. User says “make it priority 10 — super urgent!” Engine passes priority: 10. API returns 400 Bad Request: priority must be between 1 and 5. User sees “Something went wrong. Please try again.”

The fix

Slots with validation policies
1- name: priority
2 description: "Priority level"
3 data_type: number
4 validation_policy:
5 rule: value >= 1 AND value <= 5
6 error_message: "Priority must be between 1 (highest) and 5 (lowest)"
7
8- name: start_date
9 description: "When the PTO should start"
10 data_type: string
11 validation_policy:
12 rule: $PARSE_TIME(value) >= $TIME()
13 error_message: "Start date must be today or in the future"

When validation fails, the engine shows the error_message and re-collects the slot. The user gets a clear, specific prompt instead of a cryptic API error.

Common Validation Patterns

Use CaseDSL Expression
Date not in past$PARSE_TIME(value) >= $TIME()
Number in rangevalue >= 1 AND value <= 100
Non-emptyvalue != '' AND value != null
Minimum lengthvalue.$LENGTH() >= 10
Allowed valuesvalue IN ['low', 'medium', 'high']

Validation policies use the same DSL functions available in output mappers. $PARSE_TIME, $TIME, $LENGTH, and comparison operators all work inside rule expressions.


Timezone Without Resolver

Users say “PST” or “Pacific Time”. APIs need “America/Los_Angeles”. Without a resolver mapping display values to IANA identifiers, the raw user input flows straight to your API and breaks.

What it looks like

Raw string for timezone
1- name: timezone
2 description: "User's timezone like PST, EST, or America/Los_Angeles"
3 data_type: string

Why it breaks

Users say “PST” or “Pacific Time”. Calendar APIs (MS Graph, Google Calendar) need IANA format: America/Los_Angeles. Without a resolver:

  1. User enters “PST”
  2. Slot stores “PST”
  3. API receives timezone: "PST"
  4. API fails or interprets incorrectly

Real consequence

Meeting scheduled for “9 AM PST”. MS Graph Calendar API receives timeZone: "PST". Graph doesn’t recognize “PST” — it needs IANA format. Meeting created with wrong timezone. Attendees in different timezones see the wrong time. Everyone shows up at different times.

The fix

Static resolver for timezone mapping
1- name: timezone
2 description: "Timezone for the meeting in IANA format (e.g. America/Los_Angeles)"
3 data_type: string
4 resolver_strategy:
5 type: static
6 options:
7 - display_value: "Pacific Time (PT)"
8 value: "America/Los_Angeles"
9 - display_value: "Eastern Time (ET)"
10 value: "America/New_York"
11 - display_value: "Central Time (CT)"
12 value: "America/Chicago"
13 - display_value: "Mountain Time (MT)"
14 value: "America/Denver"

User sees “Pacific Time (PT)” in the selection. Slot stores "America/Los_Angeles". API gets the right format. Meetings are correct. Note the description also specifies the expected format (IANA) — this helps the engine understand what shape the resolved value takes, even though the resolver handles the actual mapping.

Yes, static resolvers add an LLM call. For timezone mapping, it’s worth it — the alternative is broken meetings.


Static Resolver for Boolean

Static resolvers add an LLM call to resolve the user’s input to a value. For binary yes/no choices, this adds latency and cost with zero benefit over a native boolean slot.

What it looks like

Resolver for a two-option choice
1- name: meeting_type
2 description: "Either virtual or in-person"
3 data_type: string
4 resolver_strategy:
5 type: static
6 options:
7 - display_value: "Virtual (Teams)"
8 value: "virtual"
9 - display_value: "In-Person"
10 value: "in-person"

Why it breaks

Static resolvers add an LLM call to resolve the user’s input to a matched value. For two options that map to yes/no, this is overhead:

  • Extra latency (~200-500ms per resolution)
  • Extra cost (LLM tokens)
  • No benefit over simple boolean

Real consequence

Every meeting request takes 500ms longer than necessary. Multiply by thousands of requests. Your plugin is “slow” compared to others. Users notice.

The fix

Boolean slot — no resolver needed
1- name: is_virtual
2 description: "Whether this is a virtual meeting with a Teams link"
3 data_type: boolean

When to Use a Resolver vs Boolean

ScenarioUse
Yes/No choiceboolean
Display value differs from API valueStatic resolver
More than 2 optionsStatic resolver
Need fuzzy matching on inputStatic resolver

Every resolver adds an LLM call. A boolean slot is resolved without one. If you’re building a two-option choice (approve/reject, yes/no, enable/disable), always use boolean over a static resolver. See Decision Frameworks — Slot Type Selection for the full decision tree.


Conditional Slot Collection

You can’t conditionally collect one slot based on another slot’s value through slot configuration alone. Comments like “required if meeting_type is in-person” in a description are ignored — the engine either always collects the slot or never does.

There is one exception: context passing in resolver strategies. If a slot’s resolver needs another slot’s value to fetch its options (like fetching time-off types for a specific user, or subcategories based on a selected category), you can pass collected slot values into the resolver via strategy mapping. But context passing is for resolver inputs, not for controlling whether a slot gets collected at all.

What it looks like

Attempting conditional slot collection
1- name: room_name
2 description: "Conference room for in-person meetings. Required if meeting_type is in-person."
3 data_type: string
4 inference_policy: infer_if_available

Why it breaks

You can’t conditionally skip slot collection based on another slot’s value. Putting “required if” logic in a description doesn’t make the engine obey it. The reasoning engine will either:

  1. Always collect the slot (ignoring your “required if” comment)
  2. Never collect the slot
  3. Randomly decide based on context

Real consequence

User schedules a virtual meeting. Engine asks “What conference room?” User confused — “It’s virtual, I don’t need a room.” User types “none” or “N/A”. Your API receives room: "none". Room booking system tries to book a room named “none”. Fails or books the wrong room.

The fix

Use a decision policy in your conversation process to control flow based on collected slot values:

Decision policy for conditional slot collection
1decision_policies:
2 - name: collect_room_if_in_person
3 condition: data.is_virtual == false
4 action: goto_activity:collect_room
5 else_action: goto_activity:create_meeting
6
7activities:
8 - id: collect_room
9 action:
10 action_name: collect_room_details
11 required_slots: [room_name]
12 output_key: room_collected
13 next_activity: create_meeting
14
15 - id: create_meeting
16 action:
17 action_name: create_meeting_ca
18 output_key: meeting_result

The conversation process collects is_virtual first (as a boolean slot — no resolver needed). Then the decision policy routes to the right activity based on the value. Users scheduling virtual meetings never see a room prompt.

Context passing vs decision policies: If your second slot’s resolver options depend on the first slot’s value (e.g. fetching subcategories based on a selected category), use context passing in your resolver strategy. If you need to skip collecting a slot entirely based on a condition, use a decision policy in the conversation process. They solve different problems.

Rule of thumb: If you see “required if” or “only when” in a slot description, you need a decision policy in your conversation process. If one slot’s resolver options depend on another slot’s value, use context passing.


Quick Reference

PatternSeverityFix
Bloated descriptionsHIGHDescription = what. Validation policy = how.
Wrong type for peopleCRITICALUse User or list[User]. Always.
Missing validationHIGHAdd DSL validation policy. Show clear error messages.
Timezone without resolverCRITICALStatic resolver mapping display names to IANA values.
Static resolver for booleanMEDIUMUse boolean type. Save the LLM call.
Conditional slot collectionCRITICALDecision policies to skip slots. Context passing for dependent resolvers.