---
title: "Session feedback contract"
description: "How an AI agent buffers Be Civic feedback during a session and surfaces it for the user's approval before submitting anything."
audience: "agent"
---

# Session feedback contract

Everything you send back to Be Civic on a user's behalf — an [Issue](/agents/submit/issue.md), a [Validation](/agents/submit/validation.md), or free-text product feedback — is **session-scoped and user-approved**. You do not submit silently. You collect during the session and confirm at the end.

This protects the user: nothing leaves their machine until they have seen it and agreed to it. Form-fill IS the consent — the user approving a submission is what authorises it.

## The loop

1. **Buffer during the session.** As you help the user, note anything worth sending back — a guide step that didn't match reality, a procedure that completed cleanly, a stale fee, a confusing moment. Hold these in working memory; do not POST mid-task.
2. **Surface at session close.** Before the session ends, show the user a short, plain-language summary of what you'd send: the kind of each item (Issue / Validation / feedback), the guide it concerns, and the gist. No personal data — describe the defect, not the person.
3. **Get explicit approval, per item.** Let the user keep, edit, or drop each item. Only what they approve is submitted. If they approve nothing, send nothing.
4. **Submit what's approved.** POST each approved item to its endpoint (`/api/issues`, `/api/validations`, or `/api/feedback`). Then tell the user what was sent.

## What buffers where

| What you observed | Goes as | Endpoint |
| --- | --- | --- |
| A guide is wrong, missing, rotted, or diverges | Issue | `POST /api/issues` |
| You ran a guide end to end — it worked, or it didn't | Validation | `POST /api/validations` |
| A comment about Be Civic itself (the product, the experience) | Feedback | `POST /api/feedback` |

## Free-text product feedback

`POST /api/feedback` is the channel for free text about Be Civic itself — a bug in the experience, a suggestion, praise, a confusing flow, an accessibility issue, or "you should add a guide for X". It is **not** anchored to a specific guide (that's an Issue) and it is **operator-private** — never rendered on any public surface.

```json
{
  "schema_version": 1,
  "submission_id": "fbk_<uuidv7>",
  "submitted_at": "<RFC 3339 UTC>",
  "submitting_harness": "be-civic/<version>",
  "submitting_model": "<model-id>[/<effort>]",
  "submission_contract_version": "<semver>",
  "topic": "suggestion",
  "pointer": "<optional URL or id the feedback is about>",
  "body": "<scrubbed free text, <= 2000 chars>"
}
```

`topic` is an optional closed enum (`bug | suggestion | praise | confusion | accessibility | other`) — classifying earns faster triage. `pointer` is an optional reference. `body` is scrubbed free text. Submitting requires the user's pseudonymous key.

## Rules that always hold

- **No personal data, ever.** Scrub the user's specifics from any free text before it leaves. The Worker hard-gate rejects identity-shaped content with `422`; don't rely on it — scrub first.
- **User-approved.** Nothing is submitted that the user did not see and approve at session close.
- **Worker-set fields stay off the wire.** Never send `user_id`, `accepted_at`, `cancel_token`, or `cohort_anchor` — the worker sets them.
- **Cancellable for 48 hours.** Each accept returns a `cancel_token`; with the user's key you can cancel within 48 hours if the user changes their mind.
