The Data Retrieval pattern is how Agent Studio plugins connect a natural language request to the right query against an external system like ServiceNow, Workday, or a data warehouse. Instead of dumping all available data, the plugin carefully pulls back the specific records or fields needed to answer the user’s request, while applying business logic, filters, and performance safeguards along the way.
In practice, this pattern can power both simple lookups and complex analytics.
For example, a simple lookup plugin might fetch a clean list of current applicants for an open job requisition from Workday, showing just the names, stages, and last updated dates.
“You currently have three applicants in the pipeline.
Alice Chen is at the phone screen stage, last updated on September 15.
Brian Lopez is further along and has an onsite interview scheduled, updated on September 18.
And Sofia Patel already has an offer extended, with the latest update on September 20.”
You could scale up to a heavy analytics scenario, like analyzing thousands of rows in a data warehouse to surface insights into Net Promoter Score (NPS) trends—summarizing the overall score, highlighting changes across regions, and extracting themes from customer comments. Below is an example of how a heavy analytics scenario can work well conversationally.
(Reasoning engine tries to summarize large dataset without structured analysis)

(Reasoning Engine powered by the Data Retrieval pattern with Structured Data Analysis)

Whether the task is small or large, the principle is the same: translate user intent into a precise, efficient query, return only what’s relevant, and present results in a structured way that the model and the end-user can trust
When building retrieval plugins, we combine slots and static typed query components for reliability and precision.
This blend is expressed in the Moveworks data mapping language and DSL, which let developers define reusable templates with placeholders that the reasoning engine populates at runtime.
Plugin consumers are the business owners of the plugin. They supply the business logic by deciding which fields must always be included and which can remain optional. Encoding these rules up front ensures queries are accurate and efficient, returning only the necessary data, reducing payload size, minimizing latency, and avoiding irrelevant attributes.
This manipulation of the query logic should happen as the query is passed into the action in your conversational process as an input mapping with Moveworks Data Mapper as highlighted below

Hardcoded query
The entire query is hardcoded in the action and should always return all data.

Slot-filled component
Single component in the query supplied by the conversational/compound action

Fully dynamic query
Entire statement supplied by the conversational/compound action

For simple use cases, the query can be fully static or involve only a single slot . A fully static query might always return the same fixed set of fields such as fetching the list of all open requisitions, or returning the current user’s profile record. A single-slot query adds just one dynamic element, like filtering opportunities by a user-provided close date, or pulling applicants for a specific role. These are straightforward to configure because the business logic is minimal: you define the core fields once, ensure they’re always included, and allow one slot to vary based on user input. This keeps the template lightweight, predictable, and fast to execute.
Example simple query input mapping (SOQL)
For advanced scenarios, queries often combine multiple slots with conditional logic. A Salesforce opportunity query, for instance, might include close_date_range, owner_email, and include_notes. If the user specifies a date filter, it’s applied; if they also request account team details, fields like Owner.Name and CSM__r.Name are added dynamically.
The Moveworks Data Mapper handles these optional “property slots” and enforces business rules such as which attributes are safe to fetch or which filters must be hardcoded. The goal: return only what’s needed to answer the request, avoiding overfetching while still letting users add conditions in natural language.
Example advanced input mapping (SOQL)
A. One Plugin per Attribute Set
This approach creates a single, purpose-built plugin for a defined set of attributes. Each plugin has a fixed property list, which means queries are straightforward, with little to no conditional logic. Because the schema is predictable, these plugins are easy for the reasoning engine to handle, making them highly reliable and fast to execute. The simplicity keeps complexity low and ensures consistent results. However, the trade-off is scalability: every new attribute set requires its own dedicated plugin, which can quickly lead to a proliferation of many small, narrowly scoped plugins.
Input arguments for Plugin for AE’s to perform lookup
Input arguments for Plugin for SDR’s to perform lookup
B. Property Group Slots (Recommended for Breadth)
Instead of creating a separate plugin for each attribute set, this pattern uses conditional logic to insert groups of semantically related fields—called property group slots—based on the persona or the user’s utterance. This approach allows a single plugin to flex across multiple use cases without exploding into dozens of narrow plugins. It gives breadth and flexibility: account executives, SDRs, renewals managers, and executives can all query the same plugin, but each gets the fields returned relevant to their role. The key is balance keeping the slot count low improves performance and reasoning quality.
Example SOQL groupings:
SELECT StageName, Amount, CloseDate, Owner.Name, Champions__c, Notes__c FROM Opportunity WHERE CloseDate = THIS_QUARTERSELECT Account.Owner.Name, StageName, Tech_Stack_Flags__c, Notes__c FROM Opportunity WHERE IsActive__c = trueSELECT Renewal_Date__c, Contract_End_Date__c, Growth_ARR__c, User_Count__c FROM Opportunity WHERE Renewal_Date__c < NEXT_90_DAYSSELECT Team__c, Key_Touch_Fields__c, Growth_ARR__c, Exec_Notes__c FROM Opportunity WHERE Amount > 100000Input arguments in Action for single a plugin using user department on the user records:
This could also be done by creating a slot in the conversational process for what type of lookup the user is attempting by creating a slot named “query_intent” with a description like “The intent of the users lookup for fields relating to one of these personas either “renewal”, “executive”, “sales development rep”, “account executive” or “generic”. The value of this slot is one of the personas identified or generic if unidentifiable from the query”
The logic you choose should be determined based on what aspects you want to be deterministic vs probabilistic there may be scenarios where you want guardrails on the query and will always enforce the query based on user profile attributes or the scope of which fields are determined by the user and reasoning engine are more limited it is not one size fits all. The other thing to be cognizant of is over-asking of slots, in a lot of cases it will provide better user experience to statically determine more slots or infer them rather than require the user provide them.

If you think of conversational processes as programs, then you can think of Slots as your program’s variables. The Moveworks Reasoning Engine’s job is to navigate the fluidity/ambiguity of a natural language conversation and fill appropriate (often structured) values for Slots so that the conversational process can run smoothly and operate on the right information. For data retrieval processes, Slots can be a powerful mechanism to properly collect critical information that needed to execute a query.
The value for a Slot can involve pulling information directly from the user or even retrieving dynamic objects from other systems (e.g. using a Resolver to get a ServiceNow ticket)”. To get a value for a Slot, the reasoning engine heavily relies on the slot name, description, and data type that you provide in the configuration:
close_date, customer_name, contract_term_months). This is a primary indicator to the reasoning engine of what the slot truly represents and why it matters. (Tip: programming conventions for “good variable naming” often apply here).string, number, boolean, array) indicate to the reasoning engine how the ultimate value for the slot should be shaped, and ensures the value is compatible for downstream uses. Based on the data type (especially if the type is complex), the reasoning engine can also lean on special mechanisms (Resolvers) attached to the type to optimize the value retrieval for the Slot.Example:
If the user says “tomorrow”, the LLM will output 2025-09-23T00:00:00Z in the specified format rather than the raw text.

Slots need to be appropriately precise and adherent to constraints in order to meet the structured needs of data retrieval processes. The reasoning engine’s is adept at correlating fluid/complex conversational context with a variety of configuration info (e.g. slot name/description/type metadata) to generate a formal slot value. However, due to its probabilistic nature and varying precision, it’s not as ideal for taking care of precise operations or exact computations on its own.
To enforce rules or compute results over multiple slots, we need to lean on Slot Validation Policies, powered by Moveworks DSL. Encoding a formal policy will enforce rules deterministically and perform more reliably than overprompting slot descriptions with logical instructions (e.g. “Make sure the date is in the future and after the contract start date”). An explicit validation policy ensures that slots produce values that are not just valid in format, but also compliant with business rules.
Example:
Here the slot captures any user-provided date, but the validation policy enforces that it must not be in the past by using DSL we take the slot collected value “value” , use $PARSE_TIME() to convert the string to unix format, then use a comparison against $TIME() (the current time in unix format). This pattern also works for cross-field checks (end_date > start_date) or validating against values in the data bank (like comparing to a previously retrieved contract date).
Important note is if you compare 2 slots against each other in this way you must ensure the other slot has been collected already.

Resolvers extend the behavior of slots by offering an explicit mechanism to determine the value of a slot. Instead of leaving everything to inference, a resolver can either force a value to be selected from a fixed list (static resolver) or retrieved from a live dataset (dynamic resolver).
priority = low, medium, high.
User, serviceNowTicket, etc.
Note: the output mapper must always point to an array if the output cardinality is set to interpret as a list of candidate values.
Given an action with an output like
The output mapping would be response.results


Slots, validation, and resolvers are all tools in the same toolbox, but each is suited to a different layer of the problem.
Use a plain slot when:
Example: Capturing an email address, a date, or a free-text search term.
Add a slot validation policy when:
Example: Ensuring end_date is later than start_date.
Introduce a resolver when:
priority = low, medium, high).Example: Resolving “my open tickets” into a specific JiraIssue object returned by a lookup action.
How filters are built
STATIC AND SAFETY AND (CONTROLLED_VOCAB) AND (IDENTITY_OR_OWNERSHIP) AND (USER_SLOTS)ANY → filter omitted.Goal: Normalize messy natural language to canonical filters.
Pattern
ANY (omit filter) if unmapped.Examples (SOQL)
Type IN ('Customer','Customer via MSP','Customer via Parent')Type IN ('Prospect','Trial','POC')Type IN ('Partner','Reseller')Example mapping (YAML / Data Mapper)
Goal: Hard guarantees that results are fresh, relevant, and policy-safe.
Pattern
Examples (SOQL)
CurrentOrUpcoming_Active__c = TRUEIsArchived__c = FALSEOrgId__c = {{TENANT_ID}}LastModifiedDate = THIS_YEAR (or parametric window)Example (YAML / Data Mapper)
Goal: Resolve “my X” to all relevant ownership fields, not just Owner.
Pattern
Common fields
Owner.Email, CSM__r.Email, SE__r.Email, Implementation_Lead__r.Email, Renewal_Manager__r.Email, AE__r.Email, BDR__r.EmailExamples (SOQL)
“my accounts”
Combine with safety + classification:
Example (YAML / Data Mapper)
General guidance
while support: Compound actions do not support looping until a condition is met (e.g., “keep fetching until next_page token is null”).“For Each” in a compound action
When you need to make multiple calls (e.g., enriching a list of users or account IDs with detailed lookups), use the for construct inside a compound action. This lets you iterate deterministically over a known list and collect results.
Static arrays for known limits
If your dataset is static with a known upper bound, you can create a fixed array for pagination. For example, if the dataset is always 1,000 records and the API limit is 200 per call, you can predefine a 5-element array:
This approach works when:
Property matching is about converting what a user says in natural language—like “T Mobile”, “T-mobile”, or “SNow”—into the canonical identifiers that an external system expects. This is a constant challenge because users often rely on nicknames, acronyms, or misspellings that don’t match the exact stored values. Without reliable matching, queries risk returning empty sets or irrelevant data.
Approach 1: At Query time (in input arguments)
Substring matching
If fuzzy matching is unavailable on the system side, you can issue substring targeted queries (e.g., LIKE '%SNow%', LIKE '%T-Mobile%') . This can work for substring matches but will often fail on acronyms or abbreviations like “SNow” for Service Now. This works best for commonly typed fields that users will likely always provide proper values.
Approach 2: At response time in the payload
Simple / Small Datasets (< ~7k tokens)
For smaller sets of candidate values (e.g., names, project codes, accounts), you can pull the full list from the system and let the LLM evaluate directly. This “enumerate & select” method works well within token limits, as the reasoning engine can compare user input against all possible options to identify the best match.
Larger or Complex Datasets (> ~7k tokens)
When the candidate list is too large, rely on Structured Data Analysis (SDA) to chunk, rank, and filter values before final selection. SDA can handle thousands of rows, applying heuristics and embeddings to propose likely matches, then confirming with the model.
The goal is to deliver just enough, well-typed data for the Reasoning Engine to reason, summarize, or write SDA code. In addition to returning trimmed fields, you can append an extra key such as display_instructions_for_model to explicitly guide the LLM on how to use the data. This can include instructions for analysis, how verbose or concise to be, or even what text formatting to apply.
account_name, nps_score, and close_date are immediately meaningful, whereas generic keys like field1 or opaque IDs are not.contract_start_date and contract_end_date as separate top-level fields rather than embedding a nested contract object).
ie: Overnested
ex: Flattened and structured well
display_instructions_for_model to explicitly tell the LLM how to handle the data. For example: “Summarize accounts grouped by region with concise bullet points” or “Render results in a table with columns for account, NPS score, and trend.” This reduces ambiguity and ensures outputs follow business or stakeholder preferences.From this example dataset, a good output mapper would:
Id, LastModifiedDate, SystemModstamp, nested Owner.Id).Owner and Contract__c.account_name, region, nps_score, delta_qoq, owner_name, contract_start_date, etc.display_instructions_for_model to guide the LLM on formatting (e.g., show results in bullet points with account, region, NPS score, and trend delta).Example Action output data:
Example output mapping:
The reasoning engine operates under a 7,000 token limit. This means that the combined output of any action call plus any instructions or metadata must fit within this boundary. If responses exceed that size, the Reasoning Engine alone cannot handle them effectively and SDA will kick in to run analysis on the data before the Reasoning Engine summarizes the plugin output.
When generating a response, the reasoning engine takes into account:
display_instructions_for_model)It then summarizes or reformats this data in whatever way it deems most useful to the user. The cleaner and more structured your data, the better the summarization and follow-up responses will be.
Because the reasoning engine is tuned for a conversational experience, long, exhaustive outputs are discouraged. Instead:
If a dataset exceeds the 7,000 token threshold, the reasoning engine will not be able to process it effectively. In these cases, the system automatically falls back to Structured Data Analysis (SDA). SDA is designed to handle very large inputs—potentially thousands of rows—and can run deeper analyses or answer follow-up questions without attempting to display everything inline.