# الذكريات القديمة في وكلاء الذكاء الاصطناعي: إلغاء أم استبدال أم تلاشٍ؟

> الذاكرة طويلة المدى بتفشل بهدوء: الـAgent يفتكر معلومة بطّلت صحيحة. تلات طرق للتعامل معاها، وإزاي Zekra وMem0 وGraphiti وEngram بيعملوها.

*2026-10-15T14:50:45Z*

# الذكريات القديمة في وكلاء الذكاء الاصطناعي: إلغاء أم استبدال أم تلاشٍ؟

لما تدي الـAI Agent ذاكرة طويلة المدى، أول مشكلة هتقابلك مش إنه بينسى. المشكلة إنه بيفتكر المعلومة الغلط وهو واثق منها.

في مارس الـAgent سجّل إن «الـAPI مبنية بـLaravel». في يونيو نقلتها لـGo وقلت له. في يوليو يسترجع المعلومتين، ويرتّب القديمة أعلى لأنها اتسترجعت مرات أكتر، ويكتب لك إجابة على أساس Laravel.

ده اللي اسمه Stale Memory. التخزين شغال والاسترجاع شغال، لكن مفيش قرار واضح: إيه اللي يحصل لمعلومة قديمة لما ييجي جديد يناقضها؟

**الإجابة المختصرة:** عندك تلات أدوات. **الإلغاء (Invalidate)** يعلّم المعلومة إنها بطّلت صحيحة من غير ما يمسحها. **الاستبدال (Supersede)** يربط القديمة بالجديدة اللي حلّت محلها. **التلاشي (Decay)** يقلل وزن المعلومات اللي محدش استخدمها من فترة. غالبًا هتحتاج الأولى والتانية وقت الكتابة، والتالتة وقت الترتيب. والمسح الفعلي للصفوف نادرًا ما يكون الحل الصح.

## ليه المشكلة دي صعبة؟

الـVector Search مش فاهم الزمن. جملة «بننشر بـDocker Compose» وجملة «مابقيناش بننشر بـDocker Compose، بقينا على Kubernetes» قريبين جدًا من بعض في الـEmbedding Space. لو سألت «بننشر إزاي؟» الاتنين هيرجعوا، والموديل هيخمّن أنهي فيهم الحالي، وكتير بيخمّن غلط.

الحل لازم يحصل في واحدة من نقطتين:

1. **وقت الكتابة:** لما تيجي معلومة جديدة، نقرر هل هي إضافة، ولا تكرار، ولا تحديث، ولا تناقض لحاجة موجودة.
2. **وقت القراءة:** وإحنا بنسترجع، نستبعد اللي بطّل صالح، وننزّل ترتيب القديم أو المهمل.

## الاستراتيجية الأولى: الإلغاء (حذف ناعم بتاريخ)

بدل ما نمسح الصف القديم، نحط له تاريخ `invalid_at`. الاسترجاع العادي يتجاهل الصفوف الملغاة، لكنها تفضل موجودة للمراجعة، وللإجابة عن سؤال زي: «كنا مصدقين إيه يوم كذا؟».

```sql
-- الصورة الحالية
SELECT * FROM memories
WHERE invalid_at IS NULL;

-- الصورة في لحظة سابقة
SELECT * FROM memories
WHERE created_at <= $1
  AND (invalid_at IS NULL OR invalid_at > $1);
```

دي الفكرة الأساسية في [Graphiti](https://github.com/getzep/graphiti)، محرك الـTemporal Knowledge Graph اللي ورا Zep. الـREADME بتاعه بيقولها صريحة: للحقائق نوافذ صلاحية، ولما المعلومة تتغير الحقيقة القديمة بتتلغي ومش بتتمسح. وبيتتبع الزمن بشكل Bi-temporal (إمتى الحقيقة كانت صحيحة، وإمتى النظام عرفها)، ودي أكمل نسخة من الأسلوب ده.

وورقة [Mem0](https://arxiv.org/abs/2504.19413) (‏Chhikara وزملاؤه، أبريل 2025) ماشية على نفس الخط في نسختها المعتمدة على الـGraph: العلاقات المتعارضة بتتعلّم إنها غير صالحة بدل ما تتمسح، عشان يفضل ممكن نستنتج حاجات عن الزمن.

**مناسبة لـ:** الحقائق اللي بتنقلب («بنستخدم X» ← «مابقيناش بنستخدم X»)، والـCompliance، والـDebugging.
**خلي بالك:** محتاج Trigger موثوق، حاجة تقرر إن المعلومة الجديدة بتناقض القديمة فعلًا.

## الاستراتيجية التانية: الاستبدال (مع الاحتفاظ بالسلسلة)

الاستبدال هو إلغاء ومعاه مؤشر: الصف القديم ياخد `invalid_at = now()` و`superseded_by = <id الجديد>`، وتقدر تمشي في السلسلة من أي نسخة لحد النسخة الحالية.

بيناسب الحقائق اللي بتتطور مش اللي بتنقلب: سعر، أو حجم فريق، أو رقم إصدار، أو قرار اتعدّل.

فيه طريقتين منتشرتين نعرف بيهم إن الجديد بيستبدل القديم:

- **بالهوية:** اللي بيكتب يبعت مفتاح ثابت. [Engram](https://github.com/Gentleman-Programming/engram)، وهو Memory Server مكتوب بـGo لوكلاء البرمجة، بيستخدم `topic_key` زي `architecture/auth-model`، ولو حفظت بنفس المفتاح بيحدّث الموضوع بدل ما يضيف نسخة شبهه. طريقة محددة ورخيصة، لكنها بتشتغل بس لما الكاتب يعرف المفتاح.
- **بالتشابه:** نعمل Embedding للنص الجديد، ونلاقي أقرب ذكرى، ونحدّثها لو التشابه عدّى حد معيّن. مش محتاجة تعاون من الكاتب، لكن الحد محتاج ضبط، وممكن تدمج حاجات شكلها بس متشابه.

ورقة Mem0 بتوصف نسخة بيقودها الـLLM: لكل حقيقة مرشّحة، Tool Call بيختار **ADD أو UPDATE أو DELETE أو NOOP** بالمقارنة مع أقرب الذكريات المخزنة. مرنة، لكن تمنها Model Call مع كل كتابة. والتوثيق الحالي لـMem0 بيوصف الـ`add` في النسخة المستضافة والمفتوحة إنه بيراكم الذكريات، مع أوامر Update وDelete صريحة لما تحتاجها، فاتأكد من سلوك النسخة اللي عندك قبل ما تعتمد عليه.

## الاستراتيجية التالتة: التلاشي (ترتيب أقل للقديم المهمل)

التلاشي مش بيلغي حاجة. هو بيغيّر درجة الترتيب بس، بحيث الذكرى اللي محدش لمسها من زمان تخسر قدام الجديدة أو المستخدمة كتير.

أشهر مرجع هنا ورقة [Generative Agents](https://arxiv.org/abs/2304.03442) (‏Park وزملاؤه، 2023). درجة الاسترجاع فيها بتجمع **الحداثة** و**الأهمية** و**الصلة**، والحداثة عبارة عن Exponential Decay حسب الوقت من آخر مرة الذكرى اتسترجعت فيها، بمعامل 0.995 لكل ساعة لعب.

نسخة مبسطة من الدرجة دي:

```python
import math

def score(mem, relevance, now_hours, decay=0.995):
    recency = decay ** (now_hours - mem.last_accessed_hours)
    return relevance + mem.importance + recency  # طبّع كل قيمة لـ 0..1 الأول
```

**مناسب لـ:** مخازن ذاكرة كبيرة فيها تفاصيل قديمة بتزاحم المعلومات المفيدة.
**خلي بالك:** التلاشي لوحده عمره ما يحل تناقض. معلومة غلط بتتستخدم كل يوم عمرها ما هتتلاشى. عشان كده التلاشي مكانه جنب الإلغاء، مش بداله.

## Zekra بتتعامل مع ده إزاي؟

[Zekra](https://github.com/fadymondy/zekra) هي Memory Server مفتوحة المصدر (AGPL-3.0) ببنيها بـGo، وبتوفّر الذاكرة للوكلاء عن طريق MCP. ده اللي الكود بيعمله النهارده، واللي مش بيعمله.

كل كتابة بتعدّي على قرار كتابة في `writedecision.go` بحدّين للـCosine Similarity:

```go
const (
    simDuplicate = 0.97 // شبه متطابق: ماتخزّنش تاني
    simSupersede = 0.88 // نفس الموضوع: استبدل الصف القديم
)
```

والقرار بيمشي بالترتيب ده:

| الشرط | القرار |
|---|---|
| النص بعد التطبيع متطابق | **NOOP** |
| التشابه ≥ 0.88 والنص الجديد فيه نفي («no longer» أو «is wrong» أو «correction:» أو «retract»…) | **INVALIDATE** |
| التشابه ≥ 0.97 | **NOOP** |
| التشابه ≥ 0.88 | **UPDATE** (استبدال) |
| غير كده | **ADD** |

شوية تفاصيل بتفرق في الاستخدام الفعلي:

- **الاستبدال ناعم:** الصف القديم ياخد `invalid_at = now()` و`superseded_by = <id الجديد>`. مفيش مسح فعلي، وحتى أداة `memory_forget` بتلغي بس.
- **الـNOOP ليه أثر:** بيزوّد `access_count` و`last_accessed_at`، فتكرار المعلومة بيقوّيها بدل ما يكررها.
- **الهوية أهم من التشابه في الاستيراد:** لو الكتابة معاها `source_ref` (رقم Issue أو مسار ملف)، بيتعامل معاه كهوية: لو الـref موجود يتحدّث الصف ده بالظبط. الميزة دي اتضافت بعد ما الاستيراد بالتشابه بس دمج 108 Release Notes منفصلين في 2.
- **السفر في الزمن موجود:** الاسترجاع بيقبل `since` و`until` و`asOf`. مع `asOf` بترجع الصفوف اللي نافذة صلاحيتها فيها اللحظة دي، حتى اللي اتستبدلت بعد كده.

والحدود بصراحة:

- **فحص النفي قائمة كلمات، عن قصد.** تعليق الكود بيقول إنه متحفّظ متعمّد: لو فاته نفي، القرار يرجع UPDATE، عمره ما يوصل لمسح غلط. تناقض من غير الكلمات دي («نقلنا لـGo») بيتعامل معاه كتحديث مش إلغاء. وكاشف تناقضات حقيقي مخطط له كخطوة لاحقة بالـLLM.
- **التلاشي لسه مش متنفّذ.** الـSpec بيوصفه (تقليل الأهمية مع الوقت، والذكريات المهمة تتلاشى أبطأ)، لكنه في الـRoadmap مش في الكود. الترتيب حاليًا Hybrid بين Vector وBM25 مع Reranking، وأهمية بتتحسب وقت الكتابة.

## مقارنة عادلة

| | إزاي بيعرف إن المعلومة قدمت | بيحتفظ بالتاريخ | تكلفة الكتابة | الخطر الأساسي |
|---|---|---|---|---|
| **Mem0 (الورقة)** | LLM يختار ADD/UPDATE/DELETE/NOOP | نسخة الـGraph: أيوه | LLM Call | أخطاء حكم الموديل |
| **Graphiti** | استخراج حقائق + Temporal Edges | أيوه، Bi-temporal | استخراج بالـLLM | تعقيد الـPipeline |
| **Engram** | الكاتب يعيد استخدام `topic_key`، وأدوات مراجعة للتعارض | الموضوع يتحدّث مكانه | صفر (محدد) | الكاتب لازم يختار المفاتيح |
| **Zekra** | حدود Cosine + كلمات نفي، وهوية بالـ`source_ref` | أيوه (`superseded_by` و`asOf`) | Embedding واحد | التناقضات الضمنية بتفوته |

مفيش واحد فيهم هو «الصح». الكتابة اللي بيحكم عليها LLM بتلقط تناقضات أكتر، لكنها أغلى وممكن تغلط. القواعد المحددة رخيصة ومتوقعة لكن بتفوّت الحالات الدقيقة. والمفاتيح ممتازة لما الكاتب يعرفها، وملهاش لازمة لما مايعرفهاش.

## وصفة عملية

لو هتضيف ذاكرة طويلة المدى لـAgent الربع ده:

1. **ماتمسحش فعليًا كـDefault.** ضيف عمودين `invalid_at` و`superseded_by` من أول يوم.
2. **استخدم الهوية لو موجودة.** رقم Ticket أو مسار ملف أو URL مفاتيح استبدال أحسن بكتير من التشابه.
3. **استخدم حدود التشابه لباقي الحالات** مع قاعدة تناقض متحفظة، واضبط الحدود على بياناتك.
4. **استبعد الصفوف الملغاة وقت الاسترجاع**، ووفّر `asOf` عشان تعرف الـAgent كان مصدق إيه الأسبوع اللي فات.
5. **ضيف التلاشي في الآخر**، كعنصر في الترتيب، لما المخزن يكبر لدرجة إن التفاصيل القديمة تزاحم النتائج فعلًا.
6. **خلي الـAgent يقدر يقول «ده غلط».** وفّر أداة Forget أو Invalidate، عشان تصحيح المستخدم يبقى بيانات مش مجرد رسالة في الشات.

## أسئلة شائعة

**أمسح الذكريات القديمة وخلاص؟** بس لأسباب خصوصية أو حذف قانوني. غير كده هتخسر سجل المراجعة، وهتخسر قدرتك تشرح إجابات قديمة.

**Context Window أكبر يحل المشكلة؟** لأ. Context أكبر يعني معلومات قديمة أكتر بتتنافس جوه الـPrompt. الصلاحية لازم تتحسم قبل ما الـPrompt يتبني.

**التلاشي يغني عن الإلغاء؟** لأ. التلاشي للمعلومات المهملة، والإلغاء للمعلومات الغلط.

## الخطوة الجاية

- اقرا الفكرة دي طلعت إزاي من استخدام حقيقي في [ذاكرة الـAI Agents عمليًا: دروس من بناء CaBrain](https://fadymondy.com/ar/blog/agent-memory-in-practice-cabrain).
- اتصفّح كود Zekra على [GitHub](https://github.com/fadymondy/zekra).
- محتاج ذاكرة أو MCP Server جوه منتجك؟ شوف [تطوير MCP Servers](https://fadymondy.com/ar/services/mcp-server-development).

---

Source: https://zekra.dev/ar/blog/stale-memories-ai-agents-invalidate-supersede-decay
