14 KiB
Sweep First-Run Interview
Loaded by SKILL.md when /ce-sweep runs with no feedback_sources configured. Captures the setup that will be merged into <repo-root>/.compound-engineering/config.local.yaml (the unified CE local config, gitignored, machine-local) and re-read on every subsequent run.
This interview is interactive only. The caller refuses first-run setup in headless mode — a scheduled or piped run with no config aborts and tells the user to run /ce-sweep interactively once. Do not attempt to infer sources, actions, or approvals without asking.
Interaction Method
Ask one question at a time using the platform's blocking question tool: AskUserQuestion in Claude Code (call ToolSearch with select:AskUserQuestion first if its schema isn't loaded), request_user_input in Codex, ask_question in Antigravity CLI (agy), ask_user in Pi (requires the pi-ask-user extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors — never silently skip a question or assume a default without surfacing it.
Overall Rules
- One source at a time, fully. Sections 1-3 form a per-source loop: for each source, capture its identity, its acknowledgment actions plus standing approval, and its sensitivity flag before moving to the next source. Do not batch these across sources — a user answering "approve writes" needs to know which source they are approving.
- Standing approval is consent, captured verbatim. Section 2's approval question authorizes source-side writes (Slack reactions, GitHub labels) on every future run with no per-run confirmation. Record the literal yes/no. A "no" is not a failure — it leaves that source read-only.
- Defaults are shown, not silently applied. Every question with a default states the default in the question. The user accepts or overrides; you never pick for them.
- Capture in the user's own terms. Config ids, emoji names, and label names are read by the whole team and used verbatim by the connectors — record exactly what the user gives.
1. Sources (repeatable loop)
Opening framing: "Let's wire up the feedback sources this sweep will watch. We'll add them one at a time — you can add as many as you want."
For each source, ask two things:
- Source type — one of:
slack— a Slack channelgithub-issues— a GitHub repository's issuesemail-experimental— an email account/folder (experimental; stored in config astype: email)
- Identity — depends on the type:
- Slack: the channel ID (e.g.
C0XXXXXXX, not the#name). Stored astarget. - GitHub: the repo as
owner/repo. Stored astarget. - Email: the account plus a folder/label hint (e.g.
feedback@acme.com / Inbox). Stored astarget.
- Slack: the channel ID (e.g.
Then assign the source a short config id — a stable, lowercase, hyphenated handle the state file and reports use to name this source (e.g. slack-alpha, gh-issues). Suggest one derived from the type, let the user override. Ids must be unique within feedback_sources.
After each source's actions and sensitivity are captured (sections 2-3), ask: "Add another source?" Loop until the user is done. At least one source is required to proceed.
Capture per source: type (slack | github-issues | email), id (short handle), target (channel ID / owner/repo / mailbox hint).
2. Acknowledgment actions + standing approval (per source)
Every source carries two source-side actions the sweep can perform, plus a standing approval that governs whether it may perform them unattended.
Ask the acknowledgment action — what the sweep does to mark an item seen on its source:
- Slack: an emoji reaction name. Default
eyes. - GitHub: a label to apply. Default
feedback:ack. - Email: none. Email items are tracked only in state; there is no source-side ack. Skip this question for email sources and note that.
Ask the close-out action — what the sweep does to mark an item resolved on its source:
- Slack: an emoji reaction. Default
white_check_mark. - GitHub: a label. Default
feedback:resolved. - Email: none — email items stay state-tracked only; explain there is no source-side close-out.
Then ask the standing-approval question, verbatim:
"Do you approve the sweep performing these actions — applying the acknowledgment and close-out
{{action names}}on{{source id}}— on every future run, without asking you again each time? Yes authorizes source-side writes for this source going forward. No keeps this source read-only: the sweep ingests and triages items but never touches the source, and items land asack_deferredfor you to action manually."
Record the literal answer:
- Yes ->
approved: true. The sweep may apply the ack and close-out actions on this source unattended. - No ->
approved: false. The source is read-only; its items are tracked asack_deferredand no reaction/label is ever written.
For email sources there are no source-side actions, so approval is moot — record approved: false and note the source is inherently read-only.
Capture per source: ack_action (emoji/label name, or omit for email), closeout_action (emoji/label name, or omit for email), approved (true | false).
3. Sensitive flag (per source)
Ask: "Should item content from {{source id}} be withheld from committed state and from plan text? Say yes when the source can carry screen recordings, PII, customer data, or anything you don't want written to a file that may be committed or shared. When yes, the sweep drops item body and quote before writing state — only titles, urls, ids, and status persist. Default is no."
- No (default) ->
sensitive: false. Full item content is retained in state and available to plans. - Yes ->
sensitive: true. The state engine dropsbodyandquoteat write time for this source's items, and plans reference items by id/title/url only.
Capture per source: sensitive (true | false).
4. State location
Ask where the sweep's state file lives:
- Committed to the repo (recommended when multiple agents or machines share branches — one source of truth everyone reads and writes). Sets
sweep_state_pathto the committed defaultdocs/feedback-sweep/state.yml. - Machine-local under
/tmp(solo setups; keeps sweep bookkeeping out of the repo, no commit noise). Setssweep_state_pathto/tmp/compound-engineering/ce-sweep/<repo-slug>/state.yml, where<repo-slug>is derived from the repo (e.g. the basename of the repo root).
Let the user override the path if they want a different location. If they pick machine-local, note that a fresh checkout or a teammate's machine will not see this state — it is per-machine by design.
Capture: sweep_state_path (string).
5. Acknowledgment cap
Ask: "What's the most acknowledgments the sweep may perform on a single source in one run before it pauses? This is a circuit breaker against a runaway sweep spamming a channel or issue tracker. When the cap is hit, an interactive run pauses and asks you; a headless run stops acknowledging and defers the rest. Default is 25."
Capture: sweep_ack_cap (integer, default 25).
6. Shared branch (only if committed state)
Skip this section entirely if the user chose machine-local state in section 4 — the shared-branch topology only applies to committed state.
Ask: "Is this a multi-agent setup where several checkouts push the sweep state to a shared docs branch? Answer yes only if more than one machine or agent commits and pushes to the same branch. Default is no — a single checkout committing locally."
- No (default) ->
sweep_shared_branch: false. The single-writer lease serializes overlapping sweeps within one checkout. - Yes ->
sweep_shared_branch: true. Explain: the lease becomes push-gated — before any source-side write, the sweep commits and pushes the lease acquisition on the shared branch and confirms its writer won, making the lease a repo-wide mutex across machines.
Capture: sweep_shared_branch (true | false).
7. Legacy import (optional)
Offer to seed state from an existing legacy feedback-tracking file so prior work is not re-ingested and already-acknowledged items are not acknowledged again.
Ask: "Do you have an existing feedback state file to import — for example a prior dogfood tracker like docs/dogfood-reports/cora-v2-alpha-feedback-state.yml? Importing carries over its cursors and items so the first sweep skips what's already been processed. Skip if this is a clean start."
-
No / skip -> proceed to section 8.
-
Yes -> ask for the file path. Then build a
--source-map: for each legacy channel/source id in the file, pair it with the configured source id from section 1 (the short name the live connector reads by), as a JSON object like{"C0AQLMQBGBD":"slack-alpha"}. This is load-bearing — without it, an importedC0AQLMQBGBDcursor lands underC0AQLMQBGBDwhile the connector reads underslack-alpha, orphaning the cursor and re-ingesting everything on the first sweep. Run the import from this skill's directory; setSKILL_DIRinline to the absolute path of the directory containing theSKILL.mdyou loaded:SKILL_DIR="<absolute path of this skill's directory>"; python3 "$SKILL_DIR/scripts/sweep-state.py" import-legacy --state <sweep_state_path> --file <legacy-path> --source-map '{"<legacy-id>":"<config-source-id>"}'where
<sweep_state_path>is the value captured in section 4 and<legacy-path>is the file the user named. Omit--source-maponly when the legacy ids already equal the configured source ids. Report thecursors_importedanditems_importedcounts the command returns. The import is additive and best-effort: it maps what matches known shapes and skips the rest. It does not re-ingest source content and does not re-acknowledge imported items — mapped cursors carry forward so already-processed items stay processed.
8. Write config
Merge the captured settings into <repo-root>/.compound-engineering/config.local.yaml. Resolve the repo root with git rev-parse --show-toplevel.
- If the directory or file does not exist, create
.compound-engineering/and write the file. - If the file exists, merge the sweep keys into the existing YAML, preserving every unrelated key untouched (e.g.
pulse_*,plan_*). Only add or update the sweep keys. - If
.compound-engineering/config.local.yamlis not already covered by the repo's.gitignore, offer to add the entry before writing.
Write these keys (see "Config File Shape" below for the exact form):
feedback_sources— the list of source maps assembled across sections 1-3.sweep_state_path— from section 4.sweep_ack_cap— from section 5.sweep_shared_branch— from section 6 (defaultfalse; only meaningful with committed state).
Then surface the resulting Sweep section to the user in chat and offer one round of edits.
9. Schedule offer
Ask: "Want the sweep to run on a recurring schedule so feedback gets triaged automatically, or run it on demand? On-demand works fully without a schedule."
- On demand -> nothing to register. Note that
/ce-sweepis ready to run any time. - Recurring -> hand off to whichever scheduling primitive the harness exposes — the in-plugin
scheduleskill if it is installed, otherwise name the platform-native mechanism (cron, GitHub Actions, the host's own automation) and emit a brief hint of what would need to run. The registered invocation must includemode:headless— e.g./ce-sweep mode:headless— so the scheduled run knows it is unattended and defers instead of prompting. Never schedule inline; always hand off to the scheduling primitive.
Declining a schedule leaves on-demand use fully working.
End the interview: tell the user setup is complete and the first sweep can run now with /ce-sweep.
Config File Shape
After the interview completes, merge these flat keys into <repo-root>/.compound-engineering/config.local.yaml, preserving any unrelated keys already present.
# --- Sweep (ce-sweep) ---
feedback_sources:
- { type: slack, id: slack-alpha, target: C0XXXXXXX, ack_action: eyes, closeout_action: white_check_mark, sensitive: false, approved: true }
- { type: github-issues, id: gh-issues, target: owner/repo, ack_action: "feedback:ack", closeout_action: "feedback:resolved", sensitive: false, approved: true }
sweep_state_path: docs/feedback-sweep/state.yml # committed (multi-agent) or /tmp path (solo)
sweep_ack_cap: 25 # max acks per source per run before the circuit breaker
sweep_lease_ttl_minutes: 60 # single-writer lease staleness threshold; not asked interactively, tunable here
sweep_shared_branch: false # true: push-gated lease for shared-docs-branch topology
Notes:
- Each
feedback_sourcesentry carries:type(slack|github-issues|email),id(short handle),target(channel ID /owner/repo/ mailbox hint),ack_actionandcloseout_action(emoji/label names; omit both for email),sensitive(truewithholds body/quote from committed state and plan text), andapproved(standing approval for source-side writes;falsekeeps the source read-only withack_deferreditems). feedback_sourcesis a generic key — other skills may read this list.sweep_lease_ttl_minutesis not asked in the interview; it is written with its default of60and left as a tunable the user can edit.- Email sources are read-only: omit
ack_action/closeout_action, and recordapproved: false.