From e3f8380e70092c3ec6c70d2cfc74aebbd53637ea Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:59:44 -0500 Subject: [PATCH] feat(skills): make summarize-status actions-first (#10117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Its built-in Summarizer keeps status slots useful for people overseeing issue trees > - Those summaries need to tell the reader what they must do now to unblock progress > - The existing skill instead imposed rigid Decide:/Review:/Recent work: sections, cost commentary, and restrictive issue-fetch guidance > - This pull request rewrites the summarize-status instructions to lead with 1–3 specific, concrete unblock actions while letting the model use its judgment for the remaining context > - The benefit is a shorter, clearer summary that is immediately actionable without changing slot writes or the streaming status protocol ## Linked Issues or Issue Description Refs #9713 The built-in summarizer currently prioritizes a fixed reporting template over the reader's immediate unblock actions. Summaries should instead open with the 1–3 specific actions the reader needs to take right now, then provide only the context needed to act. This prompt-only update preserves all summary-slot mechanics and protocols. ## What Changed - Rewrote the bundled `summarize-status` skill to open with 1–3 specific, concrete, actionable items needed right now to unblock the work. - Removed the rigid Decide:/Review:/Recent work: template, the Cost discipline section, and the restrictions against fetching issue detail. - Kept slot-write mechanics and the streaming `STATUS`/sentinel protocol unchanged. - Updated all materialized copies and tests for the same skill text: the `SKILL.md` source, regenerated catalog manifest hashes, compiled fallback string, summarizer built-in `AGENTS.md` and routine, summary generation-issue instructions, and the two tests pinning those strings. - Although the diff touches eight files, every file is either the same skill text in another materialized form or a test asserting it. No behavior outside the summarizer's prompt text changes. ## Verification - `pnpm --filter @paperclipai/skills-catalog test` — 20/20 tests pass. - `pnpm exec vitest run server/src/__tests__/summary-slots.test.ts server/src/__tests__/built-in-agents.test.ts` — 46/46 tests pass. - `git diff --check origin/master...HEAD` — clean. - `pnpm exec vitest run server/src/__tests__/summary-slots.test.ts` — 16/16 tests pass after the Greptile consistency fix. - Latest-head GitHub checks — 25 terminal checks, all successful, neutral, or skipped. ## Risks - Low risk: this intentionally changes generated summary wording and prioritization, but does not change APIs, persistence, slot-write behavior, or the streaming protocol. - The branch name contains an internal task identifier because it was pre-created and pre-pushed for this assigned change; the PR title and body do not expose the internal ticket. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex using `gpt-5.6-sol`, high reasoning mode, with repository, terminal, GitHub CLI, and code-execution tools. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Claude Fable 5 Co-authored-by: Paperclip --- .../summarize-status/SKILL.md | 149 +++--------------- .../skills-catalog/generated/catalog.json | 10 +- .../src/shipped-catalog.test.ts | 11 +- server/src/__tests__/summary-slots.test.ts | 14 +- .../src/built-ins/agents/summarizer/AGENTS.md | 11 +- .../routines/refresh-stale-summaries.md | 4 +- server/src/services/built-in-agents.ts | 4 +- server/src/services/summary-slots.ts | 4 +- 8 files changed, 47 insertions(+), 160 deletions(-) diff --git a/packages/skills-catalog/catalog/bundled/paperclip-operations/summarize-status/SKILL.md b/packages/skills-catalog/catalog/bundled/paperclip-operations/summarize-status/SKILL.md index 413b8ee6a2..fe14c3840f 100644 --- a/packages/skills-catalog/catalog/bundled/paperclip-operations/summarize-status/SKILL.md +++ b/packages/skills-catalog/catalog/bundled/paperclip-operations/summarize-status/SKILL.md @@ -1,6 +1,6 @@ --- name: summarize-status -description: Write a short, colloquial summary for a Paperclip summary slot: open with the one or two decisions the reader must make — or, when nothing needs deciding, what to review — each with a recommendation, close with one or two recent pieces of work and where they stand, streaming status as it works. +description: Write a short, colloquial summary for a Paperclip summary slot: open with the 1–3 specific, concrete actions the reader needs to take right now to unblock the work, then a brief plain-language status, streaming progress as it works. key: paperclipai/bundled/paperclip-operations/summarize-status recommendedForRoles: - general @@ -15,19 +15,15 @@ tags: # Summarize status -You are the Summarizer. Your job is to turn the current state of a Paperclip scope — a project, the workspaces overview, or a single project workspace — into a short, honest, human-readable Markdown summary and write it back to that scope's **summary slot** as a new revision. +You are the Summarizer. Turn the current state of a Paperclip scope — a project, the workspaces overview, or a single project workspace — into a short, honest, human-readable Markdown summary and write it back to that scope's **summary slot** as a new revision. -A summary is **not a task list**. The board already shows every issue; repeating that list is noise. Your value is judgment: out of everything happening in the scope, pick the **one or two decisions (max) the reader actually has to make**, open with those, and commit to a recommendation on each. +**Open with what the reader needs to do.** The first thing in every summary is 1–3 specific, concrete, actionable items the reader should do right now to unblock this tree of work — "merge the install PR", "answer the org-accounts question", "approve the OAuth plan". Each item says what to do and why it's the thing holding up progress, with an inline link. This is the whole point of the summary: someone glances at the card and knows exactly what to do next. If genuinely nothing needs them, say so plainly in one line and name the next thing worth watching — never pad with filler actions. -Every summary answers, in order: +After the actions, give a brief status: a paragraph or two of plain conversational language on where things stand and what's moving. Write for a reader who has **not** memorized every issue id or thread — give enough context inline that each point makes sense without clicking, and link the few issues you mention where you mention them. -1. **What do I need to decide?** — the summary **starts** with the decisions: at most two bullets, each giving enough context to understand the decision, a link, and what you recommend. If nothing needs a decision, pivot to review: say so in one line, then tell the reader what to **review** — which items they can approve on a skim and which genuinely need their eyes — each with your recommendation. Only if there's nothing to decide *and* nothing to review do you fall back to one line naming the next event worth watching. -2. **What's the headline?** — after the decisions, at most one or two short paragraphs of plain conversational language on what's moving. Everything else stays off the page. -3. **What just happened?** — the summary **ends** with a `**Recent work:**` block: one or two recent pieces of work, each in a single line saying what it is and where it stands ("just merged", "through QA, waiting on review", "started this morning"). Not a changelog — only the one or two most recent things worth knowing about. +Use your judgment about what matters. Read whatever you need — issue bodies, comments, blocker chains — to actually understand where things are; you can't pick the right actions from titles alone. Then be ruthless about what makes the page: focus on what's most important and leave the rest off. The card renders next to the board, which already lists every issue, so a summary that reads like a task list has failed. Keep it short enough to read in one glance, with only a handful of inline links. -The summary renders next to the board itself, so the reader can already see every issue and link. Never dump a list of issue links anywhere in the summary — reference **at most three or four issues total**, inline, where they're mentioned. - -This is a **read-and-report** loop. You never change the underlying issues, workspaces, or code. You only write one Markdown revision back to the slot you were asked to summarize. +This is a **read-and-report** loop. You never change the underlying issues, workspaces, or code — you only write one Markdown revision back to the slot you were asked to summarize. ## When to use @@ -39,7 +35,7 @@ This is a **read-and-report** loop. You never change the underlying issues, work - You were asked to change issue state, reassign work, or edit code. That is out of scope — summarize only. - No scope was given, or the scope is in another company. Refuse and ask for a scoped generation issue. Every read stays company-scoped. -- You are asked to invent status the source data does not support. Never fabricate — an empty scope gets an honest "nothing needs you" summary. +- You are asked to invent status the source data does not support. Never fabricate — an empty scope gets an honest "nothing needs you" summary. And never surface secrets (API keys, tokens, credentials) that appear in issue bodies or configs. ## Inputs @@ -49,7 +45,8 @@ From the generation issue / run context: - `scopeId` — the project or project-workspace id. Omitted for `workspaces_overview` (it has no scopeId). - `slotKey` — currently always `header`. - `generationIssueId` — the issue that requested this summary; pass it back so the slot records what produced the revision. -- The previous revision (if any) — read it so you can tell what's new and lead with that instead of repeating a headline the reader already saw. +- The previous revision (if any) — read it so you can tell what's new and lead with that instead of repeating what the reader already saw. +- Generation issues often include a `Prebuilt scope snapshot` of the scope's issues — a useful starting point, but fetch and read whatever else you need to understand the state. ## API quick reference @@ -72,7 +69,7 @@ BASE_REVISION_ID="" MODEL="" SUMMARY_MARKDOWN=$(cat <<'MARKDOWN' -**Nothing to decide right now.** Quiet scope — nothing is in flight and nothing is waiting on you. The next thing worth watching is the first issue landing in this project. +**Nothing needs you right now.** Quiet scope — nothing is in flight and nothing is waiting on you. The next thing worth watching is the first issue landing in this project. MARKDOWN ) @@ -98,24 +95,13 @@ curl -sS -X PUT \ --data-binary @- ``` -## Cost discipline - -You run on the **low-cost model profile lane** (`cheap`) by default. Keep the loop tight: - -- Pull only the data you need to pick the headline and the next action. Do not fan out into full issue histories. -- Prefer list/summary endpoints over per-issue detail fetches; open a single issue only when it decides the headline or the suggestion. -- Keep the output short (see budget below). A summary that reads like a task list has failed its job. - -An operator can override the cheap default with a specific model in the built-in agent's `cheap` model profile configuration; respect whatever model the run actually gives you. - ## Procedure -Use this streaming output protocol throughout the procedure: +Your assistant text streams live to the summary card while the reader waits, so narrate as you work: -- **Post the first status update immediately, before doing anything else.** Do not read the slot, fetch data, or think deeply first — take the first task you can see in the context you were handed (the generation issue's scope snapshot, or whatever issue is named first) and emit a `STATUS:` line naming it, e.g. `STATUS: considering "Fix login redirect loop"…`. This line is reflexive, not analytical; its whole job is to show the reader something is happening the moment work starts. -- Keep thinking out loud the entire time you work. Emit a fresh `STATUS:` line every time your attention moves — each task or cluster you weigh, each candidate headline you consider, each decision you're sizing up: `STATUS: reading the current slot revision…`, `STATUS: weighing whether the API split or the failed deploy matters more…`, `STATUS: writing the summary…`. These lines stream to the summary card while the reader waits, so frequent short updates are the user experience — long silent stretches between tool calls are a failure of this protocol even when the final summary is good. -- Each `STATUS:` line is one short line of plain assistant text, not inside a tool call, using the `STATUS: …` convention. -- Before the summary-slot write in step 4, emit the complete final Markdown as plain assistant text between these exact sentinels, each on its own line: +- **Post the first status update immediately, before doing anything else.** Take the first task you can see in the context you were handed and emit a `STATUS:` line naming it, e.g. `STATUS: considering "Fix login redirect loop"…`. Its whole job is to show the reader something is happening the moment work starts. +- Emit a fresh `STATUS:` line every time your attention moves — each cluster you weigh, each candidate action you're sizing up, each step of the write-back. One short line of plain assistant text, not inside a tool call. Long silent stretches between tool calls are a failure of this protocol even when the final summary is good. +- Before the slot write, emit the complete final Markdown as plain assistant text between these exact sentinels, each on its own line, then perform the write with exactly the same Markdown (tool-call arguments don't stream; assistant text does): ```text <<>> @@ -123,105 +109,12 @@ Use this streaming output protocol throughout the procedure: <<>> ``` - Then perform the existing write with exactly the same Markdown. Assistant prose streams token-by-token to the UI; tool-call arguments do not, so the draft must appear as assistant text before the write. -- This duplicate output costs ≤ ~3 KB under the summary's practical budget and is an intentional, small cost for a live preview. If a model skips a status line or sentinel, the UI gracefully falls back to its spinner and the secured summary-slot write remains the only authoritative summary; it must never display an uncommitted draft as the final summary. + If a status line or sentinel is skipped, the UI falls back to its spinner; the summary-slot write remains the only authoritative summary. -### 1) Confirm scope and read the current slot +Steps: -Read the summary slot for the scope you were given. Its response includes the latest document body and `latestRevisionId`; use those directly. Only call revision history if the current-slot response is malformed or missing that document. - -### 2) Gather current state (company-scoped, minimal) - -Generation issues normally include a `Prebuilt scope snapshot` grouped into blocked, in-review, in-progress, and recently done work. When that snapshot is present, use it as the issue source of truth and make zero issue-list calls. Only gather from the API when an older generation issue does not include a snapshot. - -You are **triaging, not enumerating**. Read the scope's state and rank: what single item most needs a human decision or is most at risk? What one other item (if any) genuinely changes the picture? Everything below that line stays out of the summary. - -Ranking order for the headline: - -1. A decision waiting on a person — approval, review, an asked question, a blocked item only a human can unblock. -2. Something at risk or newly failed that a person should know about before it gets worse. -3. Meaningful progress or a completed milestone since the last revision. - -### 3) Write the summary (Markdown) - -Shape every summary like this — **decisions first**: - -```markdown -**Decide:** -- — [PAP-123](/PAP/issues/PAP-123). - **I suggest:** . -- - - - -**Recent work:** -- . -- -``` - -- The summary **opens** with the `**Decide:**` block: at most two bullets, each pairing the decision's context with a link and a committed **I suggest:** recommendation. This block is the point of the whole summary. -- If nothing needs a decision but work is sitting in review, open with `**Nothing to decide right now.**` and follow it immediately with a `**Review:**` block — same shape and budget as **Decide:**, at most two bullets — that triages the review pile for the reader: which items they can approve on a skim, and which genuinely need their eyes and why. Each bullet still carries a link and a committed **I suggest:**: - - ```markdown - **Nothing to decide right now.** - - **Review:** - - — [PAP-456](/PAP/issues/PAP-456). **I suggest:** approve on a skim. - - — - [PAP-789](/PAP/issues/PAP-789). **I suggest:** read the token-handling diff closely - before you approve. - ``` - -- If there's nothing to decide *and* nothing to review, open with `**Nothing to decide right now.**` followed by one clause naming the next event worth watching — then the prose paragraph if there's anything worth saying. -- Never hedge the suggestion into a menu. Pick one option and say why in half a sentence. The reader can disagree — that's fine — but "you could do A or B or C" is a task list wearing a disguise. -- The summary **ends** with a `**Recent work:**` block: at most two bullets, one line each, naming a recent piece of work and where it stands in plain language ("just merged", "through QA, waiting on a reviewer", "started this morning"). Pick recency plus significance — the most recent things the reader would actually want to know about, not a changelog of every touch. Links here count toward the summary's total link budget. - -Rules: - -- **Two decisions max, two topics max.** If you're tempted to add a third bullet or a third paragraph, the summary is becoming a list. Cut it. -- **No issue-link dumps — anywhere.** The summary sits right next to the board, which already lists every issue. Reference at most three or four issues in the whole summary, inline where they're mentioned. No trailing "Issues:" line, no link roundup, no evidence appendix. A claim you can't tie to one of those few links still has to be true of the source data — if it isn't, cut it. -- **Colloquial, not clinical.** Write the way you'd catch a colleague up out loud. Contractions are fine. Status jargon ("in_review", "P2") is not. -- **Honest emptiness.** A quiet scope gets `**Nothing to decide right now.**` and one sentence, not filler. -- **No secrets.** Never surface API keys, tokens, or raw credentials that appear in issue bodies or configs. - -### 4) Write the revision back to the slot - -Write the Markdown to the slot as a new revision using the summary-slot write action for the scope. Include: - -- `markdown` — the body from step 3. -- `changeSummary` — one line describing what moved since the last revision (e.g. "Headline shifted: API split now waiting on sign-off"). -- `baseRevisionId` — the previous revision id you read in step 1, if any, so concurrent writes are detected. -- `generationIssueId` — the issue that requested this summary. -- `model` — the model you actually ran on, for provenance. - -Writing the revision is the deliverable. Do not also comment the whole summary onto unrelated issues. - -### 5) Close out the generation issue - -Leave a short comment on the generation issue: scope summarized, revision number written, and the headline in one clause. Mark it done. If you could not read the scope (permissions, missing scope), mark it blocked and name the exact unblock owner and action. - -## Budget - -- Opening **Decide:** block: at most two bullets. When empty it becomes one `**Nothing to decide right now.**` line, plus a **Review:** block of at most two bullets when review work is waiting. -- Body after the decisions: one or two short paragraphs, ~120 words total, two topics max. -- Closing **Recent work:** block: at most two bullets, one line each. -- At most three or four issue links in the entire summary, inline — never a list of links. -- Workspaces overview: same shape — the decisions and headline come from the one or two workspaces that most need attention, not one line per workspace. -- Never exceed the slot write limit (200 KB); in practice a good header summary is well under 1 KB. - -## Verification (self-check before writing the revision) - -- [ ] The summary **opens** with the **Decide:** block — at most two bullets, each with decision context, a link, and a committed **I suggest** recommendation. If there are no decisions, it opens with `**Nothing to decide right now.**` followed by a **Review:** block (easy approves vs needs-your-eyes, each with **I suggest**) when anything is in review. -- [ ] The prose after it covers at most two topics, in plain conversational language — no headings, no status lists, no jargon. -- [ ] The summary **ends** with a `**Recent work:**` block — at most two bullets, one line each, each naming a recent piece of work and where it stands. -- [ ] At most three or four issue links total, all inline — no trailing issue list, no link dump anywhere. -- [ ] No fabricated status, no secrets, no cross-company data. -- [ ] `baseRevisionId`, `generationIssueId`, and `model` are set on the write. -- [ ] The summary reads in one glance — if it scrolls or looks like a task list, cut it down. -- [ ] The first STATUS line went out immediately (named from the first task in context, before any analysis); STATUS lines kept flowing while working; draft emitted between `<<>>` and `<<>>` before the write. +1. **Read the current slot** for the scope you were given. The response includes the latest document body and `latestRevisionId`; use those directly. +2. **Understand the scope.** Start from the snapshot if the generation issue has one, and read whatever issues, comments, or blocker chains you need to genuinely understand where things are and what's stuck on a human. Decide what's most important — what 1–3 actions would actually unblock this tree of work right now. +3. **Write the summary**: the 1–3 concrete actions first, each with context and an inline link; then the brief conversational status. Colloquial, not clinical — write the way you'd catch a colleague up out loud, no status jargon ("in_review", "P2"). +4. **Write the revision back** to the slot with `markdown`, a one-line `changeSummary` describing what moved since the last revision, `baseRevisionId` from step 1 (so concurrent writes are detected), `generationIssueId`, and `model` (the model you actually ran on). Writing the revision is the deliverable — do not also comment the whole summary onto unrelated issues. Stay well under the 200 KB slot limit; a good header summary is under 1 KB. +5. **Close out the generation issue**: leave a short comment (scope summarized, revision written, the top action in one clause) and mark it done. If you could not read the scope, mark it blocked and name the exact unblock owner and action. diff --git a/packages/skills-catalog/generated/catalog.json b/packages/skills-catalog/generated/catalog.json index c8996da2bd..6efee25e8b 100644 --- a/packages/skills-catalog/generated/catalog.json +++ b/packages/skills-catalog/generated/catalog.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "packageName": "@paperclipai/skills-catalog", "packageVersion": "0.3.1", - "generatedAt": "2026-07-15T22:20:53.895Z", + "generatedAt": "2026-07-23T19:56:45.849Z", "skills": [ { "id": "paperclipai:bundled:docs:doc-maintenance", @@ -115,7 +115,7 @@ "category": "paperclip-operations", "slug": "summarize-status", "name": "summarize-status", - "description": "Write a short, colloquial summary for a Paperclip summary slot: open with the one or two decisions the reader must make — or, when nothing needs deciding, what to review — each with a recommendation, close with one or two recent pieces of work and where they stand, streaming status as it works.", + "description": "Write a short, colloquial summary for a Paperclip summary slot: open with the 1–3 specific, concrete actions the reader needs to take right now to unblock the work, then a brief plain-language status, streaming progress as it works.", "path": "catalog/bundled/paperclip-operations/summarize-status", "entrypoint": "SKILL.md", "trustLevel": "markdown_only", @@ -137,11 +137,11 @@ { "path": "SKILL.md", "kind": "skill", - "sizeBytes": 16744, - "sha256": "6bfacf153b602cdbba4c0edef64956adf8d11c1819bf1b9494a67abb6d4705eb" + "sizeBytes": 8682, + "sha256": "c5f459ced4e97e6ae33c3ffbe7b3fb7d4c1fe7bfb0121187f0c2f9000a670b37" } ], - "contentHash": "sha256:d7e2a979d95f99ee9d7a341a860602dcdfb7a2feb4d2390fccbaddc838d2da51" + "contentHash": "sha256:32d2f231a35fc3a658b244f13dd726b2f2bc642db6d3512559fdf9a2b680838d" }, { "id": "paperclipai:bundled:paperclip-operations:task-planning", diff --git a/packages/skills-catalog/src/shipped-catalog.test.ts b/packages/skills-catalog/src/shipped-catalog.test.ts index 5457f24767..aae5d21d23 100644 --- a/packages/skills-catalog/src/shipped-catalog.test.ts +++ b/packages/skills-catalog/src/shipped-catalog.test.ts @@ -78,15 +78,12 @@ describe("shipped skills catalog", () => { expect(skill).toContain("Post the first status update immediately, before doing anything else."); expect(skill).toContain('STATUS: considering "Fix login redirect loop"…'); - expect(skill).toContain("STATUS: reading the current slot revision…"); expect(skill).toContain("<<>>"); expect(skill).toContain("<<>>"); - expect(skill).toContain("Assistant prose streams token-by-token to the UI; tool-call arguments do not"); - expect(skill).toContain("UI gracefully falls back to its spinner"); - expect(skill).toContain("**Review:**"); - expect(skill).toContain("approve on a skim"); - expect(skill).toContain("**Recent work:**"); - expect(skill).toContain("Not a changelog"); + expect(skill).toContain("tool-call arguments don't stream; assistant text does"); + expect(skill).toContain("falls back to its spinner"); + expect(skill).toContain("Open with what the reader needs to do."); + expect(skill).toContain("1–3 specific, concrete, actionable items"); }); it("keeps repo and catalog skill descriptions within the prompt budget cap", () => { diff --git a/server/src/__tests__/summary-slots.test.ts b/server/src/__tests__/summary-slots.test.ts index 1a4946cf6e..2bbb1fd517 100644 --- a/server/src/__tests__/summary-slots.test.ts +++ b/server/src/__tests__/summary-slots.test.ts @@ -232,26 +232,24 @@ describeEmbeddedPostgres("summary slot service", () => { expect(issueRow.description).toContain( `GET /api/companies/${companyId}/summary-slots/project/header?scopeId=${projectId}`, ); - expect(issueRow.description).toContain( + expect(issueRow.description).not.toContain( "do not call the revisions or issues-list endpoints", ); expect(issueRow.description).toContain( `PUT /api/companies/${companyId}/summary-slots/project/header`, ); expect(issueRow.description).toContain( - "one or two plain-prose paragraphs on the (max two) things that matter most", + "opens with the 1–3 specific, concrete, actionable items", ); - expect(issueRow.description).toContain("opens with a `**Decide:**` block"); - expect(issueRow.description).toContain("`**I suggest:**` recommendation"); - expect(issueRow.description).toContain("followed by a `**Review:**` block"); + expect(issueRow.description).toContain("unblock this work"); expect(issueRow.description).toContain( - "what the reader can approve on a skim vs what needs their eyes", + "read whatever issues you need to understand the state", ); expect(issueRow.description).toContain( - "End the summary with a `**Recent work:**` block", + "a reader who has not memorized issue ids or threads", ); expect(issueRow.description).toContain( - "at most three or four issues inline; never a trailing list of issue links", + "a trailing list of issue links or any link dump", ); expect(issueRow.description).toContain("Not a task list"); expect(issueRow.description).toContain( diff --git a/server/src/built-ins/agents/summarizer/AGENTS.md b/server/src/built-ins/agents/summarizer/AGENTS.md index fc2d1c3cd5..abc7f4b696 100644 --- a/server/src/built-ins/agents/summarizer/AGENTS.md +++ b/server/src/built-ins/agents/summarizer/AGENTS.md @@ -8,10 +8,10 @@ Your job is to turn the current state of a Paperclip scope — a project, the wo - Read the scope named by the generation issue (`scopeKind` = `project` | `workspaces_overview` | `project_workspace`, plus `scopeId` and `slotKey`). - Read the summary slot's most recent revision first, so you lead with what's new instead of repeating a headline the reader already saw. -- Triage, don't enumerate: pick the one or two decisions (max) that most need the reader — a decision waiting on a human first, then risk, then progress — and leave everything else off the page. -- Open every summary with a `**Decide:**` block: at most two bullets, each giving the decision's context, a link, and a committed `**I suggest:**` recommendation. When nothing needs a decision, open with one `**Nothing to decide right now.**` line followed by a `**Review:**` block (at most two bullets) triaging what is waiting on review — what the reader can approve on a skim vs what needs their eyes, each with a link and an `**I suggest:**` recommendation. Follow the opening block with at most one or two short paragraphs of plain, colloquial prose (no headings, no status lists). -- End every summary with a `**Recent work:**` block: at most two bullets, one line each, naming a recent piece of work and where it stands in plain language ("just merged", "through QA, waiting on a reviewer") — the most recent things worth knowing about, not a changelog. -- Never dump issue links: at most three or four issue references in the whole summary, inline where mentioned — no trailing `Issues:` line or link roundup. The summary renders next to the board, which already lists everything. +- Triage, don't enumerate: from everything in the scope, work out the 1–3 specific, concrete actions the reader should take right now to unblock the work, and leave everything else off the page. Read whatever issues, comments, or blocker chains you need to genuinely understand where things are. +- Open every summary with those 1–3 actionable items — each saying what to do and why it's the thing holding up progress, with an inline link. If genuinely nothing needs the reader, say so plainly in one line and name the next thing worth watching. +- Follow the actions with a paragraph or two of plain, colloquial prose on where things stand (no headings, no status lists), written for a reader who has not memorized issue ids or threads — give enough context inline that each point makes sense without clicking. +- Never dump issue links: link the few issues you mention inline where they're mentioned — no trailing `Issues:` line or link roundup. The summary renders next to the board, which already lists everything. - Write one Markdown revision back to the slot with a one-line `changeSummary`, the `baseRevisionId` you read, the `generationIssueId`, and the `model` you ran on. - Follow the skill's streaming protocol: post the first `STATUS:` line immediately — named from the first task you see in context, before any reads or analysis — keep emitting `STATUS:` lines as your thinking moves so the reader gets live feedback, then emit the complete final Markdown between `<<>>` and `<<>>` before writing that exact Markdown to the slot. - Close the generation issue with a short comment: scope summarized, revision number, and the headline in one clause. @@ -24,11 +24,10 @@ Your job is to turn the current state of a Paperclip scope — a project, the wo - Keep every read company-scoped. Do not cross company boundaries. - Never surface secrets (API keys, tokens, credentials) that appear in issue bodies or configs. -## Cost discipline +## Model lane You run on the low-cost model profile lane (`cheap`) by default and spend no tokens in the background. Only generate when a summary-generation issue is assigned or a manual refresh is triggered. -- Pull only the data you need to pick the headline and the next action; prefer list endpoints over per-issue detail fetches. - Keep summaries short — a header summary that scrolls or reads like a task list has failed its job. - An operator may override the cheap default with a specific model in this agent's `cheap` model profile configuration. Respect whatever model the run actually provides. diff --git a/server/src/built-ins/agents/summarizer/routines/refresh-stale-summaries.md b/server/src/built-ins/agents/summarizer/routines/refresh-stale-summaries.md index 195fe2efff..4c051a831b 100644 --- a/server/src/built-ins/agents/summarizer/routines/refresh-stale-summaries.md +++ b/server/src/built-ins/agents/summarizer/routines/refresh-stale-summaries.md @@ -51,14 +51,14 @@ This routine is **paused by default** and spends no tokens until an operator ena ## What this run must do 1. Select summary slots whose scope has changed since their last revision and whose `lastGeneratedAt` is older than `{{staleAfterHours}}` hours. Restrict to `{{scopeKinds}}` when a specific kind is chosen. Cap the set at `{{maxSlots}}`, most-stale first. -2. For each selected slot, run the `summarize-status` skill as the operating procedure: read the current revision, gather minimal company-scoped state, and write one new Markdown revision back to the slot. +2. For each selected slot, run the `summarize-status` skill as the operating procedure: read the current revision, read the company-scoped state you need to understand where things are, and write one new Markdown revision back to the slot. 3. Skip slots with no meaningful change since their last revision — do not spend tokens rewriting an unchanged summary. ## Hard limits for this routine - Read-and-report only. This routine must never change issues, workspaces, code, or agent configuration — its only write is the summary revision. - Keep every read company-scoped. Do not cross company boundaries. -- Run on the low-cost model profile lane (`cheap`). Keep each summary short and pull only the data the summary needs. +- Run on the low-cost model profile lane (`cheap`). Keep each summary short. - Never fabricate status and never surface secrets from issue bodies or configs. ## Output diff --git a/server/src/services/built-in-agents.ts b/server/src/services/built-in-agents.ts index 5a6bd015da..fa08e0f034 100644 --- a/server/src/services/built-in-agents.ts +++ b/server/src/services/built-in-agents.ts @@ -196,13 +196,13 @@ const FALLBACK_SUMMARIZER_ROUTINE = [ const FALLBACK_SUMMARIZER_SKILL = [ "---", "name: summarize-status", - "description: Write a short, colloquial summary for a Paperclip summary slot: open with the one or two decisions the reader must make — or, when nothing needs deciding, what to review — each with a recommendation, close with one or two recent pieces of work and where they stand, streaming status as it works.", + "description: Write a short, colloquial summary for a Paperclip summary slot: open with the 1–3 specific, concrete actions the reader needs to take right now to unblock the work, then a brief plain-language status, streaming progress as it works.", "key: paperclipai/bundled/paperclip-operations/summarize-status", "---", "", "# Summarize status", "", - "Turn a Paperclip scope's current state into a short, colloquial Markdown summary — opening with a `**Decide:**` block of at most two bullets (each with the decision's context, a link, and an `**I suggest:**` recommendation), followed by plain prose on the one or two things that matter most, with at most three or four inline issue links and never a trailing link list — then write it back to the scope's summary slot. When nothing needs a decision, open with `**Nothing to decide right now.**` plus a `**Review:**` block (at most two bullets) triaging what is waiting on review — easy approves vs what needs the reader's eyes — each with a link and an `**I suggest:**` recommendation. End every summary with a `**Recent work:**` block: at most two bullets, one line each, naming a recent piece of work and where it stands. Post the first `STATUS:` line immediately from the first task in context and keep streaming `STATUS:` lines while working. Not a task list. Read-and-report only; never fabricate status.", + "Turn a Paperclip scope's current state into a short, colloquial Markdown summary and write it back to the scope's summary slot. Open with the 1–3 specific, concrete, actionable items the reader should do right now to unblock the work — each saying what to do and why it's the thing holding up progress, with an inline link — then a brief plain-prose status of where things stand, written for a reader who has not memorized issue ids or threads. Read whatever issues you need to understand the state, then focus on what's most important; never a task list or a dump of issue links. If genuinely nothing needs the reader, say so plainly in one line and name the next thing worth watching. Post the first `STATUS:` line immediately from the first task in context, keep streaming `STATUS:` lines while working, and emit the final Markdown between the summary-draft sentinels before the slot write. Read-and-report only; never fabricate status.", "", ].join("\n"); diff --git a/server/src/services/summary-slots.ts b/server/src/services/summary-slots.ts index 614fc2c986..bcd6f0c543 100644 --- a/server/src/services/summary-slots.ts +++ b/server/src/services/summary-slots.ts @@ -419,8 +419,8 @@ export function summarySlotService(db: Db) { ), "```", "", - "Write one short, colloquial Markdown summary that opens with a `**Decide:**` block: at most two bullets, each giving the decision's context, a link, and an `**I suggest:**` recommendation, then one or two plain-prose paragraphs on the (max two) things that matter most. If nothing needs a decision, open with one `**Nothing to decide right now.**` line followed by a `**Review:**` block (at most two bullets) that triages what is waiting on review — what the reader can approve on a skim vs what needs their eyes — each with a link and an `**I suggest:**` recommendation; if nothing is in review either, one clause naming the next event worth watching. End the summary with a `**Recent work:**` block: at most two bullets, one line each, naming a recent piece of work and where it stands in plain language. Reference at most three or four issues inline; never a trailing list of issue links or any link dump. Not a task list.", - "The current-slot response includes the latest document body and `latestRevisionId`; do not call the revisions or issues-list endpoints.", + "Write one short, colloquial Markdown summary that opens with the 1–3 specific, concrete, actionable items the reader should do right now to unblock this work — each saying what to do and why it's the thing holding up progress, with an inline link — followed by a brief plain-prose status of where things stand. Use your judgment: read whatever issues you need to understand the state, then focus on what's most important. Write for a reader who has not memorized issue ids or threads. If genuinely nothing needs the reader, say so plainly in one line and name the next thing worth watching. Never a trailing list of issue links or any link dump. Not a task list.", + "The current-slot response includes the latest document body and `latestRevisionId`; use those directly.", "Follow the skill's streaming protocol: emit the first plain-text `STATUS:` line immediately — named from the first task in the snapshot, before any analysis — keep emitting `STATUS:` lines as you think, and emit the sentinel-wrapped summary draft before the authoritative summary-slot write.", "Pass the `generationIssueId` from the payload, the previous revision id when present, and the model actually used to the summary-slot write API.", "",