Brainby arc-labs/docs
Python SDK

Feedback and forget

Send corrections via recall.memories.feedback and remove memories via recall.memories.forget — soft and hard delete semantics.

feedback()

POST/v1/feedbackAPI keystable
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

ParameterTypeRequired
memory_idstrrequired

The target memory ID. Must be in the caller's scope.

signalstrrequired

One of 'correct', 'duplicate', 'irrelevant'. See semantics below.

replacementstroptional

Required when signal='correct'. The corrected content. The server creates a new memory carrying the replacement content and marks the original as superseded.

duplicate_ofstroptional

Required when signal='duplicate'. The canonical memory ID this duplicate should fold into.

reasonstroptional

Optional free-text rationale, surfaced in audit logs and the dashboard correction stream.

idempotency_keystroptional

Auto-generated UUID by default; pass one explicitly for replay-safe correction submission from a UI.

Signal semantics

OptionTypeDefault / Env
correctstr

The 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.

duplicatestr

The memory restates an existing canonical memory. The server folds it into the canonical row identified by duplicate_of.

irrelevantstr

The 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 irrelevant and a "correct this" popup to correct.
  • 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 contentsignal='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()

POST/v1/forgetAPI keystable
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

ParameterTypeRequired
idslist[str]optional

The 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).

hardbooloptional

When 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_keystroptional

Auto-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

OptionTypeDefault / Env
hard=Falsedefault

Sets 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=True

Removes 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

CodeStatusRetry
RecallAuthError401/403fatal
Auth failed.
RecallNotFoundError404fatal

memory_id does not exist or out of scope.

RecallValidationError422fatal

Invalid signal value, missing replacement for signal='correct', duplicate_of not found, etc.

RecallRateLimitError429fatal
Rate limit exceeded.

Forget 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?

On this page