Skip to content

Erase a person's data

View as Markdown

This guide erases an end user's words when they ask you to forget them, and says what it clears, what it can't reach, and how it survives a backup restore.

When a person asks you to forget them, an erasure clears their words from the runtime: their memory facts, their conversations, the runs that served them, and what those runs left behind. It runs in the background.

  • Tenant admin, on a runtime with Kindgi 0.1's CLI. Only a tenant admin can start an erasure.
  • Who to erase: the id your app gives the person, their participantId, or one of the other selectors in Who an erasure can name.
  • The erasure ledger's key, set on the runtime, so the erasure survives a backup restore, as After a backup restore says.

An erasure names exactly one of:

  • an app's end user, with --participant: the participantId your app gives on their conversations and facts;
  • an external subject, with --external: a person facts name in their subjects who never talked to an agent;
  • one conversation, with --conversation;
  • one fact, every revision, with --fact.

Erasing a Kindgi user, such as an employee of yours, isn't offered: what they run is your organization's work, not their personal data. The API refuses it with a 400.

An erasure starts at once, and clears the person's words in the background:

Terminal window
pnpm exec kindgi memory erasures create --participant=end-7

--participant=end-7 names the end user. create answers at once, with 202 and the erasure, and the work goes on in the background:

{
"id": "b8516687-01dc-4855-86ed-816455bf3bf8",
"selectorKind": "participant",
"selector": {
"subject": {
"id": "end-7",
"kind": "participant"
}
},
"status": "pending",
"phase": "seed",
"requestedBy": "user:60328bcc-a6e4-4854-9442-fbdcc460838c",
"matchable": true,
"counts": {},
"attempts": 0,
"createdAt": "2026-10-09T13:30:39.645Z"
}

Follow it, where <erasure-id> is its id:

Terminal window
pnpm exec kindgi memory erasures get <erasure-id>
pnpm exec kindgi memory erasures list

get shows the erasure:

  • status: pending, running, waiting-on-run, completed or failed.
  • phase: seed, expand, settle, erase, then done.
  • counts: what each store cleared so far.
  • lastError: the last error it will retry.

From your app, in TypeScript, it's memory.erasures.create, get, list, resume, export and replay. In Python, it's memory.create_erasure, get_erasure, list_erasures, resume_erasure, export_erasures and replay_erasures. Over HTTP, it's /v1/memory/erasures.

From the moment an erasure starts:

  • no memory read returns the facts it names, even before they're cleared;
  • a new turn for the person, in their conversation or with their participantId, is refused with 409 erasure-in-progress. The CLI prints Error [erasure-in-progress]: An erasure of this person's words is in progress: no new turn starts for them until it completes.;
  • writing a fact for them is refused the same way, and so is an agent's kindgi_remember.

A turn of theirs that's already running or waiting isn't refused. In the settle phase, before it clears anything, the erasure:

  • cancels their waiting turns, and the run's history says erased;
  • so withdraws the approvals those turns waited on, with withdrawnBecause: run-cancelled;
  • waits for a turn that's executing.

Here it's waiting for a turn that's still executing:

{
"id": "044cd0b9-244e-471d-916c-431edc0dbc42",
"selectorKind": "participant",
"selector": {
"subject": {
"id": "end-61",
"kind": "participant"
}
},
"status": "running",
"phase": "settle",
"requestedBy": "user:973bc704-cfcc-49be-8b3d-cdbe61dd51e8",
"matchable": true,
"counts": {},
"attempts": 0,
"lastError": "not-yet:run-executing",
"createdAt": "2026-10-09T12:45:29.410Z",
"startedAt": "2026-10-09T12:45:29.417Z"
}

And the same erasure, completed. It keeps no trace of whom it erased: the selector is gone.

{
"id": "044cd0b9-244e-471d-916c-431edc0dbc42",
"selectorKind": "participant",
"status": "completed",
"phase": "done",
"requestedBy": "user:973bc704-cfcc-49be-8b3d-cdbe61dd51e8",
"matchable": true,
"counts": {
"kernel_runs": 3,
"memory_facts": 1,
"hitl_approvals": 1,
"kernel_waitpoints": 1,
"memory_embeddings": 0,
"memory_log_entries": 4,
"agent_conversations": 2,
"agent_run_snapshots": 3,
"conversation_recall": 4,
"kernel_runs_cancelled": 1,
"memory_fact_revisions": 1,
"kernel_journal_entries": 95
},
"attempts": 0,
"createdAt": "2026-10-09T12:45:29.410Z",
"startedAt": "2026-10-09T12:45:29.417Z",
"completedAt": "2026-10-09T12:45:41.704Z"
}

The turn it cancelled ends its journal like this, with nothing left of what it held, where <run-id> is the turn's run id:

Terminal window
pnpm exec kindgi runs journal <run-id>
{
"data": [
…
{
"sequence": 4,
"kind": "wait.suspended",
"nodeId": "setup",
"timestamp": "2026-10-09T12:45:28.617Z"
},
{
"sequence": 5,
"kind": "run.cancelled",
"payload": {
"reason": "erased"
},
"timestamp": "2026-10-09T12:45:41.565Z"
}
],
…
}
Excerpt

Once it completes, new turns for the person start again.

  • Their facts, every revision, and their vectors. A fact written from an erased one goes to review: a memory-fact approval, reason derived-from-erased.
  • Their conversations, with their messages and what recall indexed. The conversation then answers 404.
  • The runs that served them: each run's input, output and journal, its snapshots and waitpoints, and what it left in provenance. The run itself stays, with contentErasedAt. Judging such a run is refused, 410 run-erased.
  • The reasons in its ratings: a judgment of such a run keeps its verdict, who judged and when, and loses its reason and the judged item's value. A judging-queue item still waiting for it closes as erased, and a dismissal's reason is cleared.
  • What else held their words:
    • approvals' text, such as a tool call's arguments and a reviewer's rationale. The title, the status and who decided stay;
    • audit events' text, and monitoring observations;
    • a webhook delivery's error message, and a model call's error message (its cost stays);
    • evals' copies. A test set's case copied from an erased run reads erased: true, and comparisons leave it out and count it, in summary.erased.

Audit events of a kind your compliance setup holds, a legal hold, keep their text, and the erasure counts them. A fact under legal hold stops the erasure before it starts: 409 legal-hold, with the held facts in details.factIds.

  • What the runtime logged to your log system: keep production at info or warn, as What logs hold explains.
  • Signed exports already made, such as an approval's audit bundle.
  • A webhook receiver's copy of a delivery that was already sent.
  • An improvement pass's drafted prompts and hypotheses. A draft that quotes eight or more words in a row of a test case is refused, and such quotes are cut from a hypothesis, so they hold a paraphrase at most. The reviewer who approves a proposal is the check.

A person's turn can sit inside a flow that also serves other people. Cancelling it now could end their work too. So the erasure waits for that run, with the status waiting-on-run and waitingOn naming it. It waits until a deadline, KINDGI_ERASURE_SHARED_WAIT_MS, 7 days by default. Then it cancels the run and goes on. Erasing a person also clears the records of flows they shared with others.

To act before the deadline, where <erasure-id> is the erasure's id:

Terminal window
pnpm exec kindgi memory erasures resume <erasure-id> # try again now
pnpm exec kindgi memory erasures resume <erasure-id> --force # stop waiting: cancel that run and go on

Each prints the erasure as JSON, with its status and phase.

Restoring a backup brings back what an erasure cleared, and rolls the erasure ledger back too. So export the ledger off-box regularly, and replay it after a restore:

Terminal window
pnpm exec kindgi memory erasures export --out=erasure-ledger.json

--out is the file it writes. It prints:

{
"wrote": "erasure-ledger.json",
"erasures": 1
}

After a restore, replay that file:

Terminal window
pnpm exec kindgi memory erasures replay erasure-ledger.json

The ledger keeps no identifier, only a keyed hash of whom each erasure erased. A replay needs the runtime's erasure ledger key to find them again. Without the key, an erasure still runs, but it answers with an erasure-unmatchable warning. A replay can't match that erasure. Erasures and backups sets up the key and the restore.