Plugin Go-Live Checklist
Plugin Go-Live Checklist
Plugin Go-Live Checklist
Before launching a plugin to production, walk through each artifact and verify it meets these best practices. Not every item applies to every plugin — use your judgment based on your use case.
Slots are well-described. Each slot description should explain what the value represents and what format is expected. Keep descriptions to one sentence — add an example value if it helps clarify the expected format. Avoid prompt-engineering instructions in descriptions (e.g. “Ask the user for this”) — use inference policies instead. Avoid embedding conditional rules or behavioral instructions (“if the user provides…”, “prefer…”, “ignore filler words…”) — logic of that kind belongs in a resolver or validation rule, not the description.
Don’t ask for information the system already has. If the user’s email, name, or ID is available from meta_info, don’t ask for it as a slot. This creates unnecessary friction.
Slot inference is enabled where possible. Avoid setting slots to “Always prompt the user” unless truly necessary. Letting the AI infer slot values from the user’s initial message makes the experience faster.
Match slot optionality to the inference policy and fallback expression. The inference policy controls how the AI assistant collects slot values:
For required slots (without which the request cannot proceed): use “Infer slot value if available” with an empty fallback expression — the platform will always prompt the user when the value cannot be inferred from context.
For optional slots (the request can proceed without them): use “Infer slot value if available” with an explicit fallback value (e.g. NULL or a sensible default). Never leave the fallback empty on an optional slot — an empty fallback makes it behave identically to a required slot.
Use appropriate data types. Numeric values should use a numeric type, not string. Mismatched types can cause parsing issues and confuse validation.
Use validation policies over prompt tuning. When a slot needs validation (e.g. a date must be in the future), implement it as a validation rule (e.g. $PARSE_TIME(value) >= $TIME()) rather than adding instructions to the slot description.
References to business objects use data-type-based resolvers over inline resolvers where available.
Use custom data types for reusable resolvers; use inline resolvers for plugin-specific resolution. Custom data types with embedded resolver strategies are the right choice when the same resolution behavior is needed across multiple plugins (e.g., a “look up Workday employee by email” resolver shared across many plugins). Inline resolvers are appropriate for one-off, plugin-specific resolution needs (e.g., disambiguating “submit for myself” vs. “submit on behalf of a direct report” within a single plugin). Treat intentional inline resolvers as valid — not as a gap. However, if the same inline resolver logic appears across two or more of your plugins, it is no longer plugin-specific and must be promoted to a shared custom data type — duplicated inline resolvers mean that any change to the lookup must be replicated across every plugin that uses it.
Fixed option sets use static resolvers. Any slot with a small, enumerable set of options (e.g., “Add/Remove”, “Low/Medium/High”, a list of reasons) must use a static resolver with explicit display_name + value pairs — not a prose description listing the options. Without a resolver, the AI may hallucinate option values, produce text variations that don’t match the target system, or send incorrect IDs.
Don’t add a resolver to a slot that collects free-form input. Slots that accept open-ended text — comments, notes, descriptions, free-text reasons — should not have an inline resolver. Resolvers introduce a structured lookup step that adds friction and confusion when the user just needs to type something freely.
Resolver output shape is understood before mapping. When a slot uses a resolver, the resolved value’s sub-fields depend on the slot type:
string with resolver → access via data.<slot>.valuedata.<slot>.<schema_field> (fields from the type’s schema)string without resolver → access via data.<slot> (raw string, no sub-fields)Verify the correct sub-field path before referencing it in input mappers.
A resolver can have multiple methods. An inline resolver may define multiple methods, each handling a different resolution path for the same slot. For example, a “Who is this request for?” resolver might have one method for resolving the current user and another for resolving a direct report. This is valid design, not a misconfiguration. The only case worth flagging is a method with an unclear name (e.g., Untitled_Method_1) and an empty body — that’s leftover scaffolding that should be cleaned up before the resolver is saved.
No sensitive or org-specific data in resolver strategy mappings. The input arguments and expressions inside a slot’s resolver strategy should not contain hardcoded emails, user IDs, system credentials, or internal org-specific values. Use dynamic expressions (e.g. meta_info.user.email_addr) rather than literal values — hardcoded data in resolver mappings is easy to miss and must not be carried into production or exported to other environments.
User slot fields are correct. native_type: "User" slots resolve to a Moveworks User object. Key fields: data.<slot>.email_addr (email), data.<slot>.full_name (name), data.<slot>.record_id (Moveworks platform ID). The platform ID is not the same as an external system ID (e.g., ServiceNow sys_id). To get an external system ID, add an action that looks up the user by email in the target system.
requested_for and Activity B needs A’s output, which also needs requested_for), combine them into a single compound action. Back-to-back action activities that share required slots cause redundant collection-then-execute cycles, add latency, and can confuse the conversation flow.data.reason, then reason must be in its required slots. If a decision policy evaluates data.access_type, then access_type must be declared. Missing slot declarations cause the system to skip collection entirely and execute with empty or stale data — the most common cause of slots being “ignored” at runtime.needs_confirmation).https://api.vendor.com/v2/tickets/123) with user-facing links (https://vendor.com/tickets/123).output_mapper can simply pass through the result. If the compound action returns the raw API response, the activity’s output_mapper must handle the trimming. What matters is that trimming happens at least once — either in the compound action or in the activity. If neither layer trims the output, the raw response reaches the user, which increases latency, token usage, and exposes irrelevant data.u_JiraTask not u_Task, u_SalesforceAccount not u_Account). This prevents naming collisions and makes it clear where the data comes from.display_instructions_for_model{{{triple brackets}}} for template parameters. In HTTP action paths and request bodies, use {{{param}}} (triple brackets) instead of {{param}} (double brackets). Double brackets apply URL/HTML encoding which can corrupt values like emails, dates, and JSON./approvals?email={{email}}), ensure that an empty or null value does not bypass permissions and return all records.client_id, client_secret, cookies, or other credentials that belong in the connector configuration.yyyy/mm/dd).input_mapper and output_mapper expression should be verified against the actual data shapes returned by actions and slot resolvers. Common mistakes include using data.<user_slot>.Id (Moveworks platform ID) where the target system expects its own ID (e.g., a ServiceNow sys_id), using data.<slot> when the resolved value is actually at data.<slot>.value, or referencing response fields that don’t match the actual API response structure. When in doubt, test the action and inspect the response.'''value''') in validation rules (only valid in compound action YAML), comparing arrays incorrectly (arr != [] instead of arr[0] != null), or referencing functions that don’t exist in the DSL.