Skip to content
Merged
2 changes: 1 addition & 1 deletion .specify/feature.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"feature_directory": "specs/004-escalation-execution"
"feature_directory": "specs/005-cooperative-self-resolution"
}
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# cancerbero Development Guidelines

Auto-generated from all feature plans. Last updated: 2026-04-23
Auto-generated from all feature plans. Last updated: 2026-04-27

## Active Technologies
- Rust stable, edition 2021 (same toolchain as Phases 1/2/3). + `nostr-sdk 0.44.1` (gift-wrap transport), `mostro-core 0.9.1`, `rusqlite` (bundled, now via migration v4), `tokio` (existing runtime), `serde` + `serde_json` (HandoffPackage round-trip + DM body), `uuid` (dispatch_id v4), `tracing`, `thiserror`. No new crate pulls. (004-escalation-execution)
- SQLite. One new table (`escalation_dispatches`), four new `mediation_events.kind` values. Migration v4 extends the existing migrations chain in `src/db/migrations.rs`. (004-escalation-execution)
- Rust stable, edition 2021 (same toolchain as Phases 1/2/3 and Phase 4). + `nostr-sdk 0.44.1` (gift-wrap transport, reused), `mostro-core 0.9.1`, `rusqlite` (bundled, no new migration needed), `tokio` (existing runtime), `serde` + `serde_json` (config + classifier output deserialisation), `tracing`, `thiserror`. **No new crate pulls.** The reasoning provider trait already supports the existing classification round trip; the new `human_requested: bool` field rides through it as an additive struct field. (005-cooperative-self-resolution)
- SQLite. **No migration.** The new audit event kind reuses the existing `mediation_events` table (kind is TEXT). The new policy decision variant and the new escalation trigger variant are pure Rust enum extensions. (005-cooperative-self-resolution)

Currently shipped in `main` (Phases 1 and 2 implemented):

Expand Down Expand Up @@ -42,10 +44,10 @@ cargo test && cargo clippy
Rust (stable, edition 2021): Follow standard conventions

## Recent Changes
- 005-cooperative-self-resolution: Added Rust stable, edition 2021 (same toolchain as Phases 1/2/3 and Phase 4). + `nostr-sdk 0.44.1` (gift-wrap transport, reused), `mostro-core 0.9.1`, `rusqlite` (bundled, no new migration needed), `tokio` (existing runtime), `serde` + `serde_json` (config + classifier output deserialisation), `tracing`, `thiserror`. **No new crate pulls.** The reasoning provider trait already supports the existing classification round trip; the new `human_requested: bool` field rides through it as an additive struct field.
- 004-escalation-execution: Added Rust stable, edition 2021 (same toolchain as Phases 1/2/3). + `nostr-sdk 0.44.1` (gift-wrap transport), `mostro-core 0.9.1`, `rusqlite` (bundled, now via migration v4), `tokio` (existing runtime), `serde` + `serde_json` (HandoffPackage round-trip + DM body), `uuid` (dispatch_id v4), `tracing`, `thiserror`. No new crate pulls.
- 003-guided-mediation: Added Rust (stable, edition 2021) — same toolchain as Phases 1 and 2. + `nostr-sdk 0.44.1`, `mostro-core 0.9.1`, `rusqlite` (bundled), `tokio`, `serde`, `toml`, `tracing`. **New for Phase 3**: `reqwest` (HTTP client for reasoning providers), `sha2` (prompt-bundle hashing and rationale reference ids), `uuid` (session ids).

- main: Added Rust (stable, edition 2021) + nostr-sdk 0.44.1, mostro-core 0.9.1, rusqlite, tokio, serde, toml, tracing

<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->
31 changes: 31 additions & 0 deletions config.sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,37 @@ solver_auth_retry_max_interval_seconds = 3600
solver_auth_retry_max_total_seconds = 86400
solver_auth_retry_max_attempts = 24

# --- Cooperative self-resolution branch (Feature 005) ---
#
# When the classifier marks a session as
# `coordination_failure_resolvable` with high confidence, Serbero can
# send a templated, language-matched invitation to both parties
# inviting them to coordinate the next step among themselves, with an
# explicit human-assistance opt-in. The party-facing text is a
# static repo string (`prompts/phase3-self-resolution.md`) — the LLM
# only decides WHETHER the branch fires, never WHAT to say. Templates
# never name a fund-moving action; the keyword-audit unit test
# refuses to merge a bundle that violates this.

# Confidence floor at which the cooperative branch fires. Range
# 0.0..=1.0; values outside that range fail loudly at startup. Set
# higher (e.g. 0.90) to fire only on very-high-confidence cooperative
# cases. A value of 1.0 only nearly disables the branch (a classifier
# that emits exactly 1.0 still passes the gate); the strict, only
# disable mechanism is `self_resolution_enabled = false` (the
# kill-switch below).
self_resolution_threshold = 0.75

# Master kill-switch. When `self_resolution_enabled = false`, the
# cooperative branch is bypassed entirely and Serbero behaves
# byte-for-byte as before this feature shipped: a high-confidence
# `coordination_failure_resolvable` round falls through to the
# legacy cooperative-summary path (the LLM still summarizes for the
# solver; the parties just don't receive the templated invitation).
# Use during incident windows or audit reviews when you want the
# legacy summarize-only flow without any party-facing nudge.
self_resolution_enabled = true

# ---------------------------------------------------------------------------
# [reasoning] — the AI provider that classifies disputes and drafts
# clarifying messages during Phase 3 guided mediation.
Expand Down
30 changes: 30 additions & 0 deletions prompts/phase3-classification.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,33 @@ Hard rules:
clarification.
- Each question stands on its own — don't cross-reference the other
party's text, since each party only ever sees theirs.

## Per-Party Language (Feature 005)

The classifier MUST also emit two top-level fields:

- `buyer_language` (string | null): ISO-639-1 code (e.g. `"en"`,
`"es"`, `"pt"`) inferred from the buyer's most recent reply. Set
to `null` when the latest message has no buyer content or is too
short to disambiguate. Required on **every** round, not only on
rounds following a `self_resolution_offered` event — the runtime
uses the codes to drive the cooperative-self-resolution dispatch
arm and keeps round-0/1 ready in case the cooperative branch
fires later.
- `seller_language` (string | null): same shape, for the seller.

## Human-Assistance Opt-In (Feature 005, conditional)

On rounds following a `self_resolution_offered` audit event for the
session — the runtime appends the request to the prompt only on
those rounds — the classifier MUST also emit:

- `human_requested` (boolean): `true` if and only if the latest
party reply contains an explicit, unambiguous request for a human
solver / mediator / arbitrator. Examples: `"I want a human"`,
`"necesito un humano"`, `"please escalate to a person"`, `"que un
humano lo revise"`, `"preciso de um humano"`. Vague phrasings like
`"this is taking too long"` or `"I'm frustrated"` do **NOT** count.
When in doubt, set to `false` — a false negative defers escalation
by one round (the user re-states); a false positive escalates to
human prematurely on a case the parties might have resolved.
9 changes: 9 additions & 0 deletions prompts/phase3-escalation-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ serialized form is canonical; the enum name is the cross-reference.
Evidence: rationale id.
- **`notification_failed`** (`NotificationFailed`): Summary/escalation
notification undeliverable. Evidence: notification error.
- **`party_requested_human`** (`PartyRequestedHuman`): On a round
following a `self_resolution_offered` event, a party reply
contained an explicit, unambiguous request for human assistance
(Feature 005, FR-008). The classifier detects the request via the
`human_requested: bool` field and `policy::evaluate` short-circuits
to this trigger before the regular classification-label dispatch.
Evidence: rationale id of the round that detected the request;
reference to the prior `self_resolution_offered` audit row that
scoped the short-circuit.

## Handoff Package

Expand Down
47 changes: 47 additions & 0 deletions prompts/phase3-self-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!--
Cooperative self-resolution invitation templates (Feature 005).

This file is parsed as TOML by `crate::prompts::self_resolution_parser`.
HTML-style comments (like this block) are stripped before parsing.

POLICY BOUNDARIES (enforced by the keyword-audit unit test in
`tests/phase3_self_resolution_template_audit.rs`):

- The `template` and `human_assistance_optin` strings MUST NOT name
or imply ANY fund-moving action: release, settle, cancel,
disburse, transfer, refund, pay, send fiat, send sats, etc.
The Phase 3 authority boundary documented in
`prompts/phase3-system.md` continues to apply; this file's
contents are an extension of that boundary into a templated,
LLM-static surface.

- Translations MUST preserve the neutral, non-directive tone of the
English original. Confirming a factual claim from either party
(e.g. "we acknowledge you received the fiat") is forbidden — the
Phase 3 system prompt's neutrality rule applies here too.

- To add a new language: append a new `[xx]` section AND extend the
banned-substring matrix in
`tests/phase3_self_resolution_template_audit.rs`. Both changes
must land in the same PR so a reviewer can confirm the audit
covers the new translation.

The `fallback_language` is the language used when the classifier
could not determine a party's language confidently (`buyer_language`
/ `seller_language` are `null` on the structured response) OR when
the detected code has no matching `[xx]` section in this file.
-->

fallback_language = "en"

[en]
template = "Thanks for the update — it sounds like the two of you may be close to coordinating the next step between yourselves. I'll keep monitoring this conversation in case anything changes."
human_assistance_optin = "If you'd prefer human assistance instead, just let me know in this chat and I'll route you to the assigned solver."

[es]
template = "Gracias por la actualización: parece que ustedes dos podrían estar cerca de coordinar el siguiente paso entre sí. Sigo atento a esta conversación por si algo cambia."
human_assistance_optin = "Si prefieres asistencia humana, dímelo en este chat y te conecto con la persona asignada al caso."

[pt]
template = "Obrigado pela atualização — parece que vocês dois podem estar perto de coordenar o próximo passo entre si. Continuo acompanhando esta conversa caso algo mude."
human_assistance_optin = "Se preferir assistência humana, me avise neste chat e eu encaminho você para a pessoa designada."
13 changes: 11 additions & 2 deletions prompts/phase3-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,18 @@ limits, and honesty discipline. These rules apply to every reasoning call.

- Allowed: classification labels with confidence scores, clarifying
questions sourced from message templates, structured summaries for
the solver, explicit escalation recommendations.
the solver, explicit escalation recommendations, the
`self_resolution_offered` cooperative-invitation event (templated
per-party message in the party's detected language with an explicit
human-escalation opt-in; the templates are static repo strings that
MUST NOT name a fund-moving action, see
`prompts/phase3-self-resolution.md`).
- Disallowed: autonomous dispute closure, binding decisions,
fund-related instructions, fabricated factual claims.
fund-related instructions, fabricated factual claims. The
fund-action prohibition extends to every party-facing surface,
including the cooperative-self-resolution invitation — that file's
contents are an extension of the Phase 3 authority boundary, not an
exception to it.

## Language Matching

Expand Down
60 changes: 60 additions & 0 deletions specs/005-cooperative-self-resolution/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Specification Quality Checklist: Cooperative Self-Resolution Nudge

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-04-27
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Validation Notes

- Initial validation pass on 2026-04-27. All items pass.
- Spec deliberately avoids naming Rust types (`MediationEventKind::SelfResolutionOffered`,
`EscalationTrigger::PartyRequestedHuman`, `policy::evaluate`, etc.) and code-level
prompt file paths. Those will surface in `plan.md` and `data-model.md` during
`/speckit.plan`.
- The 0.75 confidence threshold default is included as a numeric value in FR-010
because the spec explicitly says it MUST be configurable; the value is a
business-facing default, not an implementation detail.
- Three [NEEDS CLARIFICATION] candidates were considered and resolved with
reasonable defaults rather than asking the user, per the speckit "max 3
markers, prefer informed guesses" guidance:
1. *What happens when both parties go silent after the invitation?* —
resolved by SC-004 (silence-rate budget + revisit-phrasing trigger) and
by FR-007 (the existing solver summary still fires, so the human can
intervene).
2. *Is the threshold global or per-classification?* — resolved as global
(FR-010), since the new branch only fires for the cooperative label
anyway, making per-label tuning moot for now.
3. *Should the solver summary be delayed when the invitation fires?* —
resolved as "no, fire immediately" via FR-007. Audit completeness
beats solver inbox volume; solvers can filter on the
"self-resolution offered" marker.

## Notes

- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`.
- All checklist items currently pass; spec is ready for the next phase.
Loading
Loading