Blog · Engineering

Closing the loop: how this site resolves feedback with FFRS

Every page here has a Feedback tab. What happens after you press it is a five-stage pipeline in which an AI agent writes the first answer, a human decides, and the outcome comes back to you — measured end to end.

The problem: feedback is captured, then it stalls

Most projects have a way to collect feedback — a form, an inbox, an issue tracker. Far fewer have a working loop: the item is acknowledged, someone acts on it, and the person who raised it hears the outcome. The interval between "raised" and "resolved" is usually owned by nobody and measured by nothing, so it is slow without anyone noticing.

Until this month scaledaiops.org had exactly that gap. The only feedback path was a link to our GitHub organisation: no account, no way to report; with an account, leave the site, pick a repository, open an issue, and hope. Nobody was notified. Nobody could say how long an issue typically waited.

The FFRS pipeline

The Fast Feedback Resolution System (FFRS) treats each item as a five-stage pipeline. Each stage is a timestamp, recorded by whichever system naturally owns the event — so nothing needs manual bookkeeping and the metrics fall out of the timestamps.

Captureissue created Acknowledgee-mail, if consented Routelabels kind · severity Respondagent PR or proposal Closeoutcome sent back TTFR = Respond − Capture · TTHR = first human decision − Capture · TTC = Close − Capture
The five stages. The Respond stage is performed by an agent by default; every stage is a timestamp.

The rule that makes FFRS fast: the first substantive response is produced without waiting for a maintainer. The rule that keeps it safe: nothing the agent produces takes effect without a human decision.

How it runs on this site

The implementation adds no new system of record. The GitHub issue tracker we already use is the store; a small function, a private object-store prefix and a mail API do the rest. FFRS is defined by these roles, not by these products — each can be swapped for an equivalent (see the table further down).

Feedback widget4 KB, every page /feedback/ pageno-JS form · status CDN /api/*→ API gateway Functionvalidate · guardscapture · status Issue trackersystem of recordroute · respond · close Private object storesidecar {e-mail}idempotency · screenshots Mail APIack · alert · close Codingagenthourly webhook: closed PRs on the site repo
Solid: capture path. Dashed: loop closure. Dotted: the agent. Blue built for FFRS, green stores, grey existing platform services.

1. Capture

The edge tab opens a small dialog: feature or bug, a title, details, an optional e-mail with an explicit "tell me when it's resolved" checkbox, and — for bugs — a screenshot of the page you were on. The function validates the input, applies three guards (a hidden honeypot field, a per-address rate limit, a bot check), stores the screenshot privately and creates the GitHub issue synchronously. Only then do you get a reference like FB-6PR9NJ. If the tracker were down you would get an honest error and the widget would retry with the same idempotency key — nothing is half-stored.

The FFRS feedback dialog on the Disciplines page, bug tab, with an automatic page screenshot
The widget on the Disciplines page. Bug reports capture a screenshot automatically; you can remove it before sending.

2. Acknowledge and route

If you left an e-mail and ticked the box, you receive an acknowledgement with your reference and a status link. Routing is a label: kind:bug or kind:feature, plus severity. Your e-mail never appears in the public issue; it lives in a private per-item record and is deleted with it.

The GitHub issue created for feedback FB-6PR9NJ with FFRS labels
The issue that FFRS filed for the first real request — the placeholder text of the widget's own title field.

3. Respond — the agent

Every hour a coding agent reads the tracker for items without a first response and takes one of two paths.

Itemcaptured · routed agent triage Pull requestfix · tests · evidence Developerreviews · merges Proposalwhat · needs · risk Requester · reviewer/accept · /confirm Closeoutcome sent back code change decision needed
Orange: produced by the agent. Yellow: human decisions. Nothing takes effect without a merge or an accept-and-confirm.
Pull request #1 opened by the FFRS agent on the scaledaiops.org repository
The agent's pull request for that first item: a one-line change, the tests it ran, and a link back to the issue.

4. Close

Closing the issue — by merging the PR or by hand — records the outcome (fixed, shipped, answered, declined, …) and, if you opted in, sends you a closing note. The public status page /feedback/ shows the same timeline for any reference id.

The first real item, end to end

On the day the system went live, a visitor (the maintainer, testing as a user) reported that the widget's title field showed the meaningless placeholder "One line". Here is what happened, from the public record:

TimeEvent
T+0:00Item captured through the widget (feature request, anonymous).
T+1:10First agent run starts (the routine was created that hour). By T+1:19 it has a correct two-line fix but cannot push: its tracker identity lacked write permission on the organisation. It reports this precisely and stops.
T+1:21Maintainer grants the permission.
T+1:55Second agent run opens the pull request with the fix and its test results, comments on the issue, applies labels.
T+2:02Maintainer reviews the one-line diff and merges within a minute. Issue closes automatically with outcome shipped. The change is deployed.

Time to first response ≈ 2 h, time to first human decision ≈ 2 h, time to close ≈ 2 h, human effort ≈ 1 minute. Since then the agent runs hourly, so a first response normally arrives within an hour of capture.

How this compares

Plain tracker linkVendor feedback widgetService deskFFRS
Anonymous reportingno (account needed)yesusually via e-mailyes
Acknowledgement to reporternoneyesyes (SLA)yes, if consented
First substantive responsewhenever a maintainer lookswhenever a maintainer looksstaffed queueagent, hourly
Outcome reported backonly if the reporter watches the issuesometimesyesyes (closing note)
System of recordtrackervendor databaseITSM tooltracker (no new store)
Metricsnonevendor dashboardSLA reportstimestamps; recomputable by anyone
Personal dataGitHub profileheld by vendorheld by toole-mail only, private, deletable
New systems to run01 (SaaS)1–21 small function
Removablecancel subscriptionhardthree switches; no trace

What it costs

At this site's volume — tens of items a month, not thousands — the running cost is effectively the free tier of the cloud we already use, plus the coding-agent subscription the maintainers already have.

ComponentRoleMonthly cost at our volume
Serverless function + API gatewaycapture, status, webhookfree tier (≈ 1 M requests/month included)
Private object storesidecar, screenshots (90-day expiry)cents
Transactional e-mailack · alert · closefractions of a cent per message
CDN path /api/*same-origin APIincluded in the site's existing distribution
Bot checkanti-spamfree tier
Issue trackersystem of recordfree (public repository)
Coding agentRespond stagewithin an existing subscription; no per-item API charge
Total≈ €0–2 per month at current volume; the marginal cost of an item is the reviewer's minute

For comparison, hosted feedback products typically start in the tens of euros per month per seat and hold the data on the vendor's side. The trade FFRS makes is deliberate: no triage UI beyond the tracker itself, in exchange for no vendor, no database and no data leaving the project.

Build cost: about one working day across eight phases, by one maintainer with an AI coding assistant. The whole history — infrastructure, code, content and the agent's own instructions — is in version control, so the audit trail is the commit and pull-request log.

Benefits we see

Faster first response

Hours instead of "when someone gets to it", because the first response is produced, not scheduled.

Human effort goes to judgement

Reviewing a diff or a proposal takes a minute; producing it took the agent's time, not the maintainer's.

The loop actually closes

Reporters get a reference, a status page and — if they want — an e-mail when it's done.

Honest metrics

Every stage is a timestamp in a public tracker; anyone can recompute TTFR, TTC, loop closure and agent share.

Nothing new to operate

The tracker is the store; one function; no database; three switches remove it all.

Privacy by construction

Consent-gated e-mail kept apart from the public record; expiring screenshots; no analytics.

Limits, stated plainly

Roles, not products

FFRS prescribes roles and the contracts between them. Ours are filled with what this project already ran on; any equivalent works.

RoleHereEquivalents
Issue trackerGitHub IssuesJira, GitLab Issues, Linear, Azure Boards, Gitea
Capture functionAWS Lambda + API GatewayCloud Run / Functions, Azure Functions, Cloudflare Workers, any container behind an HTTP gateway
Object storeS3 (private prefix)Google Cloud Storage, Azure Blob, MinIO
MailSESSendGrid, Postmark, Mailgun, SMTP
Bot checkCloudflare TurnstilereCAPTCHA, hCaptcha, honeypot only
Coding agenthosted coding-agent routineCopilot coding agent, OpenHands, Aider or SWE-agent in a scheduled job
Infrastructure as codeTerraformOpenTofu, Pulumi, CloudFormation, Bicep

Adopting it

One script tag on your pages, one function deployment configured by environment variables, and a webhook on your tracker:

<script src="/assets/js/ffrs-widget.js"
  data-endpoint="/api/feedback" data-site="example.org"
  data-turnstile="<sitekey>" data-screenshot="bug" defer></script>

Code, infrastructure module and the agent's protocol are open: ffrs-api (MIT), this site (widget, CC BY-SA), infrastructure. The tracker that receives this site's feedback is public too: Scaled-AIOps/feedback — every item, response and outcome is there to inspect. A weekly metrics report is filed there each Monday. A preprint describing the FFRS model, its metrics and this deployment as a case study is being written in the open at Scaled-AIOps/ffrs-paper.

Try it now: the Feedback tab is on the right edge of this page. Tell us what's wrong with this post.