AI recovery agent for developers
Your app already knows why it failed. Scritch turns that into a fix your user can act on.
Every product ships “Something went wrong. Please try again.” This finds the real cause, asks the one question only the user can answer, fixes it with their approval, and proves it worked before saying so.
Free recoveries to start, no credit card. After that you pay only for recoveries that actually worked, capped where you set it. No seats, no minimum.
Five failures a real product ships. Pick one and press the button.
401 Unauthorized · token expired 40 minutes ago
What most products write
async function onSave(draft: Draft) {
try {
await api.saveDraft(draft);
} catch (err) {
// The 401 knew the token expired. This throws that away.
toast.error("Something went wrong. Please try again.");
}
}The same handler, wired up
import { useRecoverableTask, useGuidance } from "scritch-react";
function SaveButton({ draft }: { draft: Draft }) {
const { incident, run } = useRecoverableTask({ code: "draft.save.rejected" });
const guidance = useGuidance(incident);
return (
<>
<button onClick={() => run(() => api.saveDraft(draft))}>Save</button>
{/* Your toast, your styling. The hook only returns text. */}
{guidance.message && (
<Toast tone={guidance.userCanFix ? "action" : "info"}>
{guidance.message}
</Toast>
)}
</>
);
}Not sure the message your own app shows a blocked user speaks human? Run it through the free error check.
A live run of the real agent
A live install of the same agent your workspace runs, on a sample invoicing app. It picks up the failure, reads what the app never surfaced, asks you the one thing only you can answer, and confirms you can carry on, while you wait rather than after you have gone. It needs no error monitoring to do this. Connect yours and it reads that too.
Northwind · Your work
Preview knobs. A real install detects the user’s language automatically and runs the model you set once.
Send invoice #1043
Northwind BV · Belgium · EUR 4,820.00
What the user was told
Something went wrong. Please try again.
You have tried four times. Nothing on the form is marked as wrong.
All your app actually caught
POST /api/invoices/1043
→ 422 Unprocessable Entity
{ "error": "validation_failed" }What the agent does with it
Import contacts.csv
240 rows · uploaded 2 minutes ago
What the user was told
Import failed. Please check your file and try again.
42 of 240 rows were not imported. The app does not say which, or why.
All your app actually caught
POST /api/contacts/import
→ 207 Multi-Status
{ "imported": 198, "failed": 42, "rows": null }What the agent does with it
Upload your profile photo
headshot.png · 96×96 · PNG
What the user was told
That file type isn't supported.
The photo is the wrong format, and larger than the avatar store keeps. The app refuses it and tells the user to fix it, but never says to what.
All your app actually caught
POST /api/avatar
→ 415 Unsupported Media Type
{ "error": "unsupported_format", "accept": ["image/jpeg", "image/webp"], "max_px": 64 }What the agent does with it
How it works
It reads, first
Read-only tools you register run unattended: the server's real response, the rules your form never surfaced, and whatever your Sentry, status page, auth provider or repository already knows. Each one is your own credential, scoped to reading and nothing else.
It asks only what it cannot find
Ambiguous dates in a CSV could be day-first or month-first, and no tool knows which, so it asks once, in plain words.
It changes nothing without you
Any tool that writes stops for approval, showing the exact effect and what can be undone.
It proves the fix
Recovery is declared by a postcondition check, never by the model saying so.
It hands off what it cannot fix
Repair needs a pack you wrote for that failure. For anything else it explains the failure in plain words, hands the person a reference to quote, and gives support the whole trail behind it. Connect Linear, Jira, Zendesk or Intercom and the handoff opens a ticket there automatically. It can also post the handoff to Slack or Discord, so the right people see it without anyone re-explaining the problem from scratch.
One hook where the error message used to go
The whole integration is an npm package and a hook. scritch-react reports the failure and hands back a plain sentence and a verdict; you render it in your own components. The panel is text, never a widget you have to theme around.
One package
scritch-react on the client, scritch-react/server for the handler and the read-only connectors it may call.
One hook
useRecoverableTask wraps the call that failed. useGuidance turns the incident into a sentence and a userCanFix verdict.
Your own UI
The hook returns text, not a component. Your toast, your inline error, your styling. Nothing of ours reaches your design.
The API key stays on your server and is swapped for a short-lived token; it never reaches the browser.
import { useRecoverableTask, useGuidance } from "scritch-react";
function SaveButton({ draft }: { draft: Draft }) {
const { incident, run } = useRecoverableTask({ code: "draft.save.rejected" });
const guidance = useGuidance(incident);
return (
<>
<button onClick={() => run(() => api.saveDraft(draft))}>Save</button>
{/* Your toast, your styling. The hook only returns text. */}
{guidance.message && (
<Toast tone={guidance.userCanFix ? "action" : "info"}>
{guidance.message}
</Toast>
)}
</>
);
}What the agent can actually do
A small set of real tools, each with one job, grouped by what it is for. You decide which run on their own and which stop for you, and the model can only propose what is on this list.
Read evidence
Looks at what actually failed: the real error, the form, the file, your monitoring. Runs unattended.
Convert & transform
Reshapes a file into one your app will accept, so the person never has to do it by hand. Always asks your user first.
Repair
Changes your data to clear the failure. Always asks your user first.
Verify
Proves the fix holds with deterministic code, never the model's word, before anyone is told they are unblocked.
Explain
Writes the plain-language reason the blocked person reads. Never leaks an internal.
Ask the user
The one fact no tool can supply. Withdrawn the moment the person refuses an action.
It fixes the file instead of telling the user to
Your endpoint takes JPEG or WebP. The person uploaded a PNG. Instead of an error that sends them off to find a converter, the agent converts the image itself, behind hard size and pixel limits, and re-attaches a file your endpoint accepts. It always asks you first, because it is changing what gets stored.
headshot.png
headshot.jpg
The same photo, re-encoded to a format the upload accepts.
convert_upload: defineTool({
risk: "confirm", // always stops for a person
run: async ({ to }, { state }) => {
// convertImage is fenced: byte + pixel caps, format allowlist.
const out = await convertImage(state.upload.bytes, { to });
state.upload = { ...state.upload, format: to, bytes: out.bytes };
return { converted: true, format: to };
},
})It answers your user in their language
A blocked person in Rotterdam or Osaka reads the explanation in their own words, not in English with an apology attached. The agent takes the language from the browser (navigator.language) or a locale your app passes, as a BCP-47 tag, and writes the recovery in it. Your console, the evidence trail and every escalation stay in English, so your team reads one language while your users read theirs.
Your sign-in expired about forty minutes ago, so the save was rejected. Your draft is safe. Sign in again here, then press Save.
navigator.language
Je aanmelding is ongeveer veertig minuten geleden verlopen, dus het opslaan werd geweigerd. Je concept is veilig. Meld je hier opnieuw aan en klik dan op Opslaan.
Votre session a expiré il y a environ quarante minutes, l'enregistrement a donc été refusé. Votre brouillon est intact. Reconnectez-vous ici, puis cliquez sur Enregistrer.
Ihre Anmeldung ist vor etwa vierzig Minuten abgelaufen, deshalb wurde das Speichern abgelehnt. Ihr Entwurf ist sicher. Melden Sie sich hier erneut an und klicken Sie dann auf Speichern.
Tu sesión caducó hace unos cuarenta minutos, así que no se pudo guardar. Tu borrador está a salvo. Vuelve a iniciar sesión aquí y pulsa Guardar.
サインインが約40分前に期限切れになったため、保存できませんでした。下書きは残っています。このタブで再度サインインしてから、保存を押してください。
The reply-language toggle on the live demo above runs this for real. In a live install there is no toggle: the SDK reads the language off the request.
Pay only for recoveries
One flat rate for each recovery the agent actually made, with no per-seat fees and no monthly minimum. Start with a run of free recoveries, then a 14-day trial when you add a card. Set a spend cap and a bill can never surprise you.
Free to start
Your first 25 recoveries are free. Integrate, watch it work on your own app, decide later.
14 days, still free
Add a card at your first recovery and everything unlocks for 14 days, with recoveries still free.
$0.25 per recovery
After the trial, you pay only for recoveries that passed their postcondition, up to the cap you set.
The questions your security reviewer will ask
Who this is for
B2B software where a blocked user costs a support ticket: integrations, onboarding, imports, billing, anything with a form a server can refuse.
Not an error monitor. If you want exceptions grouped and alerted, you already have Sentry, and this reads from it rather than replacing it.
Every recovery is a ticket your team never sees
A blocked user used to mean a support email and someone waiting for a reply. Now the person gets unstuck in the moment, in your app, and your team just sees that it happened. The failures that generate the angriest tickets (a rejected upload, an expired session, a form that refused them for a rule it never showed) are the ones the agent handles first.
The user gets unstuck now
One question to answer or one change to approve, right where they got blocked. No email, no waiting for business hours, no second visit to explain it again.
Your team does nothing
The console is somewhere to watch, not a queue to work. Recoveries resolve on their own; you read the trail afterwards if you want to.
Support only hears the hard ones
What the agent cannot fix, it explains in plain words and hands off with the whole trail attached, so the ticket that does reach a human is already halfway solved.
Free recoveries to start, no credit card. After that you pay only for recoveries that actually worked, capped where you set it. No seats, no minimum.
See it on one of your own failures
A workspace takes a minute. Point it at one endpoint that refuses people and watch what it says.
Free recoveries to start, no credit card. After that you pay only for recoveries that actually worked, capped where you set it. No seats, no minimum.