Skip to content

chore(prompts): rewrite friction guidance to be action-triggered#1305

Merged
zbigniewsobiecki merged 1 commit into
devfrom
chore/friction-prompt-rewrite
May 10, 2026
Merged

chore(prompts): rewrite friction guidance to be action-triggered#1305
zbigniewsobiecki merged 1 commit into
devfrom
chore/friction-prompt-rewrite

Conversation

@zbigniewsobiecki
Copy link
Copy Markdown
Member

Summary

  • Live signal from prod 2026-05-10 PR feat(pm-wizard): add provider-owned credential metadata #1303: implementation agent (a4585987) hit a textbook env friction (CASCADE_ORG_ID=mongrel leaking into a test, worked around with env -u) but reported it in the PR body instead of via ReportFriction.
  • Plumbing was wired correctly end-to-end (capability scoped, gadget surfaced via filterTools, CASCADE_FRICTION_SIDECAR_PATH env var injected, CLI installed in the container). Verified by re-running getToolManifests() + filterTools(integrationChecker=true) locally with the exact deployed code — ReportFriction IS in the implementation agent's toolset.
  • The prompt was just not recruiting action.

Why the old prompt under-recruited

Issue Old text Effect
Conditional hedge "When the ReportFriction tool is available…" Creates agent doubt — but the section is only ever injected when the capability IS effective, so the hedge is dead weight that produces "am I sure?" hesitation.
Constraint framing "use it ONLY for incidental papercuts…" Reads as a scoping cap, not a trigger. Agents default to "skip if uncertain."
Negative scoping × 3 "Do not report core task difficulty…" / "Do not report expected debugging effort…" / "Do not report product ambiguity…" Three "do not" clauses with zero "do" clauses. Bias is toward not reporting.

New framing

Action-trigger first, with explicit calibration toward over-reporting:

## Friction Reporting

When something makes your work harder than it strictly needs to be — at
any point during the task — file it with `ReportFriction`. When in doubt,
report. Better to over-report initial papercuts than let recurring
friction go invisible.

After filing, keep working; only let friction block your task if it
actually blocks it.

No example list — would create pattern-match traps that narrow the use to the listed buckets. The gadget's category enum (tooling | environment | permissions | dependency | test-failure | pm-data | scm-data | other) already provides categorical anchoring at invocation time, where the agent will see it.

Test plan

  • npm run lint clean
  • npm run typecheck clean
  • All 9219 unit tests pass
  • Updated tests/unit/agents/shared/frictionGuidance.test.ts — asserts the new action-trigger content + intentionally pins the removal of the old under-reporting clauses (Do not report / only for incidental papercuts / When the ReportFriction tool is available) so a future revert would fail loudly.
  • Updated tests/unit/backends/secretOrchestrator.test.ts:260 — prompt-assembly test flipped to the new content.
  • Post-deploy verification: monitor prod worker engineLogs for the next hour for any actual cascade-tools pm report-friction invocation. With the new prompt, the agent that hits the next eligible papercut should fire it instead of writing a PR-body note.

Out of scope

  • The four observability gaps (broken cascade runs llm-calls CLI, request: null for codex runs, no boot-time [FrictionGadget] log, silent drainer on empty sidecar) — those still stand and would let passive monitoring distinguish "wired but unused" without waiting for a real invocation. Worth a follow-up but separate from the prompt-effectiveness fix.
  • Configuring statuses.friction on the ucho project (currently unconfigured — Linear-based reports would queued_slot_missing). Operator config, not a code change.

🤖 Generated with Claude Code

Live signal from prod 2026-05-10 PR #1303 (a4585987 implementation run):
the agent encountered a textbook incidental papercut — `CASCADE_ORG_ID=mongrel`
leaking from worker env into `tests/unit/cli/dashboard/config.test.ts`,
worked around with `env -u CASCADE_ORG_ID npm test` — but reported it in
the PR body instead of via `ReportFriction`. The plumbing was correct
(capability scoped, gadget surfaced, sidecar env var injected, CLI
installed); the prompt just wasn't recruiting the agent.

The prior text framed reporting as a constraint, not a trigger:

  > When the ReportFriction tool is available, use it ONLY for
  > incidental papercuts in the environment, tooling, repository setup,
  > documentation, or developer workflow that make the work harder
  > than it should be.
  >
  > Do not report core task difficulty, expected debugging effort,
  > product ambiguity that belongs in the current work item, or issues
  > you can resolve directly as part of the assigned task.
  >
  > Keep working after reporting friction unless the issue blocks
  > progress. ...

Three problems with this framing — verified by reading other agent
prompt sections that DO recruit action well (`## Git`, `### Service
Recovery`, `### Test Writing Protocol`):

1. "use it only for ..." reads as a SCOPING CONSTRAINT, not an
   action trigger. Agents default to "skip if uncertain."
2. Three negative clauses ("Do not report ...") with zero positive
   trigger language. Bias is toward not reporting.
3. "When the ReportFriction tool is available" hedge — but the
   guidance is only injected when the capability IS effective, so the
   conditional just creates agent doubt.

New text drops all three. Action-trigger framing, "when in doubt,
report" calibration, "better to over-report initial papercuts" license,
non-blocking semantic preserved. No example list — would create
pattern-match traps that narrow the use; the gadget's `category` enum
(tooling | environment | permissions | dependency | test-failure |
pm-data | scm-data | other) provides the categorical anchoring at
invocation time.

Tests updated:
- `tests/unit/agents/shared/frictionGuidance.test.ts` — flipped from
  "expect old text" to assert the action-trigger framing + the
  intentional removal of the prior under-reporting clauses.
- `tests/unit/backends/secretOrchestrator.test.ts:260` — flipped the
  prompt-assembly test's `toContain('incidental papercuts')` style
  assertions to the new content.

No code wiring changes. The plumbing stays exactly as PR #1296/#1298/#1300
shipped it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zbigniewsobiecki zbigniewsobiecki merged commit e883a3c into dev May 10, 2026
7 checks passed
zbigniewsobiecki added a commit that referenced this pull request May 10, 2026
…dy, opt-in label

PRs #1305 + #1311 closed the recruit→fire loop. Looking at the 5 reports
filed live in prod 2026-05-10 (Trello cascade + Linear mongrel):

- Title slugs read `frictionlow-...` because `[Friction][severity]` put
  brackets adjacent with no separator. Category — the most useful triage
  signal — wasn't in the title at all.
- Body had 5 sections including `## What happened` (just repeats the
  summary already in the title), `## Classification` (now-redundant
  category/severity + the useful-but-buried whileDoing), and
  `## Timestamp` (the PM provider's native createdAt already shows this).
- No PM-side label, so operators couldn't filter friction cards even
  though the alert flow already established the cascade-alert opt-in
  pattern via spec 019.

Agents are already writing structured prose in `details` voluntarily
(what they tried, error, workaround, impact). The win is in rendering,
not in tightening the gadget input.

Changes:

- src/friction/format.ts:
  - Title: `[Friction · ${category} · ${severity}] ${summary}` —
    one bracket pair, dot separators, all three classification facets
    surfaced. Slugifies as `friction-tooling-low-...`.
  - Body has only two sections: `## Details` (agent prose verbatim,
    first because it's the most worth reading) and `## Run context`
    (bold-keyed bullets — Run / Work item / PR / Project / While doing).
    Italic `_Reported {iso}_` footer. Lines for absent fields drop
    entirely; no `_not provided_` placeholders for conditional context.
  - Compact bullets mirror src/integrations/alerting/_shared/format.ts.

- src/pm/config.ts: add `getFrictionLabelId(project)` mirroring the
  `getAlertLabelId` opt-in pattern from spec 019. Reads
  `labels['cascade-friction']` (Trello) / `labels.cascadeFriction`
  (JIRA, Linear). Returns undefined when unconfigured — current prod
  cascade & ucho behavior is preserved.

- src/integrations/pm/{jira,linear}/config-schema.ts: extend Zod
  schemas with the optional `cascadeFriction` label key.

- src/friction/materialize.ts: pass `[labelId]` to createWorkItem when
  configured, `[]` otherwise. Back-compat — projects without the label
  configured continue to file unlabeled cards exactly as before.

Tests:

- tests/unit/friction/format.test.ts: re-pin title + body invariants
  (only `## Details` + `## Run context` headings; no `## What happened`
  / `## Classification` / `## Timestamp`), Run-context bullet shape
  (run link, work item with monospaced id, PR with branch + 12-char SHA,
  project key/repo/pmType, whileDoing). Pin absence of prior
  bracket-concat title form as a regression net.
- tests/unit/friction/materialize.test.ts: add label-applied paths for
  Trello + JIRA + a label-absent regression net pinning `labels: []`.
- tests/unit/pm/config-friction-slot.test.ts: cover getFrictionLabelId
  across providers + Zod schema acceptance for `labels.cascadeFriction`.

Hand-rendered against a prod-shaped fixture confirms the new title slug
(`friction-tooling-low-pm-add-checklist-...`) and dense body shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant