Diese Anleitung beschreibt die Nutzung der OpenPaw-Memory-API aus Clients wie Codex, OpenPaw/Paw, Testskripten oder später einem Signal-Bridge-Prozess.
export BASE_URL='<base-url>'
export API_BASE_URL="${BASE_URL}/api"
export OPENPAW_MEMORY_TOKEN='<token>'
Standardheader:
Authorization: Bearer <token>
Content-Type: application/json
Prüft, ob die API erreichbar ist und die Authentifizierung funktioniert.
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/health"
Antwort:
{
"ok": true,
"service": "openpaw-memory",
"version": "0.3.0",
"database": "mariadb"
}
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{
"text": "Frank bevorzugt kleine, wartbare Lösungen.",
"tags": ["frank", "preference", "architecture"],
"metadata": {
"topic": "architecture"
},
"kind": "preference",
"importance": 0.8,
"scope": "personal",
"source": "codex",
"source_ref": null,
"confidence": 1.0,
"visibility": "private",
"observed_at": "2026-06-28T12:00:00Z"
}' \
"${API_BASE_URL}/memories"
Pflichtfeld:
text: nicht leerer Text der Erinnerung.Optionale Felder:
id: eigene ID, sonst erzeugt die API eine ID.tags: Liste kurzer Tags.metadata: JSON-Objekt für strukturierte Zusatzdaten.kind: Art der Erinnerung, z. B. note, preference, fact, task.importance: Zahl von 0.0 bis 1.0.scope: Geltungsbereich, z. B. personal, project, system.source: Quelle, z. B. codex, openpaw, signal, manual.source_ref: optionale Referenz auf Ursprung, z. B. Message-ID oder Dateiname.confidence: Zahl von 0.0 bis 1.0.visibility: Sichtbarkeitswert, standardmäßig private oder internal.observed_at: Zeitpunkt der Beobachtung; Default ist der Erstellzeitpunkt.Standardmäßig erlaubte Werte:
kind: note, preference, fact, task, event, decisionscope: personal, project, system, sessionvisibility: private, internalsource: api, codex, openpaw, paw, signal, manual, smoke-testDie Listen können in private/config.php unter memory.allowed_kind,
memory.allowed_scope, memory.allowed_visibility und memory.allowed_source
angepasst werden.
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/memories?limit=20&offset=0"
Grenzen:
limit: 1 bis 100.offset: 0 bis 100000.curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/memories/<id>"
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/memories/search?q=wartbar%20klein&limit=10"
Die Suche verwendet MariaDB-Fulltext. Wenn Fulltext nicht verfügbar ist, nutzt
die API eine einfache LIKE-Suche als Fallback.
Grenzen:
q: Suchbegriff.limit: 1 bis 50.PATCH akzeptiert Teilupdates. Nicht gesendete Felder bleiben unverändert.
curl -fsS \
-X PATCH \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{
"tags": ["frank", "preference", "architecture", "memory"]
}' \
"${API_BASE_URL}/memories/<id>"
curl -fsS \
-X DELETE \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/memories/<id>"
Antwort:
{
"deleted": true,
"id": "<id>"
}
Chatnachrichten bleiben von Memory-Einträgen getrennt. Ein Chat besteht aus
einem Thread und beliebig vielen Nachrichten. Nachrichten können optional über
memory_id auf kuratierte Memory-Einträge verweisen.
Thread anlegen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"title":"Paw / Frank","channel":"web","owner_context":"openpaw"}' \
"${API_BASE_URL}/chats"
Threads auflisten:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/chats?limit=20&offset=0"
Threads suchen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/chats/search?q=Signal&limit=20"
Die Suche prüft Thread-Titel, Kanal, Kontext und Nachrichteninhalte.
Thread lesen oder aktualisieren:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/chats/<id>"
curl -fsS \
-X PATCH \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"title":"Paw / Frank notes","status":"archived"}' \
"${API_BASE_URL}/chats/<id>"
Thread löschen:
curl -fsS \
-X DELETE \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/chats/<id>"
Die zugehörigen Nachrichten werden durch die Datenbankbeziehung mit gelöscht.
Nachricht speichern:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"role":"frank","text":"Bitte merk dir diesen Verlauf.","source":"web"}' \
"${API_BASE_URL}/chats/<id>/messages"
Nachrichten lesen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/chats/<id>/messages?limit=50&offset=0"
Memory für einen Thread speichern:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"text":"Die wichtigste Erkenntnis dieses Threads."}' \
"${API_BASE_URL}/chats/<id>/memories"
Thread-Memories lesen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/chats/<id>/memories"
Der folgende ältere Endpunkt bleibt zur Kompatibilität verfügbar und ordnet das erzeugte Memory ebenfalls dem Thread zu:
curl -fsS \
-X POST \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
"${API_BASE_URL}/chats/<id>/messages/<message-id>/memory"
Der Kompatibilitätsendpunkt setzt weiterhin chat_messages.memory_id, ordnet
das Memory aber zusätzlich über chat_thread_memories dem Thread zu. Wenn die
Nachricht bereits verknüpft ist, wird das vorhandene Memory zurückgegeben.
Wichtige Felder:
title, channel, owner_context, status, metadata.role, text, source, external_message_id, memory_id,
metadata, observed_at.frank, paw, system, external.open, archived.Die Bridge verwendet dieselbe Bearer-Token-Authentifizierung wie die übrige
API. Es werden keine eingehenden Verbindungen zum lokalen Agenten benötigt.
Nachrichten, die Frank im Web-Chat sendet, werden mit Status pending
gespeichert.
Pending Nachrichten atomar claimen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"limit":10}' \
"${API_BASE_URL}/bridge/messages/claim"
Die Antwort enthält ein kurzlebiges claim_token und die geclaimten
Nachrichten. Ohne Antwort werden Claims standardmäßig nach 900 Sekunden wieder
freigegeben. Der Wert kann über bridge.claim_timeout_seconds konfiguriert
werden.
Antwort zurückschreiben:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"claim_token":"<claim-token>","text":"Antwort von Paw"}' \
"${API_BASE_URL}/bridge/messages/<message-id>/reply"
Die Antwort wird als normale paw-Nachricht im ursprünglichen Thread
gespeichert. Wiederholte Reply-Requests erzeugen keine doppelte Antwort.
Browser-Polling mit Website-Session:
GET /chat/messages?thread=<thread-id>&after=<last-message-id>
Alternativ unterstützt auch
GET /api/chats/<id>/messages?after=<last-message-id> denselben Cursor.
Die erste Bildstufe unterstützt JPEG, PNG und WebP. Uploads sind standardmäßig auf 10 MB, 8192 × 8192 Pixel und 40 Millionen Pixel begrenzt. Der Server ermittelt den MIME-Type aus dem Inhalt, dekodiert das Bild mit GD, kodiert es neu und entfernt dabei eingebettete EXIF-Daten. Identische normalisierte Bilder werden über SHA-256 nur einmal gespeichert.
Bild hochladen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-F "image=@screenshot.png" \
"${API_BASE_URL}/media"
Die Antwort enthält media.id. Danach wird das Bild einem bestehenden Memory
zugeordnet:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{
"media_id":"<media-id>",
"role":"screenshot",
"caption":"Fehler beim Start der Bridge",
"alt_text":"Terminal mit einer Timeout-Fehlermeldung",
"ocr_text":"BridgeError: Agent command timed out",
"source":"openclaw",
"source_ref":"chat:<thread-id>:<message-id>",
"original_filename":"screenshot.png",
"metadata":{"topic":"bridge-client"}
}' \
"${API_BASE_URL}/memories/<memory-id>/attachments"
Attachments auflisten und Bildinhalt abrufen:
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
"${API_BASE_URL}/memories/<memory-id>/attachments"
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-o image.png \
"${API_BASE_URL}/attachments/<attachment-id>/content"
Erlaubte Rollen sind image, screenshot, reference und document.
Standardmäßig sind höchstens zehn Attachments pro Memory erlaubt. Die Grenzen
können unter media in private/config.php angepasst werden.
Das App-Backup enthält die Bilddaten und Attachment-Metadaten. Ein zusätzliches vollständiges MariaDB-Backup bleibt als zweite Sicherungsebene empfehlenswert.
Backups sind standardmäßig deaktiviert. Wenn sie in der Config aktiviert sind, ist ein separater Backup-Token Pflicht. Der Backup-Token darf nicht leer sein und darf nicht dem normalen API-Bearer-Token entsprechen.
export OPENPAW_MEMORY_BACKUP_TOKEN='<backup-token>'
curl -fsS \
-X POST \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H "X-Backup-Token: ${OPENPAW_MEMORY_BACKUP_TOKEN}" \
"${API_BASE_URL}/backups"
Die API schreibt ein ZIP-Archiv in das private Backup-Verzeichnis. Es enthält
manifest.json mit Memories, Medien- und Attachment-Metadaten sowie die
Bildinhalte unter media/. Dafür muss die PHP-Erweiterung ZipArchive
verfügbar sein.
Der Restore-Endpunkt spielt eine vorhandene Backup-Datei aus dem privaten Backup-Verzeichnis zurück. Er benötigt Bearer-Token und Backup-Token.
Trockenlauf:
curl -fsS \
-X POST \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H "X-Backup-Token: ${OPENPAW_MEMORY_BACKUP_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"file":"openpaw-memory-YYYYMMDD-HHMMSS-xxxxxxxx.zip","mode":"upsert","dry_run":true}' \
"${API_BASE_URL}/backups/restore"
Restore ausführen:
curl -fsS \
-X POST \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H "X-Backup-Token: ${OPENPAW_MEMORY_BACKUP_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{"file":"openpaw-memory-YYYYMMDD-HHMMSS-xxxxxxxx.zip","mode":"upsert","dry_run":false}' \
"${API_BASE_URL}/backups/restore"
Parameter:
file: Dateiname aus GET /backups.mode: upsert oder insert_only; Default ist upsert.dry_run: true oder false; Default ist false.upsert aktualisiert vorhandene Erinnerungen anhand der id und fügt fehlende
ein. insert_only fügt nur fehlende Erinnerungen ein und überspringt vorhandene
IDs. id, Inhalte, observed_at, created_at und updated_at werden aus dem
Backup übernommen.
ZIP-Backups verwenden das Format openpaw-memory-backup-v2 und enthalten auch
Bilddaten. Ältere JSON-Dateien im Format openpaw-memory-backup-v1 können
weiterhin wiederhergestellt werden; sie enthalten nur Memories.
Antwort:
{
"restored": true,
"dry_run": false,
"file": "openpaw-memory-YYYYMMDD-HHMMSS-xxxxxxxx.zip",
"mode": "upsert",
"count": 10,
"inserted": 2,
"updated": 8,
"skipped": 0,
"media_count": 3,
"attachment_count": 4,
"media_inserted": 3,
"media_deduplicated": 0,
"attachments_inserted": 4,
"attachments_updated": 0,
"attachments_skipped": 0
}
curl -fsS \
-H "Authorization: Bearer ${OPENPAW_MEMORY_TOKEN}" \
-H "X-Backup-Token: ${OPENPAW_MEMORY_BACKUP_TOKEN}" \
"${API_BASE_URL}/backups"
Fehlerantworten haben dieses Format:
{
"error": "message"
}
Wichtige Statuscodes:
400: ungültige Eingabe.401: fehlender oder falscher Bearer Token.403: fehlender oder falscher Backup-Token.404: Endpunkt, Erinnerung oder Chat-Thread nicht gefunden.409: ID existiert bereits.413: Request Body zu groß.429: Rate Limit überschritten.500: Server- oder Datenbankfehler.source immer setzen, damit später nachvollziehbar bleibt,
ob eine Erinnerung aus Codex, OpenPaw/Paw, Signal oder manueller Pflege kam.frank, preference, project,
server, security.chat_messages; dauerhaft wichtige Erkenntnisse
gehören kuratiert in memories und können per memory_id verlinkt werden.