Strona główna / Artykuły / Wskazówki praktyczne: Wszystko, czego potrzebujesz do projektowania pamięci agenturalnej

Wskazówki praktyczne: Wszystko, czego potrzebujesz do projektowania pamięci agenturalnej

Praktyczne wskazówki: Wszystko, czego potrzebujesz do projektowania pamięci agentowej – umowy, sprawdzenia oraz gotowe miejsca na kod dla zespołów wdrażających ten wzorzec.

4588 słów

Poniższe notatki przedstawiają praktyczną ścieżkę postępowania w ramach projektowania pamięci agenturalnej „All You Want For Agentic Memory Design”. Nacisk kładziony jest na umowy, sprawdzania oraz miejsca zastępcze dla kodu, a nie na motywacyjne aspekty. Podczas przechodzenia przez etap przeglądu najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny haseł oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

Spis treści

Etap spisu treści funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę przywracania do normalnego stanu. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później w celu udoskonalenia. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wkładki nawiasowe ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji działania po przerwach.

Agent ciągle zapominał

Agencki system najlepiej funkcjonuje, gdy traktowany jest jako mierzalna powierzchnia. Zanim rozszerzysz zakres, zapisz jeden udany przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wtórne struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwę w kontynuacji pracy po zakłóceniach.

Dlaczego okna kontekstowe zawodzą (i badanie, które to udowodniło)

Etap „Dlaczego okna kontekstowe zawodzą” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres badania. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadki częściowego ukończenia bez żadnych informacji. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wложone struktury ukrywają informacje o tym, który węzeł zapisał dane do którego pola, co utrudnia kontynuację pracy po przerwach. Etap „Dlaczego okna kontekstowe zawodzą” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres badania. Utrzymuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całego grafu.

Pamięć krótkoterminowa vs pamięć długoterminowa: Podstawowe rozróżnienie

W etapie pamięci krótkoterminowej i długoterminowej należy zdefiniować dane wejściowe, osobę odpowiedzialną za daną czynność oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić tę czynność na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżki naprawcze. Próby ponownych działań, kontrola przez ludzi oraz obsługa błędów stanowią część produktu, a nie elementy dodawane później. Konieczna jest ludzka akceptacja w przypadku operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się kompletności rozwiązania biznesowego.

Jak branża doszła do konsensusu co do czterech typów pamięci

W etapie „Jak branża się integrowała” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Ludzka akceptacja powinna być wymagana w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się kompletności rozwiązania biznesowego.

Decyzje dotyczące bazy danych produkcyjnych: SQL, Vector czy Graph

W etapie SQL decyzji dotyczących bazy danych produkcyjnej należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij pliki artefaktów, zdefiniuj sprawdzenia sukcesu i odrzuć ciche, częściowe ukończenie zadań. Wprowadź ludzką aprobatę w przypadkach, gdy dochodzi do wydawania pieniędzy lub zmiany danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie równają się pełności biznesowej. W etapie SQL decyzji dotyczących bazy danych produkcyjnej należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez uprawnień do odczytu.

całego grafu.

Szczegół 1: Pamięć buforowa z podsumowaniem

Gdy przechodzisz przez etap pamięci buforowej Szczegółu 1, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj razem ścieżkę prawidłowego działania oraz ścieżkę przywracania. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dopiero późniejszej optymalizacji. Utwórz punkt kontrolny po kosztownych krokach. Narzędzie do kontynuacji nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

from collections import deque
from typing import List, Dict

class BufferMemory:
    def __init__(self, max_turns: int = 10):
        # Each turn = one user message + one assistant message
        self.history: deque = deque(maxlen=max_turns * 2)

    def add_message(self, role: str, content: str):
        self.history.append({"role": role, "content": content})

    def get_context(self) -> List[Dict]:
        return list(self.history)

    def token_estimate(self) -> int:
        total_chars = sum(len(m["content"]) for m in self.history)
        return total_chars // 4  # rough approximation

    def clear(self):
        self.history.clear()
import os
from collections import deque
from typing import List, Dict

from openai import OpenAI


class SummarizingBufferMemory:

    def __init__(self, max_turns: int = 8, summary_batch: int = 4):
        self.client = OpenAI(
            base_url="https://openrouter.ai/api/v1",
            api_key=os.environ["OPENROUTER_API_KEY"],
        )
        self.model = "deepseek/deepseek-v4-flash-0731"
        self.recent: deque = deque(maxlen=max_turns * 2)
        self.rolling_summary: str = ""
        self.summary_batch = summary_batch

    def add_message(self, role: str, content: str):
        if len(self.recent) == self.recent.maxlen:
            self._compress_oldest()
        self.recent.append({"role": role, "content": content})

    def _compress_oldest(self):
        batch = [self.recent.popleft() for _ in range(min(self.summary_batch * 2, len(self.recent)))]
        text = "\n".join(f"{m['role']}: {m['content']}" for m in batch)
        response = self.client.chat.completions.create(
            model=self.model,
            max_tokens=300,
            messages=[
                {
                    "role": "system",
                    "content": "You compress conversation history. Reply with the summary only.",
                },
                {
                    "role": "user",
                    "content": (
                        "Summarize this conversation segment in 2-4 sentences. "
                        "Preserve any decisions made, constraints stated, and conclusions reached.\n\n"
                        f"{text}"
                    ),
                },
            ],
        )
        new_summary = response.choices[0].message.content.strip()
        if self.rolling_summary:
            self.rolling_summary = f"{self.rolling_summary} | {new_summary}"
        else:
            self.rolling_summary = new_summary

    def get_context(self) -> List[Dict]:
        context = []
        if self.rolling_summary:
            context.append({
                "role": "system",
                "content": f"[Prior conversation summary: {self.rolling_summary}]",
            })
        context.extend(list(self.recent))
        return context

Szczegół 2: Pamięć epizodyczna z weryfikacją cytatów

Gdy przechodzisz przez etap pamięci epizodycznej warstwy 2, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

import json
import sqlite3
from datetime import datetime, timedelta
from dataclasses import dataclass, field

@dataclass
class Episode:
    task_type: str
    user_query: str
    outcome: str
    tools_used: list
    citations: list  # source references for validation
    duration_seconds: float
    success: bool
    session_id: str = ""
    created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())

class EpisodicMemory:
    EXPIRY_DAYS = 28  # GitHub Copilot's production default

    def __init__(self, db_path: str = "episodes.db"):
        self.conn = sqlite3.connect(db_path, check_same_thread=False)
        self._init_schema()

    def _init_schema(self):
        self.conn.executescript("""
            CREATE TABLE IF NOT EXISTS episodes (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                task_type   TEXT NOT NULL,
                user_query  TEXT,
                outcome     TEXT,
                tools_used  TEXT,
                citations   TEXT,
                duration_s  REAL,
                success     INTEGER,
                session_id  TEXT,
                created_at  TEXT
            );
            CREATE INDEX IF NOT EXISTS idx_task_type ON episodes (task_type, success, created_at);
        """)
        self.conn.commit()

    def record(self, ep: Episode):
        self.conn.execute(
            """
            INSERT INTO episodes
                (task_type, user_query, outcome, tools_used, citations,
                 duration_s, success, session_id, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                ep.task_type, ep.user_query, ep.outcome,
                json.dumps(ep.tools_used), json.dumps(ep.citations),
                ep.duration_seconds, int(ep.success),
                ep.session_id, ep.created_at,
            ),
        )
        self.conn.commit()

    def recall_similar(self, task_type: str, limit: int = 3) -> list[Episode]:
        cutoff = (datetime.utcnow() - timedelta(days=self.EXPIRY_DAYS)).isoformat()
        cursor = self.conn.execute(
            """
            SELECT task_type, user_query, outcome, tools_used, citations,
                   duration_s, success, session_id, created_at
            FROM   episodes
            WHERE  task_type = ? AND success = 1 AND created_at > ?
            ORDER  BY created_at DESC
            LIMIT  ?
            """,
            (task_type, cutoff, limit),
        )
        return [
            Episode(
                task_type=r[0], user_query=r[1], outcome=r[2],
                tools_used=json.loads(r[3]), citations=json.loads(r[4]),
                duration_seconds=r[5], success=bool(r[6]),
                session_id=r[7], created_at=r[8],
            )
            for r in cursor.fetchall()
        ]

    def validate_citations(self, episode: Episode, validator_fn) -> bool:
        """
        validator_fn(citation: str) -> bool
        Check if cited sources are still valid (file exists, URL responds, etc.)
        Return False if any citation fails; the episode should be discarded.
        """
        return all(validator_fn(c) for c in episode.citations)

Warstwa 3: Pamięć semantyczna z hybrydowym wyszukiwaniem Weaviate

Gdy pracujesz nad etapem Pamięci Semantycznej warstwy 3, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Ustaw punkty kontrolne po kosztownych krokach. Program powinien unikać ponownego pobierania opłat za tę samą funkcję LLM, gdy operator próbuje ponownie wykonać późniejszy element. Gdy pracujesz nad etapem Pamięci Semantycznej warstwy 3, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

import uuid as uuid_lib
from datetime import datetime
import weaviate
from weaviate.classes.config import Configure, Property, DataType, VectorDistances
from weaviate.classes.query import MetadataQuery, HybridFusion, Filter

def embed(text: str) -> list[float]:
    """
    Swap in any embedding source: OpenAI, Cohere, sentence-transformers, etc.
    Returns a normalized float vector.
    """
    from sentence_transformers import SentenceTransformer
    _model = SentenceTransformer("all-MiniLM-L6-v2")
    return _model.encode(text, normalize_embeddings=True).tolist()

class WeaviateSemanticMemory:
    COLLECTION = "AgentMemory"

    def __init__(self, host: str = "localhost", port: int = 8080):
        self.client = weaviate.connect_to_local(host=host, port=port)
        self._ensure_collection()

    def _ensure_collection(self):
        if self.client.collections.exists(self.COLLECTION):
            return
        self.client.collections.create(
            name=self.COLLECTION,
            # We provide our own vectors; no built-in vectorizer needed.
            # Swap to Configure.Vectorizer.text2vec_openai() if you prefer managed embedding.
            vectorizer_config=Configure.Vectorizer.none(),
            vector_index_config=Configure.VectorIndex.hnsw(
                distance_metric=VectorDistances.COSINE
            ),
            properties=[
                Property(name="content",     data_type=DataType.TEXT),
                Property(name="task_type",   data_type=DataType.TEXT),
                Property(name="source",      data_type=DataType.TEXT),
                Property(name="citations",   data_type=DataType.TEXT),
                Property(name="session_id",  data_type=DataType.TEXT),
                Property(name="confidence",  data_type=DataType.NUMBER),
                Property(name="created_at",  data_type=DataType.TEXT),
            ],
        )

    def store(
        self,
        content: str,
        task_type: str = "",
        source: str = "agent",
        citations: str = "",
        session_id: str = "",
        confidence: float = 1.0,
    ) -> str:
        collection = self.client.collections.get(self.COLLECTION)
        doc_id = str(uuid_lib.uuid4())
        collection.data.insert(
            properties={
                "content":    content,
                "task_type":  task_type,
                "source":     source,
                "citations":  citations,
                "session_id": session_id,
                "confidence": confidence,
                "created_at": datetime.utcnow().isoformat(),
            },
            vector=embed(content),
            uuid=doc_id,
        )
        return doc_id

    def retrieve_hybrid(
        self,
        query: str,
        n_results: int = 5,
        min_confidence: float = 0.6,
        task_type: str = None,
    ) -> list[dict]:
        collection = self.client.collections.get(self.COLLECTION)
        # Filter by confidence floor and optionally by task type
        confidence_filter = Filter.by_property("confidence").greater_or_equal(min_confidence)
        if task_type:
            active_filter = (
                Filter.by_property("task_type").equal(task_type) & confidence_filter
            )
        else:
            active_filter = confidence_filter
        results = collection.query.hybrid(
            query=query,
            vector=embed(query),
            limit=n_results,
            fusion_type=HybridFusion.RELATIVE_SCORE,
            filters=active_filter,
            return_metadata=MetadataQuery(score=True),
        )
        return [
            {
                "content":    obj.properties["content"],
                "score":      obj.metadata.score,
                "confidence": obj.properties.get("confidence", 1.0),
                "source":     obj.properties.get("source", ""),
                "citations":  obj.properties.get("citations", ""),
                "uuid":       str(obj.uuid),
            }
            for obj in results.objects
        ]

    def update_confidence(self, doc_id: str, new_confidence: float):
        collection = self.client.collections.get(self.COLLECTION)
        collection.data.update(
            uuid=doc_id,
            properties={"confidence": new_confidence},
        )

    def close(self):
        self.client.close()

Szczegół 4: Pamięć proceduralna i wzorzec ewolucji promptów

Etap pamięci proceduralnej w Szczególe 4 działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przypadek, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia działań przed rozszerzaniem zakresu. Zdokumentuj zarówno pomyślny, jak i naprawczy ścieżkę działania. Próby ponownych działań, kontrola przez ludzi oraz obsługa nieudanych wiadomości stanowią część produktu, a nie elementy dodawane później. Określ budżet tokenów na jeden ruch i na jedną sesję. Narzędzia typu agentic intensywnie rozszerzają kontekst; sztywne limity zapobiegają temu, by demonstracje przerodziły się w niespodziewane rachunki.

import json
import os
import re
from datetime import datetime, timedelta, timezone

from openai import OpenAI

MODEL = "deepseek/deepseek-v4-flash-0731"

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.environ["OPENROUTER_API_KEY"],
)


class ProceduralMemory:
    def __init__(self, rules_path: str = "procedural_rules.json"):
        self.rules_path = rules_path
        self.rules: list[dict] = self._load()

    def _load(self) -> list[dict]:
        try:
            with open(self.rules_path) as f:
                return json.load(f)
        except FileNotFoundError:
            return []

    def add_rule(self, situation: str, action: str, reason: str, confidence: float = 1.0):
        self.rules.append({
            "situation":     situation,
            "action":        action,
            "reason":        reason,
            "confidence":    float(confidence),
            "added_at":      datetime.now(timezone.utc).isoformat(),
            "trigger_count": 0,
        })
        self._save()

    def get_applicable_rules(self, context: str, min_confidence: float = 0.7) -> list[dict]:
        relevant = []
        ctx_lower = context.lower()
        dirty = False
        for rule in self.rules:
            if rule.get("confidence", 1.0) < min_confidence:
                continue
            keywords = rule["situation"].lower().split()
            if not keywords:
                continue
            hits = sum(1 for kw in keywords if kw in ctx_lower)
            if hits >= max(1, len(keywords) // 3):
                rule["trigger_count"] = rule.get("trigger_count", 0) + 1
                relevant.append(rule)
                dirty = True
        if dirty:
            self._save()
        relevant.sort(key=lambda r: r.get("confidence", 1.0), reverse=True)
        return relevant[:5]

    def prune_stale(self, max_age_days: int = 60, min_triggers: int = 2):
        cutoff = (datetime.now(timezone.utc) - timedelta(days=max_age_days)).isoformat()
        self.rules = [
            r for r in self.rules
            if r.get("added_at", "") > cutoff or r.get("trigger_count", 0) >= min_triggers
        ]
        self._save()

    def _save(self):
        tmp = f"{self.rules_path}.tmp"
        with open(tmp, "w") as f:
            json.dump(self.rules, f, indent=2)
        os.replace(tmp, self.rules_path)


def _parse_json(text: str) -> dict:
    text = text.strip()
    fenced = re.search(r"```(?:json)?\s*(.*?)```", text, re.S)
    if fenced:
        text = fenced.group(1).strip()
    return json.loads(text)


def extract_rule_from_failure(failure_trace: str, memory: ProceduralMemory):
    response = client.chat.completions.create(
        model=MODEL,
        max_tokens=250,
        response_format={"type": "json_object"},
        messages=[
            {
                "role": "system",
                "content": "You return only a JSON object. No prose, no code fences.",
            },
            {
                "role": "user",
                "content": (
                    "A task failed. Extract one behavioral rule to prevent this failure.\n"
                    "Return ONLY valid JSON with keys: situation, action, reason, "
                    "confidence (0.0-1.0)\n"
                    f"Failure trace:\n{failure_trace}"
                ),
            },
        ],
    )

    try:
        rule = _parse_json(response.choices[0].message.content)
    except (json.JSONDecodeError, AttributeError, TypeError):
        return None

    if not all(k in rule for k in ("situation", "action", "reason")):
        return None

    memory.add_rule(
        situation=str(rule["situation"]),
        action=str(rule["action"]),
        reason=str(rule["reason"]),
        confidence=float(rule.get("confidence", 1.0)),
    )
    return rule

Łączenie wszystkiego: Asynchroniczny menedżer pamięci i projekt systemu

Faza asynchroniczna łączenia elementów działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres projektu. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wtórne struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co utrudnia kontynuację pracy po przerwach.

import json
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field

@dataclass
class TaskResult:
    task_type: str
    query: str
    outcome: str
    tools_used: list
    citations: list
    duration_seconds: float
    success: bool
    new_facts: list[dict] = field(default_factory=list)
    session_id: str = ""

class MemoryManager:
    def __init__(
        self,
        weaviate_host: str = "localhost",
        episodic_db: str = "episodes.db",
        rules_path: str = "procedural_rules.json",
    ):
        self.buffer    = SummarizingBufferMemory(max_turns=8)
        self.episodic  = EpisodicMemory(db_path=episodic_db)
        self.semantic  = WeaviateSemanticMemory(host=weaviate_host)
        self.procedural = ProceduralMemory(rules_path=rules_path)
        self._pool = ThreadPoolExecutor(max_workers=2, thread_name_prefix="memory_write")

    def build_context(self, query: str, task_type: str) -> list[dict]:
        context: list[dict] = []
        # Procedural rules first: they constrain behavior throughout the task
        rules = self.procedural.get_applicable_rules(query)
        if rules:
            rules_text = "\n".join(
                f"- When '{r['situation']}': {r['action']} (reason: {r['reason']})"
                for r in rules
            )
            context.append({"role": "user", "content": f"[Behavioral rules:\n{rules_text}]"})
        # Semantic facts: domain knowledge and past discoveries
        facts = self.semantic.retrieve_hybrid(query, n_results=5, min_confidence=0.6)
        if facts:
            facts_text = "\n".join(f"- {f['content']}" for f in facts)
            context.append({"role": "user", "content": f"[Relevant knowledge:\n{facts_text}]"})
        # Past episodes: outcome templates for similar tasks
        episodes = self.episodic.recall_similar(task_type, limit=3)
        if episodes:
            ep_text = "\n".join(
                f"- Outcome: {e.outcome} (tools: {', '.join(e.tools_used)})"
                for e in episodes
            )
            context.append({"role": "user", "content": f"[Past similar tasks:\n{ep_text}]"})
        # Current conversation last: the model reads this most carefully
        context.extend(self.buffer.get_context())
        return context

    def record_turn(self, role: str, content: str):
        self.buffer.add_message(role, content)

    def persist(self, result: TaskResult):
        # Submit to thread pool and return immediately; never block the caller
        self._pool.submit(self._persist_worker, result)

    def _persist_worker(self, result: TaskResult):
        ep = Episode(
            task_type=result.task_type,
            user_query=result.query,
            outcome=result.outcome,
            tools_used=result.tools_used,
            citations=result.citations,
            duration_seconds=result.duration_seconds,
            success=result.success,
            session_id=result.session_id,
        )
        self.episodic.record(ep)
        for fact in result.new_facts:
            self.semantic.store(**fact)
        if not result.success:
            extract_rule_from_failure(result.outcome, self.procedural)

  def shutdown(self):
        self._pool.shutdown(wait=True)
        self.semantic.close()

Trzy decyzje, które definiują twoją architekturę

Trzy decyzje definiujące ten etap działają najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć milczące, częściowe ukończenie zadań. Utrzymuj stan grafu w prostej formie i określonej strukturze typów. Wkładki nawiasowe ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji pracy po przerwach. Trzy decyzje definiujące ten etap działają najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Utrzymuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całego grafu.

Zatrucie pamięci: jak wyglądają ataki i jak się przed nimi bronić

Aby zapobiec zatruwaniu pamięci, przed modyfikacją kodu należy określić wejścia, osobę odpowiedzialną za dany krok oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa nieudanych transakcji stanowią część produktu, a nie elementy dodawane później. Konieczna jest ludzka akceptacja w przypadku operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Podłączenia realizowane w czasie kompilacji nie równają się kompletności rozwiązania biznesowego.

Rzeczywista rekomendacja

W fazie faktycznej rekomendacji należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Lepiej używać małych, testowalnych jednostek niż rozbudowanych skryptów. Gdy jakiś krok zawiedzie, powinno to wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Konieczna jest ludzka akceptacja w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się kompletności procesu biznesowego.

Ciągnijmy naukę razem

W fazie „Let’s keep learning” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadań. Zapewnij ludzką aprobatę w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Podłączenia realizowane w czasie kompilacji nie równają się pełnej kompletności procesu biznesowego. W fazie „Let’s keep learning” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny haseł oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

Inne przydatne artykuły

Podczas przechodzenia przez etap Innych przydatnych artykułów, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Ustal punkty kontrolne po kosztownych krokach. System powinien unikać ponownego naliczania opłat za tę samą operację LLM, gdy operator próbuje ponownie uruchomić późniejszy element.

W etapie Listy kontrolnej operacyjnej zdefiniuj dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed wprowadzaniem zmian w kodzie. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanej punktu kontrolnego, bez konieczności zgadywania ukrytego stanu systemu.

Zapisuj czas wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.

Zapewnij ludzką aprobatę dla elementów, które generują wydatki lub zmieniają dane produkcyjne. Połączenia ustalane w czasie kompilacji nie gwarantują kompletności rozwiązania biznesowego.

Napisz krótki przewodnik: jak rotować klucze, jak opróżniać kolejki z zadań, jak cofnąć ostatni proces importu.

Zachowaj konfigurację poza kodem aplikacji. Pliki środowiskowe, skrytki z danymi poufnymi oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

Zapewnij ludzką aprobatę dla elementów, które generują wydatki lub zmieniają dane produkcyjne. Połączenia ustalane w czasie kompilacji nie gwarantują kompletności rozwiązania biznesowego.

Zanim wdrożysz cały zestaw narzędzi, zamroź wersje, utwórz dokładny zapis dla kluczowych etapów realizacji oraz potwierdź kroki odwracania zmian. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji przynależności użytkowników oraz wyraźnego właściciela odpowiedzialnego za rotację haseł. Wolimy nudną niezawodność od pomysłowych, jednorazowych demonstracji.

Uwaga dotycząca procesu b038012e06fc: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj zapisy obok plików testowych, aby późniejsze zmiany modeli pozostawały porównywalne.

Dla etapu 0 związkiego z wzmocnieniem bezpieczeństwa określ wcześniej dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy plikom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań.

Szczegół wzmocnienia 0/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.

Podczas przechodzenia przez pierwszy etap notatki dotyczącej wzmocnienia, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

Szczegół wzmocnienia 1/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.

Druga faza notatki dotyczącej wzmocnienia bezpieczeństwa działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmiany, zanim rozszerzysz zakres prac. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.

Szczegół wzmocnienia bezpieczeństwa 2/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę, opierając się na ustalonej serii pytań, a nie na anegdotach.

Dla trzeciego etapu ulepszeń związanych z wzmocnieniem bezpieczeństwa należy określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie wykonać ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu systemu. Należy rejestrować czas wykonywania operacji oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.

Szczegóły ulepszenia nr 3/804: należy zmierzyć czas wykonywania operacji, klasę błędów oraz zużycie tokenów dla tego etapu, a następnie zdecydować o utrzymaniu zmiany na podstawie ustalonego zestawu kryteriów, a nie jedynie informacji anegdotycznych.

Gdy przechodzisz przez etap nr 4 notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego awarii. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę odzyskiwania. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później.

Szczegół nr 4/804 dotyczący wzmocnienia bezpieczeństwa: zmierz czas wykonywania operacji, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie informacji anegdotycznych.

Etap nr 5 notatki dotyczącej wzmocnienia bezpieczeństwa działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przepływ działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres prac. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadki cichego, częściowego ukończenia zadania.

Szczegół wzmocnienia 5/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.

W etapie 6 notatki dotyczącej wzmocnienia określ dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

Szczegół wzmocnienia 6/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.

Gdy przechodzisz przez etap 7 notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz warunki kontraktu: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego awarii. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.

Detalia wzmocnienia bezpieczeństwa 7/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie osobistych obserwacji.

Etap 8 notatki dotyczącej wzmocnienia bezpieczeństwa działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres prac. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy przechodzi się od środowiska demonstracyjnego do wspólnych środowisk.

Szczegóły wzmocnienia bezpieczeństwa 8/804: zmierz czas wykonywania operacji, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.

W etapie 9 notatki dotyczącej wzmocnienia bezpieczeństwa zdefiniuj dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie wykonać ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Zdokumentuj zarówno prawidłowy przebieg operacji, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie element późniejszej dopracowywania.

Szczegóły wzmocnienia bezpieczeństwa 9/804: zmierz czas wykonywania operacji, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.

Gdy przechodzisz przez etap 10 notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadki częściowego ukończenia bez żadnych informacji.

Szczegół 10/804 dotyczący wzmocnienia bezpieczeństwa: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie informacji anegdotycznych.

Etap 11 notatki dotyczącej wzmocnienia bezpieczeństwa funkcjonuje najlepiej, gdy jest traktowany jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przykład działania, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres prac. Utrzymuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

Szczegół wzmocnienia 11/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie anegdoty.

W etapie 12 notatki dotyczącej wzmocnienia określ dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces.

Szczegół wzmocnienia 12/804: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie anegdoty.

Literatura pokrewna

  • Notatki praktyczne: Agentic AI – jak oszczędzać na tokenach — Szczegółowy przewodnik po Notatkach praktycznych: Agentic AI – jak oszczędzać na tokenach: kontrakty, sprawdzenia oraz gotowe elementy kodu dla zespołów wdrażających ten model.
  • Notatki praktyczne: Anthropic mówi, że Agentic Analytics to nie tylko to — Szczegółowy przewodnik po Notatkach praktycznych: Anthropic mówi, że Agentic Analytics to nie tylko to: kontrakty, sprawdzenia oraz gotowe elementy kodu dla zespołów wdrażających ten model.
  • Praktyczne notatki: Odblokowanie reżimu wideo agencyjnego Gemini: Projekt schematu — Krok po kroku instrukcja dotycząca Praktycznych notatek: Odblokowanie reżimu wideo agencyjnego Gemini: Projekt schematu: kontrakty, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.
  • Praktyczne notatki: Tworzenie platformy agencyjnej przedsiębiorstwa przystosowanej do przyszłości — Krok po kroku instrukcja dotycząca Praktycznych notatek: Tworzenie platformy agencyjnej przedsiębiorstwa przystosowanej do przyszłości: kontrakty, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.