diff --git a/plugins/windmill/skills/create-pulse/SKILL.md b/plugins/windmill/skills/create-pulse/SKILL.md index 3d07e36..8e09682 100644 --- a/plugins/windmill/skills/create-pulse/SKILL.md +++ b/plugins/windmill/skills/create-pulse/SKILL.md @@ -1,6 +1,6 @@ --- name: create-pulse -description: Context and guidance for creating pulse surveys. Use when user wants to create a new pulse, gather feedback or sentiment from their team, or asks to "ask the team" about something. +description: Context and guidance for creating a new pulse survey. Use when the user wants a pulse that does not exist yet: to gather feedback or sentiment from their team, or to "ask the team" about something. For a pulse the user already has, use the managing pulses skill. domain: pulses resourceFilename: creating_pulses_skill.md --- @@ -14,6 +14,10 @@ Use this skill when: - User asks to gather feedback/sentiment from their team - User wants to "ask the team" about something +This skill covers a new pulse, from the draft through its first launch. For a pulse that already exists (change settings, requester, owners, participants, or schedule; pause or resume; send again; nudge; read results), load managing_pulses_skill.md and follow it. + +"I have a pulse", "my pulse", or "the pulse about X" means a pulse that exists. Find it with `pulse_query` first and make the requested change there. Start a draft only when the user asks for a new pulse or the query finds no match. + ## Relevant Resources - pulses_system_context.md @@ -29,29 +33,39 @@ Use this skill when: - Participant: Employee who receives the pulse - Live Response Streaming: When enabled, each employee response is posted into a Slack channel in real time as participants finish (separately from the end-of-run report). The Windmill Slack bot must already be a member of the channel for it to be selectable. Anonymous pulses cannot stream. The system silently disables streaming on anonymous pulses even if a channel is provided. +## Citing Pulses + +Write each pulse you name as a citation link: `[Pulse name](PULSE-)`, with the `pulseId` from the tool result. Do this in every reply that names the pulse, including a reply that confirms a create, test, launch, update, or send, and a reply that only asks a question about the pulse. The link replaces a quoted or bold pulse name. + +Pulse tools return people as keys. The `references.employees` block of the same result gives each key its `name` and `employeeId`. Write each person you name, also in a list of participants, as `[Name](EMPL-)`. + +The `PULSE-` and `EMPL-` prefixes are required: a link with a bare ID is wrong. A citation link needs a real ID from a tool result. A pulse or person that no tool returned gets a plain name with no link. Do not write a placeholder such as `PULSE-unknown` or `EMPL-?`, and do not look someone up only to cite them. + ## CRITICAL: How Pulse Creation Works -Creating a pulse can include schedule configuration in the same call. +A pulse goes through three stages. Each stage has its own tool: + +1. Draft: `pulse_create` makes a draft. A draft has no participants and no schedule. It is not live and sends to no one. +2. Test: `pulse_test` sends a test run to the creator only. It never adds participants and never launches the pulse. +3. Launch: `pulse_launch` saves the participants and the send timing, and makes the pulse live. + +If the user names an audience or a send time in the creation request, keep it for the launch stage. `pulse_create` does not accept participants or a schedule. Look up, count, and list the audience in Step 6, not before the draft exists. The creation flow gathers and confirms: 1. Prompt 2. Discussion topics (can be AI-generated if needed) -3. Participants -4. Anonymity -5. Run updates preference (`runUpdatesEnabled` true/false) -6. Optional schedule preference (manual, one-time, recurring, or start-date anniversary) -7. Optional live response streaming channel (only ask if the user mentions streaming, posting to Slack as responses come in, or similar; do not prompt by default) +3. Anonymity +4. Run updates (`runUpdatesEnabled`; on unless the user says otherwise) +5. Optional live response streaming channel (only ask if the user mentions streaming, posting to Slack as responses come in, or similar; do not prompt by default) The tool call also requires: - `name` (generated from prompt/topics) -- `runUpdatesEnabled` (explicit boolean; do not omit) - -The pulse is created in ACTIVE status. If no schedule fields are provided, it defaults to MANUAL. +- `runUpdatesEnabled` (always pass it; `true` unless the user says otherwise) After creation, the user can: -- Update name, prompt, participants, and other editable settings (via `pulse_update`) -- Update schedule (via `pulse_update`) -- Send it now (via `pulse_send_now`) +- Edit the draft's content and settings (via `pulse_update`). A draft rejects participant and schedule fields. +- Send a test to themselves only (via `pulse_test`) +- Launch it to participants (via `pulse_launch`) ## Workflow: Create Pulse @@ -74,7 +88,57 @@ Guidelines for good topics: These topics will be used by the agent when conducting the pulse. Each topic will be covered with the user. -### Step 3: Select Participants +### Step 3: Confirm settings +Anonymity is the only setting that needs the user's answer. When the user has already stated it, do not ask again. Every other setting has a default: no response deadline, the default reminder delay, run updates on, and no live streaming. Use the defaults without asking, create the draft, and list them in the draft confirmation so the user can change them. Ask about one of these settings only when the user raises it. + +Confirm anonymity before calling `pulse_create`. It can still change with `pulse_update` before launch, but it is locked after launch. + +Response Anonymity Options: +- Named (default): + - Each response shows who said it + - Creator can follow up with specific people + - Best for: Check-ins, 1:1 prep, non-sensitive topics + +- Manager Hierarchy: + - Responses visible to the respondent's manager chain + - Provides some privacy while allowing managers to see their team's responses + - Best for: Team-specific feedback where managers need visibility + +- Anonymous: + - Responses are fully de-identified + - Requires minimum 3 participants (protects identity) + - Best for: Sensitive topics, honest feedback, psychological safety + +Duration: How long participants have to respond to the pulse. + +Reminder Delay: How long to wait before sending the first **reminder** notification to participants who haven't responded yet. Defaults to 1 day (1440 minutes). Leave at default unless the user explicitly asks about reminder timing. + +CRITICAL DISAMBIGUATION: When the user says "send it now" or "send immediately", they mean **send the pulse now**: create the draft, then launch with `pulse_launch` and `sendNow: true` after the Step 7 confirmation. Do NOT interpret "send now" as "set the reminder delay to 0 or 1 minute". The reminder delay is a separate concept from when the pulse is sent. + +Run Updates: Whether the creator gets updates from the agent throughout each run + +Live Response Streaming (optional): If the user wants individual responses to land in a Slack channel as participants finish, pass `liveResponseStreamingChannel` (Slack channel name like `#pulse-results`, channel ID like `C0123456789`, or null to leave disabled) and optionally `liveResponseStreamingThreaded` (defaults true: responses thread under a single message instead of posting individually). The Windmill Slack bot must already be in the channel. If it isn't, the tool returns an error asking the user to add it. Anonymous pulses cannot stream. Warn the user and leave the field unset if they ask for both. + +### Step 4: Create the Draft +Call `pulse_create` with: +- `name`: Generated from prompt/topics (required) +- `prompt`: What they want to learn (required) +- `discussionTopics`: Array of topics (required) +- `anonymity`: NAMED, ANONYMOUS, or MANAGER_HIERARCHY (required; null defaults to NAMED) +- `durationMinutes`: Response duration in minutes, min 30 (optional, omit for no deadline) +- `notificationDelayMinutes`: Minutes before first **reminder** to non-respondents (optional, defaults to 1440). Do NOT change this based on when the user wants to send the pulse; it only controls reminder timing. +- `runUpdatesEnabled`: Whether creator gets updates from the agent throughout each run (required boolean; `true` unless the user says otherwise) +- `liveResponseStreamingChannel`: Optional Slack channel reference (`#name`, `name`, or external ID like `C0123456789`) to stream individual responses into. Omit for no streaming. The Windmill Slack bot must already be in the channel. +- `liveResponseStreamingThreaded`: Optional boolean, defaults true. Only meaningful when a streaming channel is set. + +### Step 5: Offer a Test +Tell the user that the draft exists, has no participants, and is not live. Offer to send a test to them with `pulse_test`, then wait for their answer. + +- When the user asks to test, call `pulse_test` only. The test goes to the user alone, also when the request named a team or the whole company. A test request never calls `pulse_launch`, `pulse_send_now`, or a participant update. +- After a test, end your turn and wait for the user's feedback. Apply edits with `pulse_update`. Never call `pulse_test` and `pulse_launch` in the same turn. "Test it on me first, then send it" means: send the test now, and launch only after the user has seen the test and tells you again to send. +- A test is encouraged, not required. If the user declines, continue to Step 6. + +### Step 6: Select Participants Determine who should receive this pulse. @@ -95,7 +159,7 @@ Common patterns: - "specific people" -> Use `employeeIds` - Employee groups -> Use `employeeGroupIds` with the send-all capability; otherwise resolve the group to `employeeIds` -Whenever constructing a participant filter, confirm the included employees with the user prior to creating the pulse. +Whenever constructing a participant filter, confirm the included employees with the user prior to launching the pulse. ### UI Participant Presets (for reference) @@ -118,63 +182,21 @@ Validation: Pulses require Windmill membership. Set `windmillAccess: "HAS_ACCESS"` in the participant filter. -### Step 4: Confirm other settings -Confirm the other settings with the user. - -Confirm anonymity before calling `pulse_create`. It can still change with `pulse_update` before launch, but it is locked after launch. - -Response Anonymity Options: -- Named (default): - - Each response shows who said it - - Creator can follow up with specific people - - Best for: Check-ins, 1:1 prep, non-sensitive topics - -- Manager Hierarchy: - - Responses visible to the respondent's manager chain - - Provides some privacy while allowing managers to see their team's responses - - Best for: Team-specific feedback where managers need visibility - -- Anonymous: - - Responses are fully de-identified - - Requires minimum 3 participants (protects identity) - - Best for: Sensitive topics, honest feedback, psychological safety - -Duration: How long participants have to respond to the pulse. - -Reminder Delay: How long to wait before sending the first **reminder** notification to participants who haven't responded yet. Defaults to 1 day (1440 minutes). Leave at default unless the user explicitly asks about reminder timing. - -CRITICAL DISAMBIGUATION: When the user says "send it now" or "send immediately", they mean **send the pulse now** (use `pulse_send_now` after creation). Do NOT interpret "send now" as "set the reminder delay to 0 or 1 minute". The reminder delay is a separate concept from when the pulse is sent. - -Run Updates: Whether the creator gets updates from the agent throughout each run - -Live Response Streaming (optional): If the user wants individual responses to land in a Slack channel as participants finish, pass `liveResponseStreamingChannel` (Slack channel name like `#pulse-results`, channel ID like `C0123456789`, or null to leave disabled) and optionally `liveResponseStreamingThreaded` (defaults true: responses thread under a single message instead of posting individually). The Windmill Slack bot must already be in the channel. If it isn't, the tool returns an error asking the user to add it. Anonymous pulses cannot stream. Warn the user and leave the field unset if they ask for both. - -### Step 5: Create Pulse (Optionally with Schedule) -Call `pulse_create` with: -- `name`: Generated from prompt/topics (required) -- `prompt`: What they want to learn (required) -- `discussionTopics`: Array of topics (required) -- `participants`: Employee filter from Step 3 (required) -- `anonymity`: NAMED, ANONYMOUS, or MANAGER_HIERARCHY (required; null defaults to NAMED) -- `durationMinutes`: Response duration in minutes, min 30 (optional, omit for no deadline) -- `notificationDelayMinutes`: Minutes before first **reminder** to non-respondents (optional, defaults to 1440). Do NOT change this based on when the user wants to send the pulse; it only controls reminder timing. -- `runUpdatesEnabled`: Whether creator gets updates from the agent throughout each run (required boolean) -- `liveResponseStreamingChannel`: Optional Slack channel reference (`#name`, `name`, or external ID like `C0123456789`) to stream individual responses into. Omit for no streaming. The Windmill Slack bot must already be in the channel. -- `liveResponseStreamingThreaded`: Optional boolean, defaults true. Only meaningful when a streaming channel is set. -- Optional schedule fields (use at most one): - - `scheduleManual: true` - - `scheduleOneTime` - - `scheduleRecurring` - - `scheduleStartDateAnniversary` +### Step 7: Confirm and Launch +Before launch, confirm in one message: +- The audience and its current count. +- The send timing: now, one time, recurring, or start-date anniversary. +- That a sent pulse cannot be recalled. -If schedule fields are omitted, create defaults to MANUAL schedule. +For a scheduled send or a dynamic filter (a team, a group, or the whole company), say that the participants are resolved again at each send. The current count is not a fixed list of future recipients. -### Step 6: Confirm Success and Offer Next Steps -Confirm creation with user. +Call `pulse_launch` only after the user approves this confirmation. An instruction to send or launch that came before this confirmation, for example in the creation request, sets the intended audience and timing. It is not the approval. Send the confirmation after the draft exists, then wait for the user's reply. Do not call `pulse_launch` to check a filter, to preview, or to save participants early: every successful call makes the pulse live. To check an audience, count it with the employee tools. Pass `pulseId`, `participants`, and exactly one timing field: -After confirming creation, offer these options: -1. Send now (immediately to participants) via `pulse_send_now` -2. Update schedule (one-time, recurring, or new hire anniversary) via `pulse_update` -3. Update additional configuration via `pulse_update` +| User intent | Timing field | +|-------------|--------------| +| "send now" | `sendNow: true` | +| "send next Monday at 9am" | `scheduleOneTime` | +| "every Friday" | `scheduleRecurring` | +| "30 days after their start date" | `scheduleStartDateAnniversary` | -Load managing_pulses_skill.md to guide them through whichever option they choose. +After launch, load managing_pulses_skill.md for repeat sends (`pulse_send_now`), participant or schedule changes (`pulse_update`), and results. diff --git a/plugins/windmill/skills/manage-pulse/SKILL.md b/plugins/windmill/skills/manage-pulse/SKILL.md index 19c9008..3c54cd5 100644 --- a/plugins/windmill/skills/manage-pulse/SKILL.md +++ b/plugins/windmill/skills/manage-pulse/SKILL.md @@ -1,6 +1,6 @@ --- name: manage-pulse -description: Context and guidance for managing existing pulses. Use when users want to pause, resume, send now, schedule, send nudges, update participants, or check on pulse results. +description: Context and guidance for managing pulses the user already has. Use when users want to change settings (name, prompt, requester or who the pulse comes from, owners), pause, resume, send now, schedule, send nudges, update participants, or check on pulse results. domain: pulses resourceFilename: managing_pulses_skill.md --- @@ -17,9 +17,17 @@ Use when users want to update, control, or check on existing pulses. - **Send Now**: Manually run a pulse immediately - **Nudge**: User triggered notification to respond to a pulse - **Requester**: Who the pulse appears to be from -- **Launched**: Whether or not the pulse has been taken out of its initial "draft" state. This happens when a non-manual schedule is set via `pulse_update`, or when `pulse_send_now` is used. +- **Launched**: Whether the pulse is live. A new pulse is a draft: it has no participants and sends to no one. Only `pulse_launch` launches it. Pulse read tools return `launched`. - **Live Response Streaming**: A separate feed that posts each employee response into a Slack channel as participants finish (independent of the end-of-run report). Disabled by default. The Windmill Slack bot must be a member of the channel. Anonymous pulses cannot stream. The system silently disables it if both are configured. +## Citing Pulses + +Write each pulse you name as a citation link: `[Pulse name](PULSE-)`, with the `pulseId` from the tool result. Do this in every reply that names the pulse, including a reply that confirms a create, test, launch, update, or send, and a reply that only asks a question about the pulse. The link replaces a quoted or bold pulse name. + +Pulse tools return people as keys. The `references.employees` block of the same result gives each key its `name` and `employeeId`. Write each person you name, also in a list of participants, as `[Name](EMPL-)`. + +The `PULSE-` and `EMPL-` prefixes are required: a link with a bare ID is wrong. A citation link needs a real ID from a tool result. A pulse or person that no tool returned gets a plain name with no link. Do not write a placeholder such as `PULSE-unknown` or `EMPL-?`, and do not look someone up only to cite them. + ## Statuses | Status | Meaning | @@ -27,16 +35,18 @@ Use when users want to update, control, or check on existing pulses. | ACTIVE | Runs on schedule | | PAUSED | Scheduling stopped, in-progress runs continue | -New pulses start ACTIVE. +New pulses start ACTIVE as unlaunched drafts. Status does not launch a draft. ## Tools | Tool | Purpose | Key Constraints | |------|---------|-----------------| | pulse_query | Find and load pulses | Supports pulseIds filter to load specific pulses | -| pulse_update | Change settings, status, or schedule | Non-manual schedule fields also launch an unlaunched pulse | -| pulse_send_now | Send pulse immediately | Requires ACTIVE status | -| pulse_send_nudge | User triggered notification | Requires `sessionId` and explicit `employeeIds`; 15-minute cooldown per employee globally | +| pulse_update | Change settings, status, or schedule | A draft rejects participant and schedule fields | +| pulse_test | Send a test run to the current user only | Works on a draft; never adds participants or launches | +| pulse_launch | Launch a draft with participants and send timing | One time per pulse; needs the user's approval | +| pulse_send_now | Send a launched pulse again immediately | Requires a launched, ACTIVE pulse | +| pulse_send_nudge | User triggered notification | Requires `sessionId`; omit `employeeIds` to remind everyone who hasn't responded; 15-minute cooldown per employee globally | | pulse_runs_query | List runs | - | | pulse_employee_runs_query | Get responses; supports `attentionStatus` filter (`NEEDS_MY_RESPONSE`, `AWAITING_PARTICIPANT_RESPONSE`, `MISSED_RESPONSE`) | - | | pulse_employees_query | List participants | Shows enrolled employees | @@ -44,9 +54,21 @@ New pulses start ACTIVE. | pulse_owner_update | Change owner access | - | | pulse_owner_remove | Remove owner | Cannot remove last WRITE owner | +## Drafts, Testing, and Launch + +A draft (`launched: false`) has no recipients. For a draft: + +- Content and settings edits: `pulse_update`. Participant and schedule fields are rejected. +- "Test it" / "let me try it": `pulse_test` only. The test goes to the user alone, also when they named a team or the whole company earlier. A test request never calls `pulse_launch`, `pulse_send_now`, or a participant update. After the test, end your turn and wait for the user's feedback. Never call `pulse_test` and `pulse_launch` in the same turn: "test it first, then send it" means launch only after the user has seen the test and says to send. +- "Send it" / "schedule it": `pulse_launch` with `participants` and exactly one timing field: `sendNow: true`, `scheduleOneTime`, `scheduleRecurring`, or `scheduleStartDateAnniversary`. + +Before `pulse_launch`, confirm the audience with its current count and the send timing, and say that a sent pulse cannot be recalled. For a scheduled send or a dynamic filter, say that the participants are resolved again at each send, so the current count is not a fixed list. Call `pulse_launch` only after the user approves this confirmation. An earlier instruction to send or launch sets the intended audience and timing; it is not the approval. Offer a test first if the user has not tested and has not declined one. + +`pulse_send_now`, participant updates, and schedule updates apply to launched pulses only. The launch confirmation applies to `pulse_launch` only. On a launched pulse (`launched: true`), a clear request to send now, send to a subset, pause, or resume needs no extra confirmation: do it, also when a scheduled run is close. A pause has no end date and no automatic resume: pause now and tell the user to ask when they want it resumed. Before a settings, participant, or schedule change, confirm which pulse you found when the user did not name it exactly. A launched pulse with no runs yet is still launched. + ## Schedule Types -Use `pulse_update` to set schedule with ONE of these fields: +For a launched pulse, use `pulse_update` to set schedule with ONE of these fields: | Field | When to Use | Example | |-----------|-------------|---------| @@ -70,7 +92,7 @@ Use `pulse_update` to set schedule with ONE of these fields: } **User intent mapping:** -- "send now" -> `pulse_send_now` +- "send now" -> `pulse_send_now` (draft: `pulse_launch` with `sendNow: true`) - "send next Monday at 9am" -> `pulse_update` with `scheduleOneTime` - "every Monday" -> `pulse_update` with `scheduleRecurring` - "every other Wednesday" -> `pulse_update` with `scheduleRecurring` interval=2 @@ -82,7 +104,7 @@ Use `pulse_update` to set schedule with ONE of these fields: - "don't thread the streamed responses" / "post each response separately" -> `pulse_update` with `liveResponseStreamingThreaded: false` - "group streamed responses in a thread again" -> `pulse_update` with `liveResponseStreamingThreaded: true` -When `scheduleOneTime`, `scheduleRecurring`, or `scheduleStartDateAnniversary` is set on an unlaunched pulse, update also launches it. +For a draft, pass the same schedule field to `pulse_launch` instead of `pulse_update`. ## Updating Participants @@ -166,7 +188,7 @@ Members with the send-all capability may use any supported participant filter. O | `notificationDelayMinutes` | Delay before first **reminder** to non-respondents (does NOT control when the pulse is sent) | 1440 (1 day) | | `pulse_deadline_update` | Change deadlines for existing runs or future defaults | Preview first | -CRITICAL: When a user says "send now" or "send immediately", use `pulse_send_now`. Do NOT change `notificationDelayMinutes`; that controls reminder timing, not pulse delivery. +CRITICAL: When a user says "send now" or "send immediately", use `pulse_send_now` (`pulse_launch` for a draft). Do NOT change `notificationDelayMinutes`; that controls reminder timing, not pulse delivery. ## Requester Options @@ -218,12 +240,11 @@ Do not leak the actual enum values to the user. Use friendly names and labels in **CRITICAL**: To send nudges, call `pulse_send_nudge`: 1. Find the pulse via `pulse_query` 2. Find the target run/session via `pulse_runs_query` (usually the latest run) -3. Determine the specific employees to nudge: call `pulse_employee_runs_query` with `pulseSessionIds: [sessionId]` and `attentionStatus: "AWAITING_PARTICIPANT_RESPONSE"` to get the non-responders for that session -4. Call `pulse_send_nudge` with `pulseId`, `sessionId`, and explicit `employeeIds` +3. To remind everyone who hasn't responded, call `pulse_send_nudge` without `employeeIds`; it returns only the count. On an anonymous pulse this is the only option +4. To nudge specific people, get them from `pulse_employee_runs_query` with `pulseSessionIds: [sessionId]` and `attentionStatus: "AWAITING_PARTICIPANT_RESPONSE"`, then pass their `employeeIds` 5. Report the result to the user Important: -- There is no "nudge all non-responders" shortcut parameter. You must send explicit `employeeIds`. - `AWAITING_PARTICIPANT_RESPONSE` is the right filter for "everyone who still hasn't responded". Scope it to the session to avoid pulling non-responders from other runs. ## Checking on Pulse Responses @@ -233,7 +254,7 @@ Use `pulse_employee_runs_query` with the `attentionStatus` filter to answer comm | User Intent | Filter | Notes | |-------------|--------|-------| | "What pulses do I need to respond to?" / "What needs my attention?" | `attentionStatus: "NEEDS_MY_RESPONSE"` | Auto-scopes to the current employee. Only includes active pulses with an opened chat, no response yet, and an open/no deadline. Do not also pass an `employee` filter. | -| "Who still hasn't responded?" / "Who's outstanding on this pulse?" | `attentionStatus: "AWAITING_PARTICIPANT_RESPONSE"` | Combine with `pulseIds` or `pulseSessionIds` to scope to a specific pulse or run. Add an `employee` filter for manager/admin views over a subset. | +| "Who still hasn't responded?" / "Who's outstanding on this pulse?" | `attentionStatus: "AWAITING_PARTICIPANT_RESPONSE"` | Combine with `pulseIds` or `pulseSessionIds` to scope to a specific pulse or run. Add an `employee` filter for manager/admin views over a subset. On an anonymous pulse, it matches only your own run. | | "Who missed the deadline?" / "Who never responded before it expired?" | `attentionStatus: "MISSED_RESPONSE"` | Expired sessions only. | Pair with `pulse_employee_runs_count` when the user only wants the number (e.g., "how many people still owe me a response?"). @@ -244,5 +265,6 @@ For pulse-level rollups (counts of completed runs, last run timestamp, etc.) use - Anonymity can change with `pulse_update` only before launch. It is locked after launch; if the user needs a different mode, explain that they must create a new pulse. - Schedule updates: pulse must not be COMPLETED or ARCHIVED -- Send now: pulse must be ACTIVE +- Send now: pulse must be launched and ACTIVE +- Launch: one time per pulse, after the user approves the audience and send timing - Cannot remove last owner with WRITE access