Skip to content

# 13 — Agent Memory System (MicroDAO)

Цей документ описує архітектуру памʼяті агентів у MicroDAO:

- short-term, mid-term, long-term;

- персональна / командна / глобальна памʼять;

- Co-Memory та RAG;

- як памʼять інтегрується з Agent Runtime Core (12);

- як це використовувати для еволюційного агента (09).

# 1. Цілі системи памʼяті

1. Зробити агентів контекстними: вони памʼятають діалоги, рішення, факти.

2. Розділити памʼять за часом і рівнем узагальнення:

3. short-term → останні репліки;

4. mid-term → сесії / таски / важливі обговорення;

5. long-term → узагальнені знання про команду/проєкт.

6. Дати можливість:

7. переглядати памʼять,

8. очищати її,

9. контролювати, що саме зберігається.

10. Готувати основу для DAGI та Train-to-Earn (агрегована колективна памʼять).

# 2. Рівні памʼяті

## 2.1. Short-Term Memory (STM)

- Останні N повідомлень (user ↔ агент) в поточному контексті (канал / чат).

- Зберігається як `AgentMessage` (див. 12_agent_runtime_core.md).

- Використовується в кожному запиті до LLM.

Приклад:

- останні 20–50 повідомлень у каналі;

- спеціальні system-репліки (актуальні інструкції).

## 2.2. Mid-Term Memory (MTM)

- Важливі фрагменти діалогів, завдання, рішення, які стосуються:

- конкретних тасок,

- спринтів,

- обговорень проєктів.

- Може бути збережена як:

- підсумки сесій,

- «замітки агента»,

- структуровані записи (JSON).

Використання:

- контекст для агентських звітів,

- нагадування про те, що команда домовилася зробити.

## 2.3. Long-Term Memory (LTM)

- Узагальнені факти про:

- команду,

- проєкти,

- стилі роботи,

- правила,

- терміни та словник.

Формат:

- списки фактів (текстові),

- векторні ембедінги (для RAG).

Приклади фактів:

- «Наш основний продукт — MicroDAO, ми фокусуємося на невеликих спільнотах.»

- «Для терміну "DAGI" ми маємо окремий опис, який завжди треба враховувати.»

# 3. Простір памʼяті (Scopes)

Памʼять розділяється за обсягом:

1. Personal

2. пов'язана з конкретним користувачем (Personal Agent).

3. Channel

4. стосується конкретного каналу, де агент працює.

5. Team (microDAO)

6. загальна памʼять всієї спільноти.

7. Global / DAGI

8. узагальнені патерни, які можуть бути анонімізовано експортовані.

# 4. Модель даних

### Таблиці (логічно):

- `agent_memory_events`

- id

- agent_id

- team_id

- channel_id (optional)

- user_id (optional)

- scope: `short_term | mid_term | long_term`

- kind: `message | fact | summary | note`

- body: text/json

- created_at

- `agent_memory_facts_vector`

- id

- team_id

- agent_id

- fact_text

- embedding (vector)

- metadata (json)

- `agent_memory_snapshots` (опціонально)

- агреговані snapshot-и стану памʼяті.

# 5. AgentMemoryAdapter (деталізація)

Посилання на 12_agent_runtime_core.md:

    export interface AgentMemoryAdapter {
      loadShortTerm(ctx: AgentContext): Promise<AgentMessage[]>;
      loadLongTerm(ctx: AgentContext): Promise<string[]>;
      saveTurn(ctx: AgentContext, turn: AgentMessage): Promise<void>;
      appendFact(ctx: AgentContext, fact: string): Promise<void>;
    }

### 5.1. loadShortTerm

- Витягує останні `N` подій типу `kind = 'message'` зі scope `short_term` для:

- `agent_id`,

- `team_id`,

- `channel_id` (якщо є).

### 5.2. loadLongTerm

- Витягує список текстових фактів зі scope `long_term` (через `agent_memory_events` з `kind = 'fact'`).

### 5.3. saveTurn

- Записує повідомлення user/assistant як `message` (short-term).

- Якщо увімкнено автоматичне ущільнення памʼяті — може переносити деякі фрагменти в mid-term.

### 5.4. appendFact

- Додає факт у long-term (як `kind = 'fact'`).

- Додатково:

- рахує ембедінг (через окремий LLM/embedding API),

- зберігає в `agent_memory_facts_vector`.

# 6. RAG (Retrieval-Augmented Generation)

### 6.1. Retrieval

Коли агент отримує новий запит, перед викликом LLM:

1. Формує пошуковий запит (query) з тексту user.

2. Шукає релевантні факти у:

3. `agent_memory_facts_vector`,

4. (опційно) Co-Memory документів (файли, wiki).

5. Обмежує контекст, наприклад Top-K = 5–10 фактів.

### 6.2. Включення в промпт

Факти додаються в `LONG_TERM_MEMORY` (див. 12_agent_runtime_core.md):

    const memoryMsg: AgentMessage = {
      role: "system",
      content:
        "LONG_TERM_MEMORY:\n" +
        retrievedFacts.map((f, i) => `- ${f.text}`).join("\n"),
    };

# 7. Перетікання памʼяті (compression / distillation)

Щоб памʼять не перетворювалась на хаос, потрібні періодичні "distillation jobs".

### 7.1. Distillation Job

Раз на N повідомлень або раз на день для команди:

1. Беремо всі short-term повідомлення за певний період.

2. Feed-имо їх у Meta-Agent (див. 09_evolutionary_agent.md).

3. Отримуємо:

4. конспект (summary),

5. витяг корисних фактів,

6. пропозиції правил.

7. Записуємо:

8. summary → mid-term,

9. факти → long-term (appendFact),

10. пропозиції → evolution suggestions.

### 7.2. Видалення шуму

Після успішної дистиляції:

- можна частину короткої памʼяті чистити;

- можна перевести непотрібні `message` у архів.

# 8. Контроль з боку користувача

У UI потрібно дати користувачу можливість:

1. Переглядати памʼять (принаймні long-term):

2. список фактів,

3. розділений по каналах / темах.

4. Видаляти факти:

5. для конфіденційних даних,

6. при помилках.

7. Вимикати зберігання:

8. «Не зберігати DM-переписку з агентом у довгострокову памʼять».

9. Експортувати памʼять:

10. для аудиту / переносу.

# 9. Memory Scopes vs Agent Roles

### Guide Agent (онбординг)

- short-term: поточна сесія онбордингу;

- long-term: факти про те, як виглядає створена команда (не обовʼязково).

### Team Assistant

- short-term: останні діалоги в конкретному каналі;

- mid-term: summaries мітингів / сесій;

- long-term: знання про команду, процеси, словник.

### Meta-Agent

- працює на mid-/long-term даних:

- аналізує їх,

- пропонує зміни в правилах,

- оновлює памʼять.

# 10. Псевдокод реалізації Adapter'а

    export class PgAgentMemoryAdapter implements AgentMemoryAdapter {
      async loadShortTerm(ctx: AgentContext): Promise<AgentMessage[]> {
        const rows = await db.agent_memory_events.findMany({
          where: {
            agent_id: ctx.agent.id,
            team_id: ctx.teamId,
            channel_id: ctx.channelId ?? null,
            scope: "short_term",
            kind: "message",
          },
          orderBy: { created_at: "desc" },
          limit: 50,
        });

        return rows.reverse().map(rowToMessage);
      }

      async loadLongTerm(ctx: AgentContext): Promise<string[]> {
        const rows = await db.agent_memory_events.findMany({
          where: {
            agent_id: ctx.agent.id,
            team_id: ctx.teamId,
            scope: "long_term",
            kind: "fact",
          },
          orderBy: { created_at: "desc" },
          limit: 100,
        });

        return rows.map(r => r.body_text);
      }

      async saveTurn(ctx: AgentContext, turn: AgentMessage): Promise<void> {
        await db.agent_memory_events.insert({
          agent_id: ctx.agent.id,
          team_id: ctx.teamId,
          channel_id: ctx.channelId ?? null,
          scope: "short_term",
          kind: "message",
          body_json: turn,
        });
      }

      async appendFact(ctx: AgentContext, fact: string): Promise<void> {
        await db.agent_memory_events.insert({
          agent_id: ctx.agent.id,
          team_id: ctx.teamId,
          scope: "long_term",
          kind: "fact",
          body_text: fact,
        });

        // TODO: обчислити embedding та зберегти у agent_memory_facts_vector
      }
    }

# 11. RAG Implementation

## 11.1. Embedding Generation

    import { OpenAIEmbeddings } from "langchain/embeddings/openai";

    const embeddings = new OpenAIEmbeddings({
      openAIApiKey: process.env.OPENAI_API_KEY,
    });

    export async function generateEmbedding(text: string): Promise<number[]> {
      const result = await embeddings.embedQuery(text);
      return result;
    }

## 11.2. Vector Search

    export async function searchRelevantFacts(
      query: string,
      agentId: string,
      teamId: string,
      topK: number = 5
    ): Promise<Array<{ text: string; score: number }>> {
      // Генеруємо embedding для запиту
      const queryEmbedding = await generateEmbedding(query);

      // Шукаємо найближчі вектори (cosine similarity)
      const results = await db.$queryRaw`
        SELECT
          fact_text,
          1 - (embedding <=> ${queryEmbedding}::vector) as similarity
        FROM agent_memory_facts_vector
        WHERE agent_id = ${agentId}
          AND team_id = ${teamId}
        ORDER BY similarity DESC
        LIMIT ${topK}
      `;

      return results.map(r => ({
        text: r.fact_text,
        score: r.similarity,
      }));
    }

## 11.3. Integration with runAgentTurn

    export async function runAgentTurn(ctx: AgentContext): Promise<AgentTurnResult> {
      // 1. Завантажуємо short-term пам'ять
      const shortTerm = await ctx.memory.loadShortTerm(ctx);

      // 2. RAG: шукаємо релевантні факти
      const relevantFacts = await searchRelevantFacts(
        ctx.input,
        ctx.agent.id,
        ctx.teamId,
        5
      );

      // 3. Завантажуємо long-term (статичні факти)
      const longTerm = await ctx.memory.loadLongTerm(ctx);

      // 4. Об'єднуємо RAG-результати з long-term
      const allFacts = [
        ...relevantFacts.map(f => f.text),
        ...longTerm,
      ];

      // 5. Готуємо повідомлення для LLM
      const messages = buildLLMMessages(ctx, shortTerm, allFacts);

      // ... решта логіки
    }

# 12. Distillation Job Implementation

    export async function runDistillationJob(
      agentId: string,
      teamId: string,
      period: { from: Date; to: Date }
    ): Promise<void> {
      // 1. Збираємо всі short-term повідомлення за період
      const messages = await db.agent_memory_events.findMany({
        where: {
          agent_id: agentId,
          team_id: teamId,
          scope: "short_term",
          kind: "message",
          created_at: {
            gte: period.from,
            lte: period.to,
          },
        },
        orderBy: { created_at: "asc" },
      });

      // 2. Формуємо контекст для Meta-Agent
      const conversationLog = messages.map(m => ({
        role: m.body_json.role,
        content: m.body_json.content,
        timestamp: m.created_at,
      }));

      // 3. Викликаємо Meta-Agent для аналізу
      const metaAgent = await getMetaAgent(agentId);
      const analysis = await metaAgent.analyze(conversationLog);

      // 4. Зберігаємо результати
      if (analysis.summary) {
        await db.agent_memory_events.create({
          data: {
            agent_id: agentId,
            team_id: teamId,
            scope: "mid_term",
            kind: "summary",
            body_text: analysis.summary,
          },
        });
      }

      if (analysis.facts && analysis.facts.length > 0) {
        for (const fact of analysis.facts) {
          await db.agent_memory_events.create({
            data: {
              agent_id: agentId,
              team_id: teamId,
              scope: "long_term",
              kind: "fact",
              body_text: fact,
            },
          });

          // Генеруємо embedding та зберігаємо
          const embedding = await generateEmbedding(fact);
          await db.agent_memory_facts_vector.create({
            data: {
              agent_id: agentId,
              team_id: teamId,
              fact_text: fact,
              embedding: embedding,
            },
          });
        }
      }

      // 5. Очищаємо оброблені short-term повідомлення
      await db.agent_memory_events.deleteMany({
        where: {
          agent_id: agentId,
          team_id: teamId,
          scope: "short_term",
          kind: "message",
          created_at: {
            gte: period.from,
            lte: period.to,
          },
        },
      });
    }

# 13. Database Schema

## 13.1. agent_memory_events

    CREATE TABLE agent_memory_events (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      agent_id UUID NOT NULL REFERENCES agents(id),
      team_id UUID NOT NULL REFERENCES teams(id),
      channel_id UUID REFERENCES channels(id),
      user_id UUID REFERENCES users(id),
      scope TEXT NOT NULL CHECK (scope IN ('short_term', 'mid_term', 'long_term')),
      kind TEXT NOT NULL CHECK (kind IN ('message', 'fact', 'summary', 'note')),
      body_text TEXT,
      body_json JSONB,
      created_at TIMESTAMP NOT NULL DEFAULT NOW(),

      INDEX idx_agent_team_scope (agent_id, team_id, scope),
      INDEX idx_agent_channel (agent_id, channel_id),
      INDEX idx_created_at (created_at)
    );

## 13.2. agent_memory_facts_vector

    CREATE EXTENSION IF NOT EXISTS vector;

    CREATE TABLE agent_memory_facts_vector (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      agent_id UUID NOT NULL REFERENCES agents(id),
      team_id UUID NOT NULL REFERENCES teams(id),
      fact_text TEXT NOT NULL,
      embedding vector(1536), -- OpenAI ada-002 embedding size
      metadata JSONB,
      created_at TIMESTAMP NOT NULL DEFAULT NOW(),

      INDEX idx_agent_team (agent_id, team_id),
      INDEX idx_embedding USING ivfflat (embedding vector_cosine_ops)
    );

# 14. API Endpoints

## 14.1. GET /agents/{id}/memory

    export async function getAgentMemory(req: Request, res: Response) {
      const { agentId } = req.params;
      const { scope, limit = 50 } = req.query;

      const memory = await db.agent_memory_events.findMany({
        where: {
          agent_id: agentId,
          scope: scope as string,
        },
        orderBy: { created_at: "desc" },
        take: parseInt(limit as string),
      });

      res.json({
        scope,
        items: memory,
      });
    }

## 14.2. DELETE /agents/{id}/memory/{memoryId}

    export async function deleteMemoryItem(req: Request, res: Response) {
      const { agentId, memoryId } = req.params;

      await db.agent_memory_events.delete({
        where: {
          id: memoryId,
          agent_id: agentId,
        },
      });

      res.json({ success: true });
    }

## 14.3. POST /agents/{id}/memory/distill

    export async function triggerDistillation(req: Request, res: Response) {
      const { agentId } = req.params;
      const { days = 7 } = req.body;

      const from = new Date();
      from.setDate(from.getDate() - days);

      await runDistillationJob(agentId, req.teamId, {
        from,
        to: new Date(),
      });

      res.json({ success: true });
    }

# 15. Інтеграція з еволюційним агентом (09)

Еволюційний агент:

- читає `agent_memory_events` (особливо з негативним фідбеком),

- агрегує логи,

- робить distillation,

- створює пропозиції покращень.

Memory System → джерело для:

- аналізу діалогів,

- виявлення патернів,

- побудови Train-to-Earn.

# 16. Тестування

## 16.1. Unit Tests

    describe("PgAgentMemoryAdapter", () => {
      it("should load short-term memory", async () => {
        const adapter = new PgAgentMemoryAdapter();
        const ctx = createMockContext();

        const messages = await adapter.loadShortTerm(ctx);
        expect(messages).toBeInstanceOf(Array);
        expect(messages.length).toBeLessThanOrEqual(50);
      });

      it("should save turn to memory", async () => {
        const adapter = new PgAgentMemoryAdapter();
        const ctx = createMockContext();
        const turn: AgentMessage = {
          role: "user",
          content: "Test message",
        };

        await adapter.saveTurn(ctx, turn);

        const messages = await adapter.loadShortTerm(ctx);
        expect(messages).toContainEqual(
          expect.objectContaining({ content: "Test message" })
        );
      });
    });

# 17. Завдання для Cursor

Приклад промта:

    You are a senior backend engineer.

    Implement the Agent Memory System for MicroDAO using:

    - 13_agent_memory_system.md
    - 12_agent_runtime_core.md
    - 11_llm_integration.md
    - 09_evolutionary_agent.md
    - 05_coding_standards.md

    Tasks:

    1) Create tables: agent_memory_events, agent_memory_facts_vector.

    2) Implement PgAgentMemoryAdapter with short-term + long-term.

    3) Wire PgAgentMemoryAdapter into Agent Runtime Core.

    4) Add a simple RAG retrieval step using facts.

    5) Expose a debug endpoint to inspect agent memory (GET /agents/{id}/memory).

    Output:

    - list of modified files
    - diff
    - summary

# 18. Результат

Після впровадження цієї системи:

- агенти MicroDAO мають справжню багаторівневу памʼять;

- можна керувати тим, що саме вони памʼятають;

- можна будувати RAG, еволюційний аналіз, Train-to-Earn;

- перехід до DAGI стає природним — через спільну колективну памʼять агентів.

Готово.
Це повна специфікація системи пам'яті агентів, готова до використання в Cursor.