Files
2026-07-13 12:20:01 +08:00

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

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. Source type — one of:
    • slack — a Slack channel
    • github-issues — a GitHub repository's issues
    • email-experimental — an email account/folder (experimental; stored in config as type: email)
  2. Identity — depends on the type:
    • Slack: the channel ID (e.g. C0XXXXXXX, not the #name). Stored as target.
    • GitHub: the repo as owner/repo. Stored as target.
    • Email: the account plus a folder/label hint (e.g. feedback@acme.com / Inbox). Stored as target.

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 as ack_deferred for 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 as ack_deferred and 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 drops body and quote at 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_path to the committed default docs/feedback-sweep/state.yml.
  • Machine-local under /tmp (solo setups; keeps sweep bookkeeping out of the repo, no commit noise). Sets sweep_state_path to /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 imported C0AQLMQBGBD cursor lands under C0AQLMQBGBD while the connector reads under slack-alpha, orphaning the cursor and re-ingesting everything on the first sweep. Run the import from this skill's directory; set SKILL_DIR inline to the absolute path of the directory containing the SKILL.md you 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-map only when the legacy ids already equal the configured source ids. Report the cursors_imported and items_imported counts 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.yaml is 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 (default false; 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-sweep is ready to run any time.
  • Recurring -> hand off to whichever scheduling primitive the harness exposes — the in-plugin schedule skill 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 include mode: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_sources entry carries: type (slack | github-issues | email), id (short handle), target (channel ID / owner/repo / mailbox hint), ack_action and closeout_action (emoji/label names; omit both for email), sensitive (true withholds body/quote from committed state and plan text), and approved (standing approval for source-side writes; false keeps the source read-only with ack_deferred items).
  • feedback_sources is a generic key — other skills may read this list.
  • sweep_lease_ttl_minutes is not asked in the interview; it is written with its default of 60 and left as a tunable the user can edit.
  • Email sources are read-only: omit ack_action/closeout_action, and record approved: false.