Aufbau eines langfristigen Agentengedächtnisses mit EverOS und Milvus
EverOS ist ein „Markdown-first“-Gedächtnissystem für KI-Agenten. Es extrahiert dauerhafte Erinnerungen aus Unterhaltungen, behält Markdown als „Source of Truth“ bei und erstellt einen durchsuchbaren abgeleiteten Index.
In diesem Tutorial erstellen wir einen Projektassistenten, der sich über verschiedene Konversationen hinweg an Release-Entscheidungen erinnert. Wir fügen Konversationen über den Start von „Project Atlas“ sowie unabhängige Konversationen über andere Projekte hinzu. EverOS nutzt ein LLM, um die Erinnerungen zu extrahieren, während Milvus die für die hybride Suche verwendeten BM25- und Vektorindizes speichert.
Conversations
|
v
EverOS + LLM ------> Markdown memory files
|
| embedding model
v
Milvus ------> BM25 + vector hybrid search
Das LLM und das Embedding-Modell haben unterschiedliche Aufgaben. Das LLM wandelt eine Konversation in strukturierte Erinnerungen um. Das Embedding-Modell wandelt diese Erinnerungen und spätere Suchanfragen in Vektoren um. Die grundlegende hybride Suche in diesem Tutorial erfordert kein Reranking-Modell.
Voraussetzungen
Sie benötigen:
- Python 3.12 oder höher
uv- Einen laufenden Milvus-Server
- Einen OpenAI-API-Schlüssel
Dieses Tutorial stellt eine Verbindung zum Milvus-Server unter http://localhost:19530 her. EverOS unterstützt auch Zilliz Cloud über dieselben URI- und Token-Einstellungen. Das Milvus-Backend erwartet einen Remote-Endpunkt und akzeptiert keinen Milvus-Lite-Dateipfad.
EverOS installieren
Erstellen Sie ein lokales Projekt und installieren Sie EverOS mit den optionalen Milvus-Abhängigkeiten:
mkdir everos-milvus-demo
cd everos-milvus-demo
uv init --bare --python 3.12
uv add "everos[milvus]"
Der Befehl legt bewusst keine bestimmte Version fest, sodass bei einer Neuinstallation die aktuellste kompatible EverOS-Version installiert wird.
Initialisieren Sie einen separaten Speicher-Stammordner für das Tutorial:
export EVEROS_ROOT="$PWD/everos-data"
uv run everos init --root "$EVEROS_ROOT"
EverOS erstellt unter diesem Verzeichnis die Dateien „ everos.toml “ und „ ome.toml “. Außerdem werden die extrahierten Speicherdaten hier abgelegt.
OpenAI und Milvus konfigurieren
Legen Sie den OpenAI-API-Schlüssel fest und konfigurieren Sie EverOS über Umgebungsvariablen:
export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"
export MILVUS_URI="http://localhost:19530"
export EVEROS_INDEX__BACKEND="milvus"
export EVEROS_MILVUS__URI="$MILVUS_URI"
export EVEROS_MILVUS__COLLECTION_PREFIX="everos_bootcamp"
export EVEROS_LLM__MODEL="gpt-5.4-mini"
export EVEROS_LLM__API_KEY="$OPENAI_API_KEY"
export EVEROS_LLM__BASE_URL="https://api.openai.com/v1"
export EVEROS_EMBEDDING__MODEL="text-embedding-3-small"
export EVEROS_EMBEDDING__API_KEY="$OPENAI_API_KEY"
export EVEROS_EMBEDDING__BASE_URL="https://api.openai.com/v1"
export EVEROS_EMBEDDING__DIMENSIONS="1024"
export EVEROS_MEMORIZE__MODE="chat"
EverOS nutzt OpenAI sowohl für die Speicher-Extraktion als auch für Embeddings. „ text-embedding-3-small “ gibt standardmäßig die Dimensionen „ 1536 “ zurück, doch EverOS leitet den konfigurierten Wert „ dimensions “ an OpenAI weiter. Dieses Tutorial fordert die Dimensionen „ 1024 “ an, um sie an die von EverOS verwalteten Milvus-Schemas anzupassen.
Der Speichermodus „ chat “ stellt in diesem Beispiel den Fokus auf Benutzerspeicher. EverOS verwaltet die Milvus-Sammlungen und deren Schemata, sodass Sie diese nicht selbst erstellen müssen.
EverOS starten
Starten Sie den EverOS-HTTP-Server:
uv run everos server start --root "$EVEROS_ROOT"
Lassen Sie dieses Terminal geöffnet. EverOS stellt beim Start eine Verbindung zu Milvus her und erstellt sieben „derived-index“-Sammlungen mit dem konfigurierten Präfix.
Öffnen Sie ein weiteres Terminal im selben Projektverzeichnis und überprüfen Sie den Dienst:
curl http://127.0.0.1:8000/health
Beispielausgabe:
{
"status": "ok",
"version": "1.3.0",
"capabilities": {
"llm": true,
"embed": true,
"rerank": false,
"multimodal_llm": false,
"parser": true
},
"cascade": {
"healthy": true,
"pending": 0
}
}
Die Antwort enthält zusätzliche Statusfelder. Die für dieses Tutorial wichtigen Werte sind „ status: "ok" “, „ llm: true “, „ embed: true “ und „ cascade.healthy: true “.
Projektkonversationen hinzufügen
Das folgende Python-Programm sendet zehn unabhängige Konversationen an EverOS. Atlas verfügt über separate Diskussionen zum Start und zum Rollback. Acht Konversationen über andere Projekte dienen als Ablenkungsmanöver, sodass die spätere Suche die richtigen Projekterinnerungen identifizieren muss.
Speichern Sie den folgenden Code als „ add_memories.py “:
import json
import time
from urllib.request import Request, urlopen
API_URL = "http://127.0.0.1:8000/api/v2/memory"
NOW = int(time.time() * 1000)
conversations = [
(
"atlas-release",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW,
"content": (
"For Project Atlas, we decided to launch with a 10% canary "
"on September 30. Promote to all users only after the checkout "
"error rate stays below 1% for 30 minutes."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 1_000,
"content": (
"Understood. I will remember the Atlas launch date, canary "
"percentage, and promotion gate."
),
},
],
),
(
"atlas-rollback",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 10_000,
"content": (
"The Atlas rollback owner is Priya. Roll back immediately if "
"checkout errors exceed 2% for five minutes, and keep the "
"previous container image available for 24 hours."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 11_000,
"content": (
"Got it. Priya owns rollback, with the 2% five-minute trigger "
"and a 24-hour image retention window."
),
},
],
),
(
"orion-pricing",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 20_000,
"content": (
"Project Orion will test annual billing with the education "
"segment. The pricing review is scheduled for October 12."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 21_000,
"content": (
"I will remember Orion's annual billing experiment and October "
"pricing review."
),
},
],
),
(
"vega-mobile",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 30_000,
"content": (
"For Project Vega, the mobile team chose offline drafts as the "
"next milestone. Elena will review the interaction design on "
"October 18."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 31_000,
"content": (
"Noted. Vega's next milestone is offline drafts, followed by "
"Elena's design review."
),
},
],
),
(
"nova-warehouse",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 40_000,
"content": (
"Project Nova will migrate the analytics warehouse to Iceberg. "
"Marcus owns the checksum rehearsal scheduled for October 22."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 41_000,
"content": (
"I will remember Nova's warehouse migration and Marcus's "
"checksum rehearsal."
),
},
],
),
(
"helios-support",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 50_000,
"content": (
"Project Helios needs weekend support coverage for the APAC "
"region. Imani will publish the rotation schedule on November 1."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 51_000,
"content": (
"Noted. Helios needs APAC weekend coverage, and Imani owns the "
"rotation schedule."
),
},
],
),
(
"luna-onboarding",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 60_000,
"content": (
"Project Luna will replace the onboarding tour with a checklist. "
"The localized copy is due from the content team on October 25."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 61_000,
"content": (
"I will remember Luna's checklist approach and the localization "
"deadline."
),
},
],
),
(
"aurora-observability",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 70_000,
"content": (
"Project Aurora will retain detailed telemetry for 30 days. "
"The operations team should alert after three consecutive "
"heartbeat misses."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 71_000,
"content": (
"Understood. Aurora keeps 30 days of telemetry and alerts after "
"three missed heartbeats."
),
},
],
),
(
"comet-invoices",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 80_000,
"content": (
"Project Comet will add downloadable invoice PDFs for enterprise "
"accounts. Finance will approve the tax-field layout on October 28."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 81_000,
"content": (
"Noted. Comet covers enterprise invoice PDFs and an October tax "
"layout review."
),
},
],
),
(
"solstice-research",
[
{
"sender_id": "maya",
"sender_name": "Maya",
"role": "user",
"timestamp": NOW + 90_000,
"content": (
"Project Solstice is prototyping voice notes for field researchers. "
"The research team will interview 12 participants in November."
),
},
{
"sender_id": "assistant",
"role": "assistant",
"timestamp": NOW + 91_000,
"content": (
"I will remember Solstice's voice-note prototype and the planned "
"participant interviews."
),
},
],
),
]
def post(path, payload):
request = Request(
f"{API_URL}/{path}",
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urlopen(request, timeout=300) as response:
return json.load(response)["data"]
for session_id, messages in conversations:
added = post(
"add",
{
"session_id": session_id,
"app_id": "project-assistant",
"project_id": "launch-planning",
"messages": messages,
"defer_extraction": True,
},
)
flushed = post(
"flush",
{
"session_id": session_id,
"app_id": "project-assistant",
"project_id": "launch-planning",
},
)
print(f"{session_id}: {added['status']} -> {flushed['status']}")
Führen Sie ihn aus dem Projektverzeichnis aus:
uv run python add_memories.py
Beispielausgabe:
atlas-release: accumulated -> extracted
atlas-rollback: accumulated -> extracted
orion-pricing: accumulated -> extracted
vega-mobile: accumulated -> extracted
nova-warehouse: accumulated -> extracted
helios-support: accumulated -> extracted
luna-onboarding: accumulated -> extracted
aurora-observability: accumulated -> extracted
comet-invoices: accumulated -> extracted
solstice-research: accumulated -> extracted
Durch die Einstellung „ defer_extraction “ auf „ true “ wird jede Konversation im dauerhaften Puffer gespeichert, ohne dass das LLM aufgefordert wird, eine Grenze zu erkennen. Der folgende Aufruf von „ /flush “ markiert das Ende dieser Sitzung und löst eine Extraktion aus. EverOS schreibt die extrahierte Episode anschließend in Markdown und bettet sie asynchron in den Milvus-Index ein.
Den Markdown-Speicher überprüfen
Die generierte Episodendatei wird unter den Bereichen „Anwendung“, „Projekt“ und „Benutzer“ gespeichert:
find "$EVEROS_ROOT/project-assistant/launch-planning/users/maya/episodes" \
-type f -name "*.md"
Beispielausgabe (das Datum im Dateinamen gibt den Zeitpunkt der Ausführung des Beispiels an):
everos-data/project-assistant/launch-planning/users/maya/episodes/episode-2026-09-08.md
Öffnen Sie die Datei, um die vom LLM extrahierten Erinnerungen anzuzeigen. Ein gekürzter Auszug sieht wie folgt aus:
## ep_20260908_00000001
**owner_id**: maya
**session_id**: atlas-release
**sender_ids**: [maya, assistant]
### Subject
Maya's Project Atlas Launch Decision: September 30 Canary and Promotion Criteria
### Content
Maya decided that Project Atlas would launch with a 10% canary on September 30.
The promotion to all users would occur only after the checkout error rate remained
below 1% for 30 minutes.
Der genaue Wortlaut, die Bezeichner und die Zeitstempel können variieren, da die Erinnerung vom LLM extrahiert wird. Die ursprünglichen Markdown-Dateien bleiben die verlässliche Quelle der Wahrheit; der Milvus-Index kann anhand dieser Dateien neu erstellt werden.
Durchsuchen Sie die Erinnerungen
Nutzen Sie die hybride Suche, um zu ermitteln, was gespeichert werden soll, bevor Atlas in Betrieb genommen wird. Speichern Sie den folgenden Code unter „ search_memories.py “:
import json
import time
from urllib.request import Request, urlopen
URL = "http://127.0.0.1:8000/api/v2/memory/search"
payload = {
"user_id": "maya",
"app_id": "project-assistant",
"project_id": "launch-planning",
"query": "What should I remember before Atlas goes live?",
"method": "hybrid",
"top_k": 4,
}
def search():
request = Request(
URL,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urlopen(request, timeout=300) as response:
return json.load(response)["data"]["episodes"]
expected_sessions = {"atlas-release", "atlas-rollback"}
for _ in range(30):
episodes = search()
top_results = episodes[:2]
if {episode["session_id"] for episode in top_results} == expected_sessions:
break
time.sleep(2)
else:
raise RuntimeError("The expected Atlas memories were not indexed in time")
for rank, episode in enumerate(top_results, start=1):
print(f"{rank}. {episode['session_id']} | score={episode['score']:.3f}")
print(f" {episode['subject']}")
Führen Sie die Suche durch:
uv run python search_memories.py
Bezugsausgabe (Ergebnisse und Formulierungen können variieren):
1. atlas-release | score=0.492
Project Atlas Launch Plan: 10% Canary Rollout on September 30 with Error Rate Gate
2. atlas-rollback | score=0.400
Atlas Rollback Plan Details: Priya as Owner, 2% Error Trigger, 24-Hour Image Retention
Beide Atlas-Konversationen werden vor den acht nicht verwandten Konversationen zurückgegeben. EverOS sendet die Abfrage an den OpenAI-Embedding-Endpunkt, fragt Milvus nach BM25- und Vektorkandidaten innerhalb des Anwendungs- und Projektumfangs von Maya und führt die beiden Ergebnislisten zusammen.
Überprüfen Sie die Milvus-Sammlungen
EverOS erstellt für jeden unterstützten abgeleiteten Speichertyp eine Sammlung. Verwenden Sie MilvusClient, um deren Zeilenanzahl aufzulisten:
import os
from pymilvus import MilvusClient
prefix = "everos_bootcamp"
client = MilvusClient(uri=os.environ.get("MILVUS_URI", "http://localhost:19530"))
memory_kinds = [
"agent_case",
"agent_skill",
"atomic_fact",
"episode",
"foresight",
"knowledge_topic",
"user_profile",
]
for kind in memory_kinds:
name = f"{prefix}_{kind}"
if client.has_collection(collection_name=name):
result = client.query(
collection_name=name,
filter="",
output_fields=["count(*)"],
)
print(f"{kind}: {result[0]['count(*)']} rows")
client.close()
Beziehen Sie sich auf die Ausgabe des validierten Durchlaufs:
agent_case: 0 rows
agent_skill: 0 rows
atomic_fact: 50 rows
episode: 10 rows
foresight: 0 rows
knowledge_topic: 0 rows
user_profile: 1 rows
Die genaue Anzahl der atomaren Fakten kann je nach LLM-Ausgabe variieren. Die zehn Episodenzeilen entsprechen den zehn gespeicherten Konversationen. Die anderen Sammlungen stehen für EverOS-Speichermodi und -Funktionen zur Verfügung, die in diesem fokussierten Beispiel nicht zum Einsatz kommen.
Verwenden Sie eine andere Milvus-Bereitstellung
Um einen anderen Milvus-Server-Endpunkt oder die Zilliz Cloud zu verwenden, aktualisieren Sie EVEROS_MILVUS__URI. Setzen Sie „ EVEROS_MILVUS__TOKEN “, wenn der Endpunkt eine Authentifizierung erfordert. Der Code für die Dateneingabe und die Suche bleibt unverändert.
Fazit
Durch die Kombination von EverOS mit Milvus können Sie Konversationen in dauerhafte Erinnerungen umwandeln und diese anhand von Schlüsselwörtern und semantischen Signalen abrufen. Sie können dasselbe Muster anpassen, um Assistenten und anderen agentenbasierten Anwendungen ein Langzeitgedächtnis für Ihre eigenen Nutzer, Projekte und Arbeitsabläufe zu verleihen.