diff --git a/plugins/compound-engineering/AGENTS.md b/plugins/compound-engineering/AGENTS.md index b947a66..c9e226c 100644 --- a/plugins/compound-engineering/AGENTS.md +++ b/plugins/compound-engineering/AGENTS.md @@ -68,6 +68,10 @@ Important: Just because the developer's installed plugin may be out of date, it' **Why `ce:`?** Claude Code has built-in `/plan` and `/review` commands. The `ce:` namespace (short for compound-engineering) makes it immediately clear these commands belong to this plugin. +## Known External Limitations + +**Proof HITL surfaces a ghost "AI collaborator" agent** (noted 2026-04-16, may change): The Proof API auto-joins any header-less `/state` read under a synthetic `ai:auto-` identity, so docs created by the `skills/proof/` HITL workflow show a phantom participant alongside `Compound Engineering`. The only way to suppress it is to set `ownerId: "agent:ai:compound-engineering"` on create — but that transfers document ownership to the agent and prevents the user from claiming it into their Proof library, so we don't use it. Treat as cosmetic noise; don't reintroduce the `ownerId` workaround. Tracked upstream: https://github.com/EveryInc/proof/issues/951. + ## Skill Compliance Checklist When adding or modifying skills, verify compliance with the skill spec: @@ -113,6 +117,21 @@ Keep rationale at the highest-level location that covers it; restate behavioral - [ ] When a skill needs to ask the user a question, instruct use of the platform's blocking question tool and name the known equivalents (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini) - [ ] Include a fallback for environments without a question tool (e.g., present numbered options and wait for the user's reply before proceeding) +### Interactive Question Tool Design + +Design rules for blocking question menus (`AskUserQuestion` / `request_user_input` / `ask_user`). Violations silently degrade the UX in harnesses where secondary description text is hidden or labels are truncated. + +- [ ] Each option label must be self-contained — some harnesses render only the label, not the accompanying description; the label alone must convey what the option does +- [ ] Keep total options to 4 or fewer (`AskUserQuestion` caps at 4 across platforms we target) +- [ ] Do not offer "still working" / "I'll come back" options — the blocking tool already waits; such options are no-op wrappers. If the user needs to go do something, they simply leave the prompt open +- [ ] Refer to the agent in third person ("the agent") in labels and stems — first-person "me" / "I'll" is ambiguous in a tool-mediated exchange where it's unclear whether the speaker is the user, the agent, or the tool +- [ ] Phrase labels from the user's intent, not the system's internal state — each option should complete "I want to ___" from the user's POV; avoid leaking mode names like "end-sync" or "phase-3" into labels +- [ ] Use the question stem as a teaching surface for first-time mechanics — teach the mechanic there (e.g., "Highlight text in Proof to leave a comment"), not in option descriptions that may be hidden +- [ ] When renaming a display label, rename its matching routing block (`**If user selects "X":**`) in the same edit — the model matches selections by verbatim label string, so a missed rename silently breaks routing +- [ ] Front-load the distinguishing word when options share a prefix — "Proceed to planning" vs "Proceed directly to work" look identical when truncated; put the differentiator in the first 3-4 words +- [ ] Name the target when an artifact is ambiguous — "save to my local file" beats "save to my file" when multiple artifacts (Proof doc, local markdown, cached copy) coexist +- [ ] Keep voice consistent across a menu — mixing imperative ("Pause") with user-voice status ("I'm done — save…") within the same set reads as authored by different agents + ### Cross-Platform Task Tracking - [ ] When a skill needs to create or track tasks, describe the intent (e.g., "create a task list") and name the known equivalents (`TaskCreate`/`TaskUpdate`/`TaskList` in Claude Code, `update_plan` in Codex) diff --git a/plugins/compound-engineering/skills/ce-brainstorm/references/handoff.md b/plugins/compound-engineering/skills/ce-brainstorm/references/handoff.md index 956a739..9454d75 100644 --- a/plugins/compound-engineering/skills/ce-brainstorm/references/handoff.md +++ b/plugins/compound-engineering/skills/ce-brainstorm/references/handoff.md @@ -23,7 +23,7 @@ Present only the options that apply, keeping the total at 4 or fewer: - **Proceed to planning (Recommended)** - Move to `/ce:plan` for structured implementation planning. Shown only when `Resolve Before Planning` is empty. - **Proceed directly to work** - Skip planning and move to `/ce:work`; suited to lightweight, well-defined changes. Shown only when `Resolve Before Planning` is empty **and** scope is lightweight, success criteria are clear, scope boundaries are clear, and no meaningful technical or research questions remain (the "direct-to-work gate"). - **Continue the brainstorm** - Answer more clarifying questions to tighten scope, edge cases, and preferences. Always shown. -- **View & share in Proof** - Open the requirements doc in Proof to read, comment, collaborate, and share a link. Shown only when a requirements document exists **and** the direct-to-work gate is not satisfied (when both conditions collide, `Proceed directly to work` takes priority and Proof becomes reachable via free-form request). +- **Open in Proof (web app) — review and comment to iterate with the agent** - Open the doc in Every's Proof editor, iterate with the agent via comments, or copy a link to share with others. Shown only when a requirements document exists **and** the direct-to-work gate is not satisfied (when both conditions collide, `Proceed directly to work` takes priority and Proof becomes reachable via free-form request). - **Done for now** - Pause; the requirements doc is saved and can be resumed later. Always shown. **Surface additional document review contextually, not as a menu fixture:** When the prior document-review pass surfaced residual P0/P1 findings that the user has not addressed, mention them adjacent to the menu and offer another review pass in prose (e.g., "Document review flagged 2 P1 findings you may want to address — want me to run another pass?"). Do not add it to the option list. @@ -40,20 +40,25 @@ Immediately run `/ce:work` in the current session using the finalized brainstorm **If user selects "Continue the brainstorm":** Return to Phase 1.3 (Collaborative Dialogue) and continue asking the user clarifying questions one at a time to further refine scope, edge cases, constraints, and preferences. Continue until the user is satisfied, then return to Phase 4. Do not show the closing summary yet. -**If user selects "View & share in Proof":** +**If user selects "Open in Proof (web app) — review and comment to iterate with the agent":** -```bash -CONTENT=$(cat docs/brainstorms/YYYY-MM-DD--requirements.md) -TITLE="Requirements: " -RESPONSE=$(curl -s -X POST https://www.proofeditor.ai/share/markdown \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg title "$TITLE" --arg markdown "$CONTENT" --arg by "ai:compound" '{title: $title, markdown: $markdown, by: $by}')") -PROOF_URL=$(echo "$RESPONSE" | jq -r '.tokenUrl') -``` +Load the `proof` skill in HITL-review mode with: -Display the URL prominently: `View & collaborate in Proof: ` +- **source file:** `docs/brainstorms/YYYY-MM-DD--requirements.md` +- **doc title:** `Requirements: ` +- **identity:** `ai:compound-engineering` / `Compound Engineering` +- **recommended next step:** `/ce:plan` (shown in the proof skill's final terminal output) -If the curl fails, skip silently. Then return to the Phase 4 options. +Follow `references/hitl-review.md` in the proof skill. It uploads the doc, prompts the user for review in Proof's web UI, ingests each thread by reading it fresh and replying in-thread, applies agreed edits as tracked suggestions, and syncs the final markdown back to the source file atomically on proceed. + +When the proof skill returns control: + +- `status: proceeded` with `localSynced: true` → the requirements doc on disk now reflects the review. Return to the Phase 4 options and re-render the menu (the doc may have changed substantially during review, so option eligibility can shift — re-evaluate `Resolve Before Planning`, direct-to-work gate, and residual document-review findings against the updated doc). +- `status: proceeded` with `localSynced: false` → the reviewed version lives in Proof at `docUrl` but the local copy is stale. Offer to pull the Proof doc to `localPath` using the proof skill's Pull workflow. Re-render the Phase 4 menu after the pull completes (or is declined). If the pull was declined, include a one-line note above the menu that `` is stale vs. Proof — otherwise `Proceed to planning` / `Proceed directly to work` will silently read the pre-review copy. +- `status: done_for_now` → the doc on disk may be stale if the user edited in Proof before leaving. Offer to pull the Proof doc to `localPath` so the local requirements file stays in sync, then return to the Phase 4 options. If the pull was declined, include the stale-local note above the menu. `done_for_now` means the user stopped the HITL loop without syncing — it does not mean they ended the whole brainstorm; they may still want to proceed to planning or continue the brainstorm. +- `status: aborted` → fall back to the Phase 4 options without changes. + +If the initial upload fails (network error, Proof API down), retry once after a short wait. If it still fails, tell the user the upload didn't succeed and briefly explain why, then return to the Phase 4 options — don't leave them wondering why the option did nothing. **If the user asks to run another document review** (either from the contextual prompt when P0/P1 findings remain, or by free-form request): diff --git a/plugins/compound-engineering/skills/ce-brainstorm/references/universal-brainstorming.md b/plugins/compound-engineering/skills/ce-brainstorm/references/universal-brainstorming.md index 231d009..147c566 100644 --- a/plugins/compound-engineering/skills/ce-brainstorm/references/universal-brainstorming.md +++ b/plugins/compound-engineering/skills/ce-brainstorm/references/universal-brainstorming.md @@ -45,8 +45,11 @@ When the conversation has enough material to narrow — reflect back what you've **Always synthesize a summary in the chat.** Before offering any next steps, reflect back what emerged: key decisions, the direction chosen, open threads, and any assumptions made. This is the primary output of the brainstorm — the user should be able to read the summary and know what they landed on. -**Then offer next steps** using the platform's question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini): +**Then offer next steps** using the platform's question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini). If no question tool is available, present the numbered options in chat and wait for the user's reply before proceeding. + +**Question:** "Brainstorm wrapped. What would you like to do next?" + - **Create a plan** → hand off to `/ce:plan` with the decided goal and constraints - **Save summary to disk** → write the summary as a markdown file in the current working directory -- **View & share in Proof** → load the `proof` skill to open the summary in Proof for reading, collaborating, and sharing a link +- **Open in Proof (web app) — review and comment to iterate with the agent** → load the `proof` skill to open the doc in Every's Proof editor, iterate with the agent via comments, or copy a link to share with others - **Done** → the conversation was the value, no artifact needed diff --git a/plugins/compound-engineering/skills/ce-ideate/SKILL.md b/plugins/compound-engineering/skills/ce-ideate/SKILL.md index d61b658..6471037 100644 --- a/plugins/compound-engineering/skills/ce-ideate/SKILL.md +++ b/plugins/compound-engineering/skills/ce-ideate/SKILL.md @@ -62,7 +62,7 @@ If a relevant doc exists, ask whether to: If continuing: - read the document - summarize what has already been explored -- preserve previous idea statuses and session log entries +- preserve previous idea statuses - update the existing file instead of creating a duplicate #### 0.2 Interpret Focus and Volume diff --git a/plugins/compound-engineering/skills/ce-ideate/references/post-ideation-workflow.md b/plugins/compound-engineering/skills/ce-ideate/references/post-ideation-workflow.md index eb7bfcf..c25fd0a 100644 --- a/plugins/compound-engineering/skills/ce-ideate/references/post-ideation-workflow.md +++ b/plugins/compound-engineering/skills/ce-ideate/references/post-ideation-workflow.md @@ -96,32 +96,29 @@ focus: | # | Idea | Reason Rejected | |---|------|-----------------| | 1 | | | - -## Session Log -- YYYY-MM-DD: Initial ideation — generated, survived ``` If resuming: - update the existing file in place -- append to the session log - preserve explored markers ## Phase 6: Refine or Hand Off -After presenting the results, ask what should happen next. +After presenting the results, ask what should happen next using the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini). If no question tool is available, present the numbered options in chat and wait for the user's reply before proceeding. + +**Question:** "Ideation saved. What's next?" Offer these options: -1. brainstorm a selected idea -2. refine the ideation -3. share to Proof -4. end the session +1. **Brainstorm a selected idea** — hand off to `ce:brainstorm` with the selected idea as the seed +2. **Refine the ideation** — add, re-evaluate, or deepen ideas before handing off +3. **Open in Proof (web app) — review and comment to iterate with the agent** — open the doc in Every's Proof editor, iterate via comments, or copy a link to share with others +4. **End the session** — no further action; the ideation doc is saved ### 6.1 Brainstorm a Selected Idea If the user selects an idea: - write or update the ideation doc first - mark that idea as `Explored` -- note the brainstorm date in the session log - invoke `ce:brainstorm` with the selected idea as the seed Do **not** skip brainstorming and go straight to planning from ideation output. @@ -136,13 +133,26 @@ Route refinement by intent: After each refinement: - update the ideation document before any handoff, sharing, or session end -- append a session log entry -### 6.3 Share to Proof +### 6.3 Open in Proof (web app) -If requested, share the ideation document using the standard Proof markdown upload pattern already used elsewhere in the plugin. +If requested, hand off the ideation document to the proof skill in HITL review mode. This uploads the doc, runs an iterative review loop (user annotates in Proof, agent ingests feedback and applies tracked edits), and syncs the reviewed markdown back to `docs/ideation/`. -Return to the next-step options after sharing. +Load the `proof` skill in HITL-review mode with: + +- **source file:** the ideation document path written in Phase 5 (e.g., `docs/ideation/YYYY-MM-DD--ideation.md`) +- **doc title:** `Ideation: ` or the H1 of the ideation doc +- **identity:** `ai:compound-engineering` / `Compound Engineering` +- **recommended next step:** `/ce:brainstorm` (shown in the proof skill's final terminal output) + +If the initial upload fails (network error, Proof API down), retry once after a short wait. If it still fails, tell the user the upload didn't succeed and briefly explain why, then return to the next-step options — don't leave them wondering why the option did nothing. + +When the proof skill returns control: + +- `status: proceeded` with `localSynced: true` → the ideation doc on disk now reflects the review. Return to the next-step options. +- `status: proceeded` with `localSynced: false` → the reviewed version lives in Proof at `docUrl` but the local copy is stale. Offer to pull the Proof doc to `localPath` using the proof skill's Pull workflow. Return to the next-step options; if the pull was declined, include a one-line note above the menu that `` is stale vs. Proof so the next handoff doesn't read the old content silently. +- `status: done_for_now` → the doc on disk may be stale if the user edited in Proof before leaving. Offer to pull the Proof doc to `localPath` so the local ideation artifact stays in sync, then return to the next-step options. `done_for_now` means the user stopped the HITL loop — it does not mean they ended the whole ideation session; they may still want to brainstorm or refine. If the pull was declined, include the stale-local note above the menu. +- `status: aborted` → fall back to the next-step options without changes. ### 6.4 End the Session diff --git a/plugins/compound-engineering/skills/ce-plan/references/plan-handoff.md b/plugins/compound-engineering/skills/ce-plan/references/plan-handoff.md index d88b082..ad6398c 100644 --- a/plugins/compound-engineering/skills/ce-plan/references/plan-handoff.md +++ b/plugins/compound-engineering/skills/ce-plan/references/plan-handoff.md @@ -38,7 +38,7 @@ After document-review completes, present the options using the platform's blocki **Options:** 1. **Start `/ce:work`** (recommended) - Begin implementing this plan in the current session 2. **Create Issue** - Create a tracked issue from this plan in your configured issue tracker (GitHub or Linear) -3. **View & share in Proof** - Open the plan in Proof to read, comment, collaborate, and share a link +3. **Open in Proof (web app) — review and comment to iterate with the agent** - Open the doc in Every's Proof editor, iterate with the agent via comments, or copy a link to share with others 4. **Done for now** - Pause; the plan file is saved and can be resumed later **Surface additional document review contextually, not as a menu fixture:** When the prior document-review pass surfaced residual P0/P1 findings that the user has not addressed, mention them adjacent to the menu and offer another review pass in prose (e.g., "Document review flagged 2 P1 findings you may want to address — want me to run another pass before you pick?"). Do not add it to the option list. @@ -46,16 +46,21 @@ After document-review completes, present the options using the platform's blocki Based on selection: - **Start `/ce:work`** -> Call `/ce:work` with the plan path - **Create Issue** -> Follow the Issue Creation section below -- **View & share in Proof** -> Upload the plan: - ```bash - CONTENT=$(cat docs/plans/.md) - TITLE="Plan: " - RESPONSE=$(curl -s -X POST https://www.proofeditor.ai/share/markdown \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg title "$TITLE" --arg markdown "$CONTENT" --arg by "ai:compound" '{title: $title, markdown: $markdown, by: $by}')") - PROOF_URL=$(echo "$RESPONSE" | jq -r '.tokenUrl') - ``` - Display `View & collaborate in Proof: ` if successful, then return to the options +- **Open in Proof (web app) — review and comment to iterate with the agent** -> Load the `proof` skill in HITL-review mode with: + - source file: `docs/plans/.md` + - doc title: `Plan: ` + - identity: `ai:compound-engineering` / `Compound Engineering` + - recommended next step: `/ce:work` (shown in the proof skill's final terminal output) + + Follow `references/hitl-review.md` in the proof skill. It uploads the plan, prompts the user for review in Proof's web UI, ingests each thread by reading it fresh and replying in-thread, applies agreed edits as tracked suggestions, and syncs the final markdown back to the plan file atomically on proceed. + + When the proof skill returns: + - `status: proceeded` with `localSynced: true` -> the plan on disk now reflects the review. Re-run `document-review` on the updated plan before re-rendering the menu — HITL can materially rewrite the plan body, so the prior document-review pass no longer covers the current file and section 5.3.8 requires a review before any handoff option is offered. Then return to the post-generation options with the refreshed residual findings. + - `status: proceeded` with `localSynced: false` -> the reviewed version lives in Proof at `docUrl` but the local copy is stale. Offer to pull the Proof doc to `localPath` using the proof skill's Pull workflow. If the pull happened, re-run `document-review` on the pulled file before re-rendering the options (same 5.3.8 rationale — the local plan was materially updated by the pull). If the pull was declined, include a one-line note above the menu that `` is stale vs. Proof — otherwise `Start /ce:work` or `Create Issue` will silently use the pre-review copy. + - `status: done_for_now` -> the plan on disk may be stale if the user edited in Proof before leaving. Offer to pull the Proof doc to `localPath` so the local plan file stays in sync. If the pull happened, re-run `document-review` on the pulled file before re-rendering the options (same 5.3.8 rationale). If the pull was declined, include the stale-local note above the menu. `done_for_now` means the user stopped the HITL loop — it does not mean they ended the whole plan session; they may still want to start work or create an issue. + - `status: aborted` -> fall back to the options without changes. + + If the initial upload fails (network error, Proof API down), retry once after a short wait. If it still fails, tell the user the upload didn't succeed and briefly explain why, then return to the options — don't leave them wondering why the option did nothing. - **Done for now** -> Display a brief confirmation that the plan file is saved and end the turn - **If the user asks for another document review** (either from the contextual prompt when P0/P1 findings remain, or by free-form request) -> Load the `document-review` skill with the plan path for another pass, then return to the options - **Other** -> Accept free text for revisions and loop back to options diff --git a/plugins/compound-engineering/skills/ce-plan/references/universal-planning.md b/plugins/compound-engineering/skills/ce-plan/references/universal-planning.md index 4f93992..5773008 100644 --- a/plugins/compound-engineering/skills/ce-plan/references/universal-planning.md +++ b/plugins/compound-engineering/skills/ce-plan/references/universal-planning.md @@ -93,6 +93,8 @@ Example: A date night plan should present 2-3 restaurant options, 2-3 activity o After structuring the plan, ask the user how they want to receive it using the platform's question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini). Otherwise, present numbered options in chat. +**Question:** "Plan ready. How would you like to receive it?" + **Options:** 1. **Save to disk** — Write the plan as a markdown file. Ask where: @@ -103,8 +105,8 @@ After structuring the plan, ask the user how they want to receive it using the p - Use filename convention: `YYYY-MM-DD--plan.md` - Start the document with a `# Title` heading, followed by `Created: YYYY-MM-DD` on the next line. No YAML frontmatter. -2. **Share to Proof** — View the plan on the web and get a shareable link. Load the `proof` skill to create and share the document. Useful for sharing with others who aren't in the terminal. +2. **Open in Proof (web app) — review and comment to iterate with the agent** — Open the doc in Every's Proof editor, iterate with the agent via comments, or copy a link to share with others. Load the `proof` skill to create and open the document. -3. **Both** — Save to disk and share to Proof. +3. **Save to disk AND open in Proof** — Do both: write the markdown file to disk and open the doc in Proof for review. Do not offer `/ce:work` (software-only) or issue creation (not applicable to non-software plans). diff --git a/plugins/compound-engineering/skills/proof/SKILL.md b/plugins/compound-engineering/skills/proof/SKILL.md index f4f5c4f..c4f74af 100644 --- a/plugins/compound-engineering/skills/proof/SKILL.md +++ b/plugins/compound-engineering/skills/proof/SKILL.md @@ -1,6 +1,6 @@ --- name: proof -description: Create, edit, comment on, and share markdown documents via Proof's web API and local bridge. Use when asked to "proof", "share a doc", "create a proof doc", "comment on a document", "suggest edits", "review in proof", or when given a proofeditor.ai URL. +description: Create, edit, comment on, share, and run human-in-the-loop iteration loops over markdown documents via Proof's web API. Use when asked to "proof", "share a doc", "create a proof doc", "comment on a document", "suggest edits", "review in proof", "iterate on this doc in proof", "HITL this doc", "sync a Proof doc to local", when a caller needs an HITL review loop over a local markdown file (e.g., ce-brainstorm, ce-ideate, or ce-plan handoff), or when given a proofeditor.ai URL. Prefer this skill for any workflow whose output is a Proof URL or that uses a Proof doc as the review surface, even when not named explicitly. allowed-tools: - Bash - Read @@ -15,6 +15,19 @@ Proof is a collaborative document editor for humans and agents. It supports two 1. **Web API** - Create and edit shared documents via HTTP (no install needed) 2. **Local Bridge** - Drive the macOS Proof app via localhost:9847 +## Identity and Attribution + +Every write to a Proof doc must be attributed. Two fields carry the agent's identity: + +- **Machine ID (`by` on every op, `X-Agent-Id` header):** `ai:compound-engineering` — stable, lowercase-hyphenated, machine-parseable. Appears in marks, events, and the API response. +- **Display name (`name` on `POST /presence`):** `Compound Engineering` — human-readable, shown in Proof's presence chips and comment-author badges. + +Set the display name once per doc session by posting to presence with the `X-Agent-Id` header; Proof binds the name to that agent ID for the session. These values are the defaults for any caller of this skill; callers running HITL review (`references/hitl-review.md`) may pass a different `identity` pair if a distinct sub-agent should own the doc. Do not use `ai:compound` or other ad-hoc variants — identity stays uniform unless a caller explicitly overrides it. + +## Human-in-the-Loop Review Mode + +When a caller (e.g., `ce-brainstorm`, `ce-plan`) needs to upload a local markdown doc, collect structured human feedback in Proof, and sync the final doc back to disk, load `references/hitl-review.md` for the full loop spec: invocation contract, mark classification (change / question / objection / ambiguous), idempotent ingest passes, exception-based terminal reporting, and end-sync atomic write. + ## Web API (Primary for Sharing) ### Create a Shared Document @@ -59,41 +72,81 @@ All operations go to `POST https://www.proofeditor.ai/api/agent/{slug}/ops` **Authentication for protected docs:** - Header: `x-share-token: ` or `Authorization: Bearer ` - Token comes from the URL parameter: `?token=xxx` or the `accessToken` from create response +- Header: `X-Agent-Id: ai:compound-engineering` (required for presence; include on ops for consistent attribution) + +**Wire-format reminder.** `/api/agent/{slug}/ops` uses a top-level `type` field; `/api/agent/{slug}/edit/v2` uses an `operations` array where each entry has `op`. Do not mix — sending `op` to `/ops` returns 422. + +**Every mutation requires a `baseToken`.** Read it from `/state.mutationBase.token` (or `/snapshot.mutationBase.token`) immediately before each write, and include it in the request body. On `BASE_TOKEN_REQUIRED` or `STALE_BASE`, re-read and retry once. See the baseToken recipe in `references/hitl-review.md`. + +**`Idempotency-Key` header** is recommended on every mutation for safe automation retries; required when `/state.contract.idempotencyRequired` is true. **Comment on text:** ```json -{"op": "comment.add", "quote": "text to comment on", "by": "ai:", "text": "Your comment here"} +{"type": "comment.add", "quote": "text to comment on", "by": "ai:compound-engineering", "text": "Your comment here", "baseToken": ""} ``` **Reply to a comment:** ```json -{"op": "comment.reply", "markId": "", "by": "ai:", "text": "Reply text"} +{"type": "comment.reply", "markId": "", "by": "ai:compound-engineering", "text": "Reply text", "baseToken": ""} ``` -**Resolve a comment:** +**Resolve / unresolve a comment:** ```json -{"op": "comment.resolve", "markId": "", "by": "ai:"} +{"type": "comment.resolve", "markId": "", "by": "ai:compound-engineering", "baseToken": ""} +{"type": "comment.unresolve", "markId": "", "by": "ai:compound-engineering", "baseToken": ""} ``` -**Suggest a replacement:** +**Suggest a replacement (pending — user must accept/reject):** ```json -{"op": "suggestion.add", "kind": "replace", "quote": "original text", "by": "ai:", "content": "replacement text"} +{"type": "suggestion.add", "kind": "replace", "quote": "original text", "by": "ai:compound-engineering", "content": "replacement text", "baseToken": ""} ``` -**Suggest a deletion:** +**Suggest and immediately apply (tracked but committed — user can reject to revert):** ```json -{"op": "suggestion.add", "kind": "delete", "quote": "text to delete", "by": "ai:"} +{"type": "suggestion.add", "kind": "replace", "quote": "original text", "by": "ai:compound-engineering", "content": "replacement text", "status": "accepted", "baseToken": ""} ``` -**Bulk rewrite:** +`status: "accepted"` creates the suggestion mark and commits the change in one call. The mark persists as an audit trail with per-edit attribution and a reject-to-revert affordance. Works with `kind: "insert" | "delete" | "replace"`. + +**Accept or reject an existing suggestion:** ```json -{"op": "rewrite.apply", "content": "full new markdown", "by": "ai:"} +{"type": "suggestion.accept", "markId": "", "by": "ai:compound-engineering", "baseToken": ""} +{"type": "suggestion.reject", "markId": "", "by": "ai:compound-engineering", "baseToken": ""} ``` +`suggestion.resolve` is not supported — use accept or reject instead. + +**Bulk rewrite (whole-doc replacement):** +```json +{"type": "rewrite.apply", "content": "full new markdown", "by": "ai:compound-engineering", "baseToken": ""} +``` + +**Block-level edits via `/edit/v2`** (separate endpoint, separate shape): +```bash +curl -X POST "https://www.proofeditor.ai/api/agent/{slug}/edit/v2" \ + -H "Content-Type: application/json" \ + -H "x-share-token: " \ + -H "X-Agent-Id: ai:compound-engineering" \ + -H "Idempotency-Key: " \ + -d '{ + "by": "ai:compound-engineering", + "baseToken": "mt1:", + "operations": [ + {"op": "replace_block", "ref": "b3", "block": {"markdown": "Updated paragraph."}}, + {"op": "insert_after", "ref": "b3", "block": {"markdown": "## New section"}} + ] + }' +``` + +Supported `op` kinds inside `operations`: `replace_block`, `insert_before`, `insert_after`, `delete_block`, `replace_range` (uses `fromRef` + `toRef`), `find_replace_in_block` (takes `occurrence: "first" | "all"`). Read `/snapshot` to get stable block `ref` IDs and the `mutationBase.token`. + +**Editing while a client is connected is fine.** `/edit/v2`, `suggestion.add` (including `status: "accepted"`), and all comment ops work during active collab. Only `rewrite.apply` is blocked by `LIVE_CLIENTS_PRESENT` — it would clobber in-flight Yjs edits. + +**When the loop breaks.** If a mutation keeps failing after a fresh read and one retry, or state across reads looks inconsistent, call `POST https://www.proofeditor.ai/api/bridge/report_bug` with the failing request ID, slug, and raw response. The server enriches and files an issue. + ### Known Limitations (Web API) -- `suggestion.add` with `kind: "insert"` returns Bad Request on the web ops endpoint. Use `kind: "replace"` with a broader quote instead, or use `rewrite.apply` for insertions. -- Bridge-style endpoints (`/d/{slug}/bridge/*`) require client version headers (`x-proof-client-version`, `x-proof-client-build`, `x-proof-client-protocol`) and return 426 CLIENT_UPGRADE_REQUIRED without them. Use the `/api/agent/{slug}/ops` endpoint instead. +- Bridge-style endpoints (`/d/{slug}/bridge/*`) require client version headers (`x-proof-client-version`, `x-proof-client-build`, `x-proof-client-protocol`) and return 426 CLIENT_UPGRADE_REQUIRED without them. Use `/api/agent/{slug}/ops` instead. ## Local Bridge (macOS App) @@ -111,15 +164,15 @@ Requires Proof.app running. Bridge at `http://localhost:9847`. | GET | `/windows` | List open documents | | GET | `/state` | Read markdown, cursor, word count | | GET | `/marks` | List all suggestions and comments | -| POST | `/marks/suggest-replace` | `{"quote":"old","by":"ai:","content":"new"}` | -| POST | `/marks/suggest-insert` | `{"quote":"after this","by":"ai:","content":"insert"}` | -| POST | `/marks/suggest-delete` | `{"quote":"delete this","by":"ai:"}` | -| POST | `/marks/comment` | `{"quote":"text","by":"ai:","text":"comment"}` | -| POST | `/marks/reply` | `{"markId":"","by":"ai:","text":"reply"}` | -| POST | `/marks/resolve` | `{"markId":"","by":"ai:"}` | +| POST | `/marks/suggest-replace` | `{"quote":"old","by":"ai:compound-engineering","content":"new"}` | +| POST | `/marks/suggest-insert` | `{"quote":"after this","by":"ai:compound-engineering","content":"insert"}` | +| POST | `/marks/suggest-delete` | `{"quote":"delete this","by":"ai:compound-engineering"}` | +| POST | `/marks/comment` | `{"quote":"text","by":"ai:compound-engineering","text":"comment"}` | +| POST | `/marks/reply` | `{"markId":"","by":"ai:compound-engineering","text":"reply"}` | +| POST | `/marks/resolve` | `{"markId":"","by":"ai:compound-engineering"}` | | POST | `/marks/accept` | `{"markId":""}` | | POST | `/marks/reject` | `{"markId":""}` | -| POST | `/rewrite` | `{"content":"full markdown","by":"ai:"}` | +| POST | `/rewrite` | `{"content":"full markdown","by":"ai:compound-engineering"}` | | POST | `/presence` | `{"status":"reading","summary":"..."}` | | GET | `/events/pending` | Poll for user actions | @@ -141,17 +194,30 @@ When given a Proof URL like `https://www.proofeditor.ai/d/abc123?token=xxx`: curl -s "https://www.proofeditor.ai/api/agent/abc123/state" \ -H "x-share-token: xxx" +# Get baseToken for the next mutation +BASE=$(curl -s "https://www.proofeditor.ai/api/agent/abc123/state" \ + -H "x-share-token: xxx" | jq -r '.mutationBase.token') + # Comment curl -X POST "https://www.proofeditor.ai/api/agent/abc123/ops" \ -H "Content-Type: application/json" \ -H "x-share-token: xxx" \ - -d '{"op":"comment.add","quote":"text","by":"ai:compound","text":"comment"}' + -H "X-Agent-Id: ai:compound-engineering" \ + -d "$(jq -n --arg base "$BASE" '{type:"comment.add",quote:"text",by:"ai:compound-engineering",text:"comment",baseToken:$base}')" -# Suggest edit +# Suggest edit (tracked, pending) curl -X POST "https://www.proofeditor.ai/api/agent/abc123/ops" \ -H "Content-Type: application/json" \ -H "x-share-token: xxx" \ - -d '{"op":"suggestion.add","kind":"replace","quote":"old","by":"ai:compound","content":"new"}' + -H "X-Agent-Id: ai:compound-engineering" \ + -d "$(jq -n --arg base "$BASE" '{type:"suggestion.add",kind:"replace",quote:"old",by:"ai:compound-engineering",content:"new",baseToken:$base}')" + +# Suggest and immediately apply (tracked, committed) +curl -X POST "https://www.proofeditor.ai/api/agent/abc123/ops" \ + -H "Content-Type: application/json" \ + -H "x-share-token: xxx" \ + -H "X-Agent-Id: ai:compound-engineering" \ + -d "$(jq -n --arg base "$BASE" '{type:"suggestion.add",kind:"replace",quote:"old",by:"ai:compound-engineering",content:"new",status:"accepted",baseToken:$base}')" ``` ## Workflow: Create and Share a New Document @@ -167,19 +233,59 @@ URL=$(echo "$RESPONSE" | jq -r '.tokenUrl') SLUG=$(echo "$RESPONSE" | jq -r '.slug') TOKEN=$(echo "$RESPONSE" | jq -r '.accessToken') -# 3. Share the URL +# 3. Bind display name via presence +curl -s -X POST "https://www.proofeditor.ai/api/agent/$SLUG/presence" \ + -H "Content-Type: application/json" \ + -H "x-share-token: $TOKEN" \ + -H "X-Agent-Id: ai:compound-engineering" \ + -d '{"name":"Compound Engineering","status":"reading","summary":"Uploaded doc"}' + +# 4. Share the URL echo "$URL" -# 4. Make edits using the ops endpoint +# 5. Make edits using the ops endpoint (baseToken required) +BASE=$(curl -s "https://www.proofeditor.ai/api/agent/$SLUG/state" \ + -H "x-share-token: $TOKEN" | jq -r '.mutationBase.token') curl -X POST "https://www.proofeditor.ai/api/agent/$SLUG/ops" \ -H "Content-Type: application/json" \ -H "x-share-token: $TOKEN" \ - -d '{"op":"comment.add","quote":"Content here","by":"ai:compound","text":"Added a note"}' + -H "X-Agent-Id: ai:compound-engineering" \ + -d "$(jq -n --arg base "$BASE" '{type:"comment.add",quote:"Content here",by:"ai:compound-engineering",text:"Added a note",baseToken:$base}')" ``` +## Workflow: Pull a Proof Doc to Local + +Sync the current Proof doc state to a local markdown file. Used by: + +- HITL review end-sync (`references/hitl-review.md` Phase 5) when the doc originated from a local file +- Ad-hoc snapshots of a Proof doc to disk (before closing the tab, archiving, handing off) +- Refreshing a local working copy against the live Proof version + +```bash +SLUG= +TOKEN= +LOCAL= + +# One read to a temp file — avoids passing markdown through $(...), which would strip trailing newlines. +STATE_TMP=$(mktemp) +curl -s "https://www.proofeditor.ai/api/agent/$SLUG/state" \ + -H "x-share-token: $TOKEN" > "$STATE_TMP" +REVISION=$(jq -r '.revision' "$STATE_TMP") + +# Atomic write: stream .markdown bytes directly to a temp sibling, then rename. +TMP="${LOCAL}.proof-sync.$$" +jq -jr '.markdown' "$STATE_TMP" > "$TMP" && mv "$TMP" "$LOCAL" +rm "$STATE_TMP" +``` + +`jq -jr` (`-j` no trailing newline, `-r` raw string) streams the markdown bytes straight to the temp file without going through a shell variable, so trailing newlines survive intact. `mv` within the same filesystem is atomic — a crashed write leaves the original untouched rather than a half-written file. + +**Confirm before writing when the pull isn't directly asked for.** If a workflow ends up pulling as a side-effect of a different action (e.g., HITL review completion), surface the impending write with a short confirm like "Sync reviewed doc to ``?" A silent overwrite is surprising — the user may have forgotten the local file exists in that session, or expected Proof to stay canonical until they explicitly asked to pull. + ## Safety - Use `/state` content as source of truth before editing -- Prefer suggest-replace over full rewrite for small changes +- During active collab use `edit/v2` (direct block changes) or `suggestion.add` (tracked changes); reserve `rewrite.apply` for no-client scenarios since it's blocked by `LIVE_CLIENTS_PRESENT` when anyone is connected - Don't span table cells in a single replace -- Always include `by` field for attribution tracking +- Always include `by: "ai:compound-engineering"` on every op and `X-Agent-Id: ai:compound-engineering` in headers for consistent attribution +- Read a fresh `baseToken` before every mutation; on `STALE_BASE`, re-read and retry once diff --git a/plugins/compound-engineering/skills/proof/references/hitl-review.md b/plugins/compound-engineering/skills/proof/references/hitl-review.md new file mode 100644 index 0000000..4b29a13 --- /dev/null +++ b/plugins/compound-engineering/skills/proof/references/hitl-review.md @@ -0,0 +1,313 @@ +# HITL Review Mode + +Human-in-the-loop iteration loop for a markdown document shared via Proof. Invoked either by an upstream skill (`ce-brainstorm`, `ce-ideate`, `ce-plan`) handing off a draft it produced, or directly by the user asking to iterate on an existing markdown file they already have on disk ("share this to proof and iterate", "HITL this doc with me"). Mechanics are identical in both cases: upload the local doc, let the user annotate in Proof's web UI, ingest feedback as in-thread replies and tracked edits, and sync the final doc back to disk. + +This mode assumes a local markdown file exists. There is no "from scratch" entry — if the user wants a fresh doc, create one with the normal proof create workflow first, then invoke HITL. + +Load this file when HITL review mode is requested — whether by an upstream caller or directly by the user. + +--- + +## Invocation Contract + +Inputs: + +- **Source file path** (required): absolute or repo-relative path to the local markdown file. When an upstream caller invokes this mode, it passes the path explicitly. When the user invokes directly ("share that doc to proof and let's iterate"), derive the path from conversation context — the file the user just referenced, created, or edited. If ambiguous, ask the user which file. +- **Doc title** (required): display title for the Proof doc. Upstream callers pass this explicitly; on direct-user invocation, default to the file's H1 heading, falling back to the filename (minus extension) if no H1 exists. +- **Recommended next step** (optional, caller-specific): short string the caller wants echoed in the final terminal output (e.g., "Recommended next: `/ce:plan`"). Not used on direct-user invocation — the terminal report simply summarizes the iteration and asks what's next. + +Agent identity is fixed, not a parameter: every API call uses agent ID `ai:compound-engineering` and display name `Compound Engineering`. Callers do not override this. + +Return shape (used by upstream callers to resume their handoff; also shown to the user in the terminal when invoked directly): + +- `status`: `proceeded` | `done_for_now` | `aborted` +- `localPath`: the source file path (same as input) +- `localSynced`: `true` if Phase 5 wrote the reviewed doc back to `localPath`; `false` if the user declined the sync and local is stale. Only present on `proceeded`. +- `docUrl`: the tokenUrl for the Proof doc +- `openThreadCount`: number of unresolved threads still in the doc +- `revision`: final doc revision after end-sync (only on `proceeded`) + +--- + +## Phase 1: Upload and Wait + +1. Read the local markdown file into memory. Remember this content as `uploadedMarkdown` — Phase 5 compares against it to detect whether anything changed during the session. +2. `POST https://www.proofeditor.ai/share/markdown` with `{title, markdown}` → capture `slug`, `accessToken`, `tokenUrl` +3. `POST /api/agent/{slug}/presence` with `X-Agent-Id: ai:compound-engineering`, `x-share-token: `, body `{"name":"Compound Engineering","status":"reading","summary":"Uploaded doc for review"}` +4. Display prominently in the terminal: + + ``` + Doc ready for review: + ``` + +5. Ask the user with the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini). If no question tool is available, present the options in chat and wait for the reply. + + **Question:** "Highlight text in Proof to leave a comment. The agent will read each one, reply in-thread or apply the fix, then sync changes back to your local file. What's next?" + + **Options:** + - **I'm done with feedback — read it and apply** + - **I have no feedback — proceed** + + If the user is still reviewing, they leave the prompt open — the blocking question waits naturally. A third "still working" option would be a no-op wrapper for that. + + On **I have no feedback — proceed**: skip to Phase 5 (end-sync); return to caller with `status: proceeded`. + + On **I'm done with feedback**: continue to Phase 2. + +--- + +## Phase 2: Ingest Pass + +A single pass over the current doc state. Deterministic, idempotent, derivable from marks — no session cache, no sidecar state. + +At the start of the pass, update presence to `status: "acting"` with a short summary like `"Reading your feedback"` so anyone watching the Proof tab sees the agent is live on their comments. Update to `status: "waiting"` before the Phase 3 terminal report so the tab signals "ball is in your court" while the terminal asks for the next signal. Same `POST /presence` call as Phase 1 — just different `status`/`summary`. + +### 2.1 Read fresh state + +``` +GET /api/agent/{slug}/state +Headers: x-share-token: +``` + +Capture: +- `markdown` (current body — includes any user direct edits and accepted suggestions) +- `revision` +- `marks` (object keyed by markId) +- `mutationBase.token` — the baseToken required for this round's mutations + +### 2.2 Identify marks that need attention + +Filter `marks` to items where **all** of the following hold: + +- `by` starts with `human:` (authored by a human, not the agent) +- `resolved` is `false` +- Either `thread` has no entry authored by any `ai:*` identity, **OR** the latest entry in `thread` is authored by `human:*` with an `at` timestamp newer than the latest `ai:*` entry (user responded to a prior agent reply) + +Skip everything else. Agent-authored marks, resolved threads, and threads already replied to with no new human response are done. + +### 2.3 Read each mark and decide how to respond + +The point of HITL is to give the user a natural way to steer the doc without dragging every decision into the terminal. Most feedback can be auto-applied. Only escalate when the agent genuinely can't make a confident call alone. + +Real feedback blends types — "this is wrong, rename to Y" is both objection and directive; "why X? I'd prefer Z" is both question and suggestion. Don't force a clean classification. Read the comment text, the anchored `quote`, and any prior thread replies, and decide: + +**Can the agent apply a fix directly with confidence?** Imperatives ("rename X to Y", "remove this", "add a section about Z") usually qualify. Apply the edit, reply with a one-line summary of what changed, resolve. + +**Is this a question with a clear answer?** Answer in-thread. Resolve if the answer stands on its own. If answering surfaces a new decision the user should weigh in on, leave open and surface it in the terminal report. + +**Is this a disagreement?** ("this is wrong", "contradicts §2", "this won't work"). Evaluate the claim against current content. If the agent agrees, fix and reply "Agreed — updated to X". If the agent disagrees, reply with the reasoning and leave open. Don't silently apply an objection without evaluating it — the whole point is that the user flagged it *because* they think the plan is wrong. + +**Is the intent genuinely unclear?** First try: attempt the most reasonable interpretation, apply it, and reply "I read this as X — let me know if I should revert." That's cheaper than a round-trip when stakes are low. Ask for clarification only when the interpretations lead to meaningfully different outcomes. When asking, use the platform's blocking question tool for a quick multiple-choice when the options are discrete, or leave it as an open thread comment when free-form response is more natural. Either way the thread stays open so the next pass picks up the user's reply. + +**Invariant:** every attention-needing mark ends the pass with an agent reply in its thread. Unreplied = "still to do" — the next pass re-classifies it. This is what makes the loop idempotent without a sidecar: mark state *is* the state. Even when the agent disagrees or can't decide, reply (with reasoning or a question) rather than silently skip. + +### 2.4 Apply edits + +The user is collaborating in the doc, not waiting on approval. Every mutation works with live clients — only whole-doc `rewrite.apply` is gated. Pick the tool that matches intent: + +**Default: `suggestion.add` with `status: "accepted"`** for content changes anchored on a quote (reword, rename, clarify, correct, add a sentence inline). One call creates a tracked suggestion mark *and* commits the change. The user sees committed text (no pending approval needed), and the mark persists as audit trail with per-edit attribution and a one-click reject-to-revert. This is the right primitive for HITL auto-applied edits — it gives the user a reversible trail without asking them to re-review anything. + +```json +{"type":"suggestion.add","kind":"replace","quote":"","content":"","by":"ai:compound-engineering","status":"accepted","baseToken":""} +``` + +Use `kind: "insert" | "delete" | "replace"` as appropriate; all three support `status: "accepted"`. + +**Use `/edit/v2` silently** only when the trail is actively wrong or technically blocked: + +- **Atomicity is required** — multiple coordinated edits must commit together or not at all (e.g., insert new section + update a reference in another block + delete the obsolete paragraph). `/edit/v2` takes an `operations` array that commits atomically; separate `suggestion.add` calls can partially succeed. +- **Pre-user self-correction** — the agent is fixing its own output *before* the user has looked at the doc (e.g., spotted a mistake mid-ingest-pass). A tracked mark would imply "there was an old version," which is misleading from the user's perspective. +- **Pure structural insertion with no quote anchor** — adding an entirely new block/section where no existing text serves as an anchor. `suggestion.add` requires a `quote`; `/edit/v2` has `insert_before` / `insert_after` keyed on block `ref`. +- **Structural list-item or block removal** — `suggestion.add` with `kind: "delete"` only deletes the text inside a list item; the bullet marker (`*`, `-`, or numeric `1.`) stays behind as an orphan line. Use `/edit/v2 delete_block` to remove an entire block, or `find_replace_in_block` to splice out the item plus its surrounding whitespace cleanly. + +```bash +# Get snapshot for block refs + baseToken +curl -s "https://www.proofeditor.ai/api/agent/{slug}/snapshot" -H "x-share-token: " +# Apply +curl -X POST "https://www.proofeditor.ai/api/agent/{slug}/edit/v2" \ + -H "Content-Type: application/json" -H "x-share-token: " \ + -H "X-Agent-Id: ai:compound-engineering" -H "Idempotency-Key: " \ + -d '{"by":"ai:compound-engineering","baseToken":"","operations":[...]}' +``` + +Supported `op` kinds: `replace_block`, `insert_before`, `insert_after`, `delete_block`, `replace_range` (`fromRef`+`toRef`), `find_replace_in_block` (`occurrence: "first"|"all"`). + +Op body shapes (block content must be wrapped in `block: {markdown}` — the server rejects flat `{op, ref, markdown}` shapes): + +```json +{"op":"replace_block","ref":"b8","block":{"markdown":"new content"}} +{"op":"insert_after","ref":"b3","block":{"markdown":"new block"}} +{"op":"delete_block","ref":"b6"} +{"op":"find_replace_in_block","ref":"b4","find":"old","replace":"new","occurrence":"first"} +{"op":"replace_range","fromRef":"b2","toRef":"b5","block":{"markdown":"..."}} +``` + +Block `ref` values drift across revisions — always re-fetch `/snapshot` for fresh refs before each `/edit/v2` call. + +**Use pending `suggestion.add` (no status)** when the change is judgment-sensitive enough that the agent wants explicit user approval before commit — rare in HITL, since the point of auto-applied edits is to reduce round-trips. Most judgment-sensitive cases are better handled by leaving the thread open with a clarifying question. + +**`rewrite.apply` is not needed during a live review.** It's blocked by `LIVE_CLIENTS_PRESENT` anyway. + +**Mutation requirements (every write, including replies and resolves):** + +- Top-level field is `type` on `/ops`; `operations[].op` on `/edit/v2`. Do not mix. +- Include `baseToken` from `/state.mutationBase.token` (or `/snapshot.mutationBase.token` for `/edit/v2`). On `STALE_BASE` or `BASE_TOKEN_REQUIRED`, re-read and retry once. +- Set `by: "ai:compound-engineering"` and header `X-Agent-Id: ai:compound-engineering`. +- Include an `Idempotency-Key` header (fresh UUID per logical write) so retries stay safe. +- Reply: `{"type":"comment.reply","markId":"","by":"ai:compound-engineering","text":"..."}`. Resolve: `{"type":"comment.resolve","markId":"","by":"ai:compound-engineering"}`. Reopen if needed: `{"type":"comment.unresolve", ...}`. + +**When the loop breaks.** If a mutation keeps failing after a fresh read and one retry, or two reads disagree about state, call `POST https://www.proofeditor.ai/api/bridge/report_bug` with the request ID, slug, and raw response body before falling back. Don't silently skip — that loses the audit trail the user is relying on. + +--- + +## Phase 3: Terminal Report + +Exception-based. Don't replay what the user can already see in the Proof doc — the full reasoning for each thread lives there. The terminal is for the decisions the user needs to make next. + +Every report covers three things, phrased naturally for the current state: + +- **What got handled** (e.g., how many comments resolved, any edits auto-applied) +- **What's still open** — if any escalations remain, each one gets one line of anchored quote plus one line of the agent's reply or question. Fuller context stays in the Proof thread +- **The doc URL** — always include it; the user may have closed the tab + +Keep the whole report scannable at a glance. Three common shapes fall out of this naturally: + +- A clean pass with everything handled collapses to a single line plus the doc URL +- An escalation pass lists the open threads compactly after a one-line summary of what was handled +- A pass with no new feedback just notes that and points to the doc + +Phrase them in whatever voice matches the situation rather than matching a template — "handled 4, 1 still needs you" and "all 5 addressed, doc's ready" are both fine. + +--- + +## Phase 4: Next-Signal Prompt + +Ask the user with the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini). If no question tool is available, present the options in chat and wait for the reply. + +**Question:** "Proof review pass done. What's next?" + +Offer options that cover these intents — use concrete user-facing labels, not agent-internal jargon (no "end-sync", "ingest pass", etc.). Only include the options that fit the current state. Keep labels imperative and third-person (no "I'll" / "I'm" — it is ambiguous in a tool-mediated menu whether the speaker is the user or the agent) and keep the `[short label] — [description]` shape consistent across every option. A "still working, come back later" option is not offered: the blocking question already waits, so that option would be a no-op wrapper (per the Interactive Question Tool Design rules in `plugins/compound-engineering/AGENTS.md`). + +- **Discuss** → `Discuss — walk through the open threads in terminal` + Talk through open threads in the terminal; the agent echoes decisions back to Proof threads. Only useful when escalations are open. +- **Proceed** → `Save — save the reviewed doc back to the local file` + Go to Phase 5 end-sync. If escalations are still open, name that in the label (e.g., `Save with 3 threads still open`) so the user is accepting the tradeoff explicitly instead of via a nested confirm. +- **Another pass** → `Re-check — look for new comments in Proof` + Re-read state and re-ingest. Worth offering even after a clean pass, since the user may have added comments while the report rendered. +- **Done for now** → `Pause — stop without saving` + Stop without syncing; return to caller with `status: done_for_now`, no end-sync. + +The sync confirmation happens in Phase 5 regardless of whether threads are open — this step only asks what the user wants next, not whether to overwrite the local file. + +--- + +## Phase 5: End-Sync + +Runs when the user selects **Proceed**. Before prompting anything, check whether the Proof content actually diverged from what was uploaded — if not, there's nothing to sync and no reason to ask. + +1. Fetch current state: `GET /api/agent/{slug}/state` with `x-share-token: `. Save the full response body to a temp file (`$STATE_TMP`) so the markdown bytes can later be streamed to disk without passing through `$(...)` (which would strip trailing newlines). Extract `state.revision` from that file into `$REVISION`. Read `state.markdown` from that file for the comparison in step 2. + +2. Compare `state.markdown` to `uploadedMarkdown` (captured in Phase 1). + + **If identical** — no content changes happened during the session. Skip the sync prompt entirely. Display: + + ``` + No changes to sync. Local file is unchanged. + Doc: + ``` + + Set presence `status: completed`, summary `"Review complete, no changes"`. Return to the caller with `status: proceeded`, `localSynced: true` (local matches Proof — no write needed, local is not stale), `revision: `, and the rest of the standard fields. + + **If different** — continue to step 3. + +3. Ask with the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini). If no question tool is available, present the options in chat and wait for the reply. + + **Question:** "Sync the reviewed doc back to ``? Proof has your review changes; local still has the pre-review copy." + + **Options:** + - **Yes, sync now** (default, recommended) + - **Not yet, I'll pull it later** (returns to caller with `localSynced: false`) + + Why the extra prompt: the user may have started review hours ago and lost track of the local file at stake. A brief confirm makes the file write visible rather than a silent side-effect of clicking Proceed earlier. The caller signals via `localSynced` so downstream workflows can warn that local is stale. + +4. On **Yes, sync now**, write the fetched markdown to local — see `Workflow: Pull a Proof Doc to Local` in `SKILL.md`: + + ```bash + # $STATE_TMP is the temp file holding the /state response from step 1. + TMP="${SOURCE}.proof-sync.$$" + jq -jr '.markdown' "$STATE_TMP" > "$TMP" && mv "$TMP" "$SOURCE" + rm "$STATE_TMP" + ``` + + Stream `.markdown` bytes directly from the saved state file with `jq -jr` — do not capture the markdown into a shell variable, since `$(...)` would strip trailing newlines and corrupt the write. `$REVISION` (extracted separately in step 1) is safe to keep as a variable; it's an opaque scalar. + + On **Not yet**, skip the write (still clean up `$STATE_TMP`). + +5. Set presence `status: completed`, summary `"Review synced to "` (or `"Review complete, local not updated"` if sync was declined) so the Proof UI shows the loop has finished. + +6. Display one of: + + Synced: + ``` + Doc synced to (revision ). + Doc: + ``` + + Declined: + ``` + Review complete. Local file kept as-is — pull from Proof when ready. + Doc: + ``` + +7. Return to the caller with: + ``` + status: proceeded + localPath: + localSynced: true | false + docUrl: + openThreadCount: + revision: + ``` + +Do **not** delete the Proof doc. It remains the durable review record; the caller's workflow may want to link back to it. + +--- + +## Recipes + +### BaseToken-aware mutation + +```bash +SLUG= +TOKEN= +AGENT_ID=ai:compound-engineering + +mutate() { + local PAYLOAD="$1" # jq template without baseToken + local BASE + BASE=$(curl -s "https://www.proofeditor.ai/api/agent/$SLUG/state" \ + -H "x-share-token: $TOKEN" | jq -r '.mutationBase.token') + curl -s -X POST "https://www.proofeditor.ai/api/agent/$SLUG/ops" \ + -H "Content-Type: application/json" \ + -H "x-share-token: $TOKEN" \ + -H "X-Agent-Id: $AGENT_ID" \ + -H "Idempotency-Key: $(uuidgen)" \ + -d "$(jq -n --arg base "$BASE" --argjson payload "$PAYLOAD" '$payload + {baseToken: $base}')" +} +``` + +Every mutation sends a fresh `Idempotency-Key` so retries on network hiccups do not double-apply the op. This is required when `/state.contract.idempotencyRequired` is true and harmless otherwise. + +On `STALE_BASE` in the response, re-run — the state read picks up the fresh token automatically. + +### jq gotcha when inspecting responses + +When extracting fields from API responses with jq's `//` alternative operator, parenthesize inside object constructors — jq parses `{markId: .markId // .result.markId}` as a syntax error. Use `{markId: (.markId // .result.markId)}`, or pull the value outside the object: `jq -r '.markId // .result.markId'`. + +### Identity + +All ops must include: +- `by: "ai:compound-engineering"` in the request body +- `X-Agent-Id: ai:compound-engineering` in headers (required for presence; recommended for ops for consistent attribution) + +Display name `Compound Engineering` is bound via `POST /presence` with `{"name":"Compound Engineering", ...}`. Set this once after upload; it carries across subsequent ops.