Plugin Go-Live Checklist

Best practices to review before launching your plugin to production.
View as Markdown

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.

Plugin

  • Description is clear and specific. Users and the AI assistant both rely on the description to understand what the plugin does. Vague descriptions lead to poor triggering and confused users.
  • Log redaction is enabled. Strict log redaction should be on by default to prevent sensitive data from appearing in logs.
  • No internal data in trigger examples. Trigger utterances should not contain real employee names, internal org names, or customer-specific references.
  • No junk triggers. Remove any test triggers (“test”, “asdf”, “hello”) before going live.
  • Each plugin should serve a single, focused intent. Avoid combining multiple distinct user intents (e.g., “look up a specific item” and “list all items”) into one plugin. Split them into separate plugins so each one has clear triggering, simpler slot design, and easier debugging.

Listeners

  • Webhooks are secured. Ensure inbound webhooks are validated using signature verification or credentials — never accept unauthenticated webhook payloads.
  • Filter events at source. If your Listener receives events from a third-party system, configure the webhook on the source side to send only the events you care about, rather than filtering in Moveworks.

Conversation Process

Slots

  • Slots are well-described. Each slot description should explain what the value represents and what format is expected. Avoid prompt-engineering instructions in descriptions (e.g. “Ask the user for this”) — use inference policies instead.

  • 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:

    • “Infer slot value if available” — the assistant derives the value from conversation context if it can, and falls back to prompting if it cannot.
    • “Always prompt the user” — the assistant always explicitly asks, ignoring context. Use this sparingly and only when the value must never be inferred.

    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.

  • 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.

  • 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>.value
    • Custom data type with resolver → access via data.<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.

  • 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.

Activities & Decision Policies

  • Prefer compound actions over multi-activity processes. If your plugin chains multiple API calls, consolidate them into a single compound action rather than multiple sequential activities. This is more maintainable and often faster.
  • No back-to-back action activities with the same required slots. If two sequential activities both depend on the same slot (e.g., Activity A needs 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.
  • Every activity and decision policy declares its required slots. The “Required Slots” field on each activity and the slot declarations on each decision policy must list every slot the system needs to collect before that step executes. If an activity’s input mapper references 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.
  • Confirmation is required for destructive actions. Any activity that modifies, creates, or deletes business records should have consent enabled (needs_confirmation).
  • Compound actions wait for completion. If an activity calls a compound action, ensure “Wait for this action to fully complete” is checked — otherwise the output will be empty when downstream steps try to use it. Uncheck only for fire-and-forget side effects (e.g., logging) where no output is needed. This setting does not apply to HTTP, Script, or Built-in actions, which are synchronous by default.
  • Output is trimmed to what matters. Don’t return the raw API response to the user. Trim unnecessary fields (internal IDs, API metadata, system timestamps) to reduce latency, lower token usage, and avoid exposing irrelevant data.
    • Replace API links (e.g. https://api.vendor.com/v2/tickets/123) with user-facing links (https://vendor.com/tickets/123).
    • Consider using citation format for structured results.
  • Output trimming can happen at the compound action level or the activity level — either is valid. If the compound action already trims the response and returns only user-friendly fields, the activity’s 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.
  • Use display instructions to control output presentation. If you need to control how results from an action activity are presented to the user, use display instructions for the model rather than adding a content activity or LLM action after it. The reasoning engine already generates a response from the action output — adding an extra LLM step doubles the latency for no benefit.

Data Types

  • Name data types with system awareness. Include the source system in the name (e.g. u_JiraTask not u_Task, u_SalesforceAccount not u_Account). This prevents naming collisions and makes it clear where the data comes from.
  • Schema accurately reflects the business object. The data type should contain the fields that the API actually returns, with correct types.

Compound Actions

  • No internal data in input arg examples. Example values should not reference internal teams, employee names, or org-specific terminology.
  • Parallelize where possible. Any code that can safely run in parallel should be made parallel.
  • Avoid duplicating logic across compound actions. If two compound actions share most of their structure but differ only in a minor variation (e.g., same RENDER template, same field mappings, same API calls), merge them into a single compound action with a parameter to control the variation.
  • Avoid LLM actions in user-facing compound actions. If the compound action is invoked by a conversational plugin, adding an LLM action inside it doubles latency — the LLM runs once inside the compound action, then the reasoning engine runs again to generate the user-facing response. Return structured data from the compound action and let the reasoning engine handle presentation.
  • Don’t use notify expressions to send notifications back to the originating user. Notify expressions are designed to send messages to NEW users — not to show intermediate status updates.

    Common anti-patterns include:
    • Sending status updates for a long-running plugin → use progress updates instead.
    • Sending notifications to show related follow-up actions → Steer the tool results conversationally with display_instructions_for_model
  • Return data in citation structure where possible, so results render cleanly in the conversation.

Script Actions

  • Code is documented. Functions should have docstrings explaining what they do, what they accept, and what they return.

HTTP Actions

Request Safety

  • Use {{{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.
  • HTTP filtering handles null values safely. If an HTTP action filters by a user-provided value (e.g. /approvals?email={{email}}), ensure that an empty or null value does not bypass permissions and return all records.
  • Remove dead parameters from request bodies. If a parameter in the HTTP body is not actually used, remove it to avoid accidentally writing empty or null values to the target system.
  • Action works on test. Before publishing, test each HTTP action to verify it returns the expected response.

Authentication & Security

  • Use User Consent Auth when available. If the connector supports OAuth 2.0 Authorization Code grants, use User Consent Auth so actions run as the requesting user rather than a service account.
  • Audit trail for write actions. If User Consent Auth is not available and your plugin modifies business records, include an audit comment identifying who made the change:
    "Created via Moveworks on behalf of {{meta_info.user.email_addr}} on {{NOW()}}"
    If User Consent Auth is configured, this requirement does not apply — the action already runs as the requesting user and the system’s own auth record is sufficient. Always verify the connector’s auth type before flagging this as a gap.
  • No sensitive data in auth headers. Auth headers should not contain client_id, client_secret, cookies, or other credentials that belong in the connector configuration.
  • No internal data in configuration. Input arg descriptions, example values, and URL paths should not contain real employee names, internal org names, customer data, or partner-specific URLs.

Schema & Documentation

  • HTTP actions have a response schema. Define a response schema so the platform can validate responses and provide better error handling. For HTTP actions that are fully wrapped inside a compound action where all output handling occurs at the compound action layer, a minimal or empty schema on the individual action is acceptable — the compound action’s output is what the process consumes, not the raw HTTP response.
  • Input arguments are well-documented. Each input arg should have a clear description and, where relevant, a link back to the source API documentation. Example values should match the expected format (e.g. ISO-8601 for timestamps, not yyyy/mm/dd).

General

  • Minimize unnecessary API calls. Design your plugin to avoid extra round-trips. For example, if a plugin has separate paths for “request for self” vs. “request for others,” don’t make a preliminary API call to fetch direct reports if it’s not needed on the current path.
  • Use user identity for system IDs where available. When the target system can resolve the current user’s identity, use it directly rather than making a separate lookup call.
  • The API does not expose capabilities beyond what the user can already do in the UI. Plugins should mirror what’s possible through normal product usage, not bypass access controls.
  • Input/output mapper expressions reference real fields. Every 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.
  • All DSL expressions are syntactically valid. Validation policies, slot conditions, and mapper expressions using the Moveworks DSL should be tested before publishing. Common errors include using triple-quoted strings ('''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.
  • All compound action paths are reachable and complete. For compound actions with branching (switch/case), verify that every case can be reached given valid slot values, every branch returns a well-formed output, and every action reference matches an existing published action name.