Feedback and forget
Send corrections via recall.memories.feedback and remove memories via recall.memories.forget — soft and hard delete semantics.
feedback()
def feedback(
self,
memory_id: str,
signal: str,
replacement: Optional[str] = None,
duplicate_of: Optional[str] = None,
reason: Optional[str] = None,
*,
idempotency_key: Optional[str] = None,
) -> Any: ...Send a correction signal about a single memory. The server uses the signal to update the memory's confidence, mark it as superseded, or merge it with its canonical duplicate.
Parameters
memory_idstrrequiredThe target memory ID. Must be in the caller's scope.
signalstrrequiredOne of 'correct', 'duplicate', 'irrelevant'. See semantics below.
replacementstroptionalRequired when signal='correct'. The corrected content. The server
creates a new memory carrying the replacement content and marks the
original as superseded.
duplicate_ofstroptionalRequired when signal='duplicate'. The canonical memory ID this
duplicate should fold into.
reasonstroptionalOptional free-text rationale, surfaced in audit logs and the dashboard correction stream.
idempotency_keystroptionalAuto-generated UUID by default; pass one explicitly for replay-safe correction submission from a UI.
Signal semantics
correctstrThe memory has a factual error. The server soft-supersedes the
original and creates a new memory with replacement as content,
pointing back to the original via supersedes.
duplicatestrThe memory restates an existing canonical memory. The server folds it
into the canonical row identified by duplicate_of.
irrelevantstrThe memory is correct but not useful for the agent's purpose (a fragment captured from passing chitchat). The server soft-deletes it and decays the embedding's contribution to future retrieval.
Examples
# Correct a wrong fact
recall.memories.feedback(
memory_id='m_01HEX…',
signal='correct',
replacement='User prefers light mode, not dark mode.',
reason='User clarified during onboarding call.',
)
# Mark a duplicate
recall.memories.feedback(
memory_id='m_dup1',
signal='duplicate',
duplicate_of='m_canonical',
)
# Mark as irrelevant noise
recall.memories.feedback(
memory_id='m_chitchat',
signal='irrelevant',
reason='Off-topic banter, not actionable.',
)await recall.memories.feedback(
memory_id='m_01HEX…',
signal='correct',
replacement='User prefers light mode, not dark mode.',
)
await recall.memories.feedback(
memory_id='m_dup1',
signal='duplicate',
duplicate_of='m_canonical',
)
await recall.memories.feedback(
memory_id='m_chitchat',
signal='irrelevant',
)When to use
- Build a "thumbs up / thumbs down" UI on top of memories surfaced to
end users, mapping thumbs-down to
irrelevantand a "correct this" popup tocorrect. - Run a periodic dedupe job that scans for high-similarity memory pairs
and submits
signal='duplicate'when the embeddings agree above a threshold. - Plumb agent-level reflection: when the LLM detects a contradiction in
the memory store, write a
feedback(signal='correct')call as the resolution.
feedback does not retroactively change the memory's content —
signal='correct' creates a new memory and supersedes the old one. To
edit the existing row in place, use recall.memories.update(...)
instead. Use feedback when you want the audit trail; use update
when you don't need it.
forget()
def forget(
self,
ids: Optional[list[str]] = None,
hard: bool = False,
*,
idempotency_key: Optional[str] = None,
) -> Any: ...Remove a batch of memories. Soft delete by default (hard=False); hard
delete on demand. Hard delete is irreversible.
Parameters
idslist[str]optionalThe memory IDs to remove. Sent as memory_ids on the wire. Omit to
forget everything in the caller's scope (rare; intended for
namespace cleanup).
hardbooloptionalWhen True, the rows are removed from the database — the embedding
index is rebuilt and audit-trail evidence is preserved only in the
write-ahead log. When False, rows are soft-deleted (deleted_at
set) and remain queryable through the admin plane.
idempotency_keystroptionalAuto-generated UUID by default. Override when reissuing a deletion after a transient network failure.
Examples
# Soft delete a batch
recall.memories.forget(ids=['m_a', 'm_b', 'm_c'])
# Hard delete for GDPR erasure
recall.memories.forget(
ids=['m_pii_1', 'm_pii_2'],
hard=True,
)await recall.memories.forget(ids=['m_a', 'm_b', 'm_c'])
await recall.memories.forget(
ids=['m_pii_1', 'm_pii_2'],
hard=True,
)Soft vs hard delete
hard=FalsedefaultSets deleted_at on the row. The memory stops appearing in search,
but the row remains in the database for audit and recovery. The
namespace's retention worker may eventually hard-delete soft-deleted
rows older than the retention window.
hard=TrueRemoves the row from the database. The embedding is removed from the pgvector index. There is no recovery — only the write-ahead log can reconstruct the memory's previous existence, and that's not exposed via the SDK.
When to use
- Soft delete for normal "the user no longer cares about this" removal. Cheap, recoverable, audit-trail-preserving.
- Hard delete for legal/compliance erasure (GDPR right-to-erasure, CCPA delete requests) and namespace teardown. Reserve for rare occasions — soft delete is almost always the right answer.
Build a "right to erasure" workflow on top of forget(hard=True). On
user deletion request, run forget(hard=True) against every memory
with the user's ID in scope, then run a retention pruning pass to
remove soft-deleted rows older than the retention window. See
retention for the namespace-level retention
config.
Errors
RecallAuthError401/403fatalRecallNotFoundError404fatalmemory_id does not exist or out of scope.
RecallValidationError422fatalInvalid signal value, missing replacement for signal='correct',
duplicate_of not found, etc.
RecallRateLimitError429fatalForget vs delete
recall.memories.delete(memory_id) removes one memory by ID — useful in
small handlers where you have one ID. recall.memories.forget(ids=[...])
removes many in one call — preferred for batch cleanup. Both default to
soft delete; only forget accepts hard=True.
Was this page helpful?