#AI#大模型#AI Agent

Agent Memory Write Admission Gates: From Candidate Facts to Revocable Commits

Preferences and facts a model extracts from a conversation cannot become long-term memory directly. This article designs candidate proposals, deterministic validation, deduplicated commits, revocation lineage, and a contamination-rate gate.

Key Points

  • Topic Focus: AI、大模型、AI Agent
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

The most dangerous long-term memory incident is not missing one record, but permanently writing a joke, a model inference, or an already-rejected claim as fact. When a user says "let's just use npm this time," it does not mean their preference changed from pnpm to npm; when the model says "you're probably in Shanghai," it should certainly not become a location profile. Agent Memory System introduced memory types; this article focuses only on production writes: the model can only propose candidates, and only the deterministic service has commit authority.

Define extraction results as candidates, not facts

The extractor outputs a MemoryProposal constrained by a JSON Schema, carrying the tenant, subject, normalized scope, a bounded set of kinds and keys, source offsets, version, confidence, and expiry. The deterministic evidence resolver first assigns stableCandidateOrdinal based on source clause position and fixed schema field order, then the model fills in the value; therefore neither the value nor the model's output order participate in candidate identity.

ts
type MemoryProposal = {
  proposalId: string;
  tenantId: string;
  subjectId: string;
  scope: { kind: string; key: string };
  kind: "preference" | "profile" | "decision" | "task_fact";
  normalizedKey: string;
  stableCandidateOrdinal: number;
  value: unknown;
  evidence: { messageId: string; start: number; end: number; sha256: string };
  extractorVersion: string;
  confidence: number;
  expiresAt: string | null;
};

const identity = { schema: 1, tenantId, subjectId, scope, kind, normalizedKey,
  messageId, start, end, stableCandidateOrdinal, extractorVersion };
const proposalId = sha256(jcs(identity));
const payloadHash = sha256(jcs({ value, confidence, expiresAt, evidenceHash }));

JCS is the unambiguous canonical encoding from RFC 8785; identity and payload are hashed separately. This way retries of the same candidate can be deduplicated, and tampered values or expirations surface as the same ID with a different payload, rather than silently obtaining a new approval.

The server re-reads messageId and verifies that the tenant, text hash, and offsets actually correspond to the evidence; the model cannot self-report explicit=true. Passwords, tokens, payment data, and types not listed in the schema are rejected outright. Candidates from external web pages or tool results continue to carry taint and are isolated per Untrusted Data Taint Propagation; they cannot be upgraded to user facts just because they passed through a summary.

Use a rule matrix to decide auto-commit, confirm, or discard

Explicit phrasing like "please remember X" where the value passes field validation can be auto-committed as a short-term revocable record; preferences inferred from behavior go into NEEDS_CONFIRMATION; sensitive profiles and cross-subject statements are always rejected. Confidence is used only for ranking and cannot override source, consent, and sensitivity rules.

text
explicit + allowed_key + evidence_exact  -> COMMIT
explicit + conflicts_current             -> CONFIRM_REPLACE
inferred + reversible_preference         -> NEEDS_CONFIRMATION
secret | cross_tenant | missing_evidence -> REJECT

Confirmation requests must display the normalized concrete fact and its expiry, and user approval binds to proposalId + payloadHash. Modifying the value afterward invalidates the approval; broad authorizations like "user agreed to remember" cannot be reused. The execution binding of Agent Human Approval Contracts applies equally here.

Commits must be idempotent and preserve a revocation path

The database has a unique constraint on (tenant_id, proposal_id). Replaying the same candidate returns the original result; the same ID with a different payload enters a protocol conflict, but proposal deduplication cannot serialize the contention of two proposals over the same fact. The transaction performs INSERT ... ON CONFLICT DO NOTHING followed by SELECT ... FOR UPDATE on the unique memory_fact_key(tenant,subject,scope,kind,normalized_key), serializing even facts that did not previously exist; it then reads the current version inside the lock and redoes the supersession decision.

Only when there is no conflict does the transaction perform a CAS on expectedCurrentVersion, writing the immutable proposal, a new fact version, the current projection, and the outbox together in one commit. On CAS failure or discovering the current value has changed inside the lock, a low-risk explicit replacement enters CONFIRM_REPLACE, while anything that cannot be auto-adjudicated enters CONFLICT; the loser must never overwrite. The vector worker only consumes committed memoryId:version records. The at-least-once delivery of Background Job Queues is deduplicated by the same version key.

Each memory stores derivedFrom and supersedes. When the user revokes the source message or rejects a candidate, first change the status to REVOKED and publish an invalidation event, then clean up the projection, vectors, and cache. Do not physically delete the only audit evidence before trying to find copies.

Accept with contamination samples, not success demos

The fixed test set must at least include negations, citations of others' opinions, sarcasm, temporary choices, retractions, same-named subjects, malicious tool output, repeated webhooks, and same-key different-payload cases. A concurrency barrier lets two different proposals write the same fact key while current does not yet exist, asserting that the key row lock serializes them: at most one auto-commits and the loser enters CONFIRM_REPLACE/CONFLICT; then concurrently supersede from an existing version, asserting no lost updates. Also verify that "I don't live in Beijing" and "my colleague lives in Beijing" can never be written as the user's address, and that recall is zero after revocation.

Compute offline false_commit_rate = incorrect_auto_commits / total_auto_commits, confirmation acceptance rate, duplicate-effect count, and revocation-residual count. On release, first only record proposals without affecting recall; after a week of manual review of real samples, open up only 5% auto-commit for low-risk keys. Any cross-tenant write, secret ingestion, or revocation residual triggers a hard rollback.

A rollback only turns off the commit switch and lets reads continue using the previous policy version; new proposals stay in an isolation table for postmortem, and schema or evidence validation cannot be skipped to restore availability. Agent Replay Evaluation can rerun different extractors on the same source messages. The goal of write admission is not to remember the most, but to let every long-term fact answer: who proposed it, on what basis, who approved it, and how to revoke it.

Further Reading

Related

Related Content