mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 21:44:38 +00:00
* Refactor core cross-domain flows onto event bus * feat(agent): enhance error handling and message processing - Introduced a new constant for maximum error message length in the Agent. - Added methods for comparing conversation messages and determining new entries for turns. - Implemented a function to sanitize error messages, improving clarity and consistency in error reporting. - Updated the `run_single` method to utilize the new error handling and message processing logic, ensuring better tracking of conversation history and error states. * feat(supervision): initialize global event bus and register health subscriber - Added initialization of the global event bus with default capacity to ensure channel health events have a live bus and subscriber target. - Registered a health subscriber to enhance monitoring capabilities within the supervised listener context.
358 lines
15 KiB
Markdown
358 lines
15 KiB
Markdown
# Memory Module — Consumer Reference
|
|
|
|
How to use the memory layer from services outside `src/openhuman/memory/`.
|
|
|
|
**Rule**: Always use `MemoryClient` (`src/openhuman/memory/store/client.rs`). Never call `UnifiedMemory` directly — it's internal to the memory module.
|
|
|
|
---
|
|
|
|
## Which Function to Use
|
|
|
|
### Decision Tree
|
|
|
|
```text
|
|
Need to store data?
|
|
|
|
|
+-- Structured key-value pair (config, state, counters)?
|
|
| -> kv_set()
|
|
|
|
|
+-- Full document (text, sync payload, user content)?
|
|
| |
|
|
| +-- Ephemeral / high-frequency (screen captures, ticks)?
|
|
| | -> put_doc_light() (no embedding, no graph extraction)
|
|
| |
|
|
| +-- Important content that should be semantically searchable?
|
|
| | -> put_doc() (embeds + background graph extraction)
|
|
| |
|
|
| +-- Rich content needing entity/relation extraction NOW?
|
|
| -> ingest_doc() (full synchronous pipeline, slower)
|
|
|
|
|
+-- Knowledge graph fact (entity-relation-entity)?
|
|
-> graph_upsert()
|
|
|
|
Need to read data?
|
|
|
|
|
+-- Have a user query / search string?
|
|
| -> query_namespace() (returns text for LLM prompt)
|
|
| -> query_namespace_context_data() (returns structured data)
|
|
|
|
|
+-- Need recent context, no specific query?
|
|
| -> recall_namespace() (returns text)
|
|
| -> recall_namespace_context_data() (returns structured data)
|
|
| -> recall_namespace_memories() (returns individual hits)
|
|
|
|
|
+-- Looking up a specific key?
|
|
| -> kv_get()
|
|
|
|
|
+-- Listing what exists?
|
|
| -> list_documents(), list_namespaces(), kv_list_namespace()
|
|
|
|
|
+-- Querying entity relationships?
|
|
-> graph_query()
|
|
|
|
Need to delete data?
|
|
|
|
|
+-- Single document? -> delete_document()
|
|
+-- Single KV entry? -> kv_delete()
|
|
+-- All data for a skill (e.g. on disconnect/revoke)? -> clear_skill_memory()
|
|
+-- All data in namespace? -> clear_namespace()
|
|
```
|
|
|
|
---
|
|
|
|
### Writing Data
|
|
|
|
#### `put_doc()` — General-purpose document storage
|
|
|
|
Use when content should be **semantically searchable** later. Embeds the content and enqueues background graph extraction.
|
|
|
|
```rust
|
|
client.put_doc(NamespaceDocumentInput {
|
|
namespace: "autocomplete-memory".into(),
|
|
key: Some(format!("completion:{timestamp:018}")),
|
|
title: "Accepted completion".into(),
|
|
content: format!("{context}\n---\n{suggestion}"),
|
|
source_type: Some("autocomplete".into()),
|
|
metadata: Some(json!({ "app_name": app, "timestamp_ms": ts })),
|
|
..Default::default()
|
|
}).await?;
|
|
```
|
|
|
|
**Used by**: Skills JS `memory.insert()`, Autocomplete (searchable completions), Subconscious (working-memory docs).
|
|
|
|
#### `put_doc_light()` — High-frequency / ephemeral storage
|
|
|
|
Use when data is **written often** and doesn't need embedding or graph extraction. Much faster than `put_doc()`.
|
|
|
|
```rust
|
|
client.put_doc_light(NamespaceDocumentInput {
|
|
namespace: "vision".into(),
|
|
key: Some(format!("screen_intelligence_{id}")),
|
|
title: format!("Screen: {app_name} — {window_title}"),
|
|
content: yaml_frontmatter_and_text,
|
|
..Default::default()
|
|
}).await?;
|
|
```
|
|
|
|
**Used by**: Screen Intelligence (vision summaries every few seconds).
|
|
|
|
#### `ingest_doc()` — Full synchronous ingestion
|
|
|
|
Use when you need the complete pipeline (chunking, embedding, entity extraction, relation extraction) to **finish before proceeding**. Blocks until done. Prefer `put_doc()` unless you need synchronous guarantees.
|
|
|
|
**Used by**: Skills event loop (ingesting sync content into vector graph).
|
|
|
|
#### `kv_set()` — Structured key-value storage
|
|
|
|
Use for **small, structured data** you'll look up by exact key. Not semantically searchable.
|
|
|
|
```rust
|
|
client.kv_set(
|
|
Some("autocomplete"), // namespace (None = global)
|
|
&format!("accepted:{ts:018}"), // key
|
|
&json!({ "context": ctx, "suggestion": s }),
|
|
).await?;
|
|
```
|
|
|
|
**Used by**: Autocomplete (completion records keyed by timestamp).
|
|
|
|
#### `graph_upsert()` — Knowledge graph facts
|
|
|
|
Use to store **entity-relation-entity triples**. You rarely call this directly — `put_doc()` and `ingest_doc()` extract graph relations automatically. Only call `graph_upsert()` when you have an explicit fact without an associated document.
|
|
|
|
---
|
|
|
|
### Reading Data
|
|
|
|
#### `query_namespace()` — Semantic search (text)
|
|
|
|
Use when you have a **search string** and want relevant content. Returns formatted text ready for LLM prompt injection.
|
|
|
|
```rust
|
|
let context = client.query_namespace(
|
|
"autocomplete-memory", // namespace
|
|
&last_80_chars, // query
|
|
10, // max chunks
|
|
).await?;
|
|
```
|
|
|
|
**Used by**: Autocomplete (matching past completions to current context), Frontend (user-initiated search in MemoryWorkspace).
|
|
|
|
#### `query_namespace_context_data()` — Semantic search (structured)
|
|
|
|
Same query, but returns `NamespaceRetrievalContext` with individual hits, entities, relations, and score breakdowns. Use when you need to process results programmatically.
|
|
|
|
#### `recall_namespace()` — Recent context (text)
|
|
|
|
Use when you need **recent memory** but don't have a query. Returns most recent docs as formatted text.
|
|
|
|
```rust
|
|
if let Ok(Some(text)) = client.recall_namespace(&ns, 3).await {
|
|
// top 3 chunks of recent context
|
|
}
|
|
```
|
|
|
|
**Used by**: Subconscious (fetching context per namespace for situation report).
|
|
|
|
#### `recall_namespace_memories()` — Recent context (individual hits)
|
|
|
|
Returns `Vec<NamespaceMemoryHit>` instead of text. Use when you need to iterate hits and inspect scores.
|
|
|
|
#### `recall_namespace_context_data()` — Recent context (structured)
|
|
|
|
Returns `NamespaceRetrievalContext`. Use when you need the full retrieval object (hits + entities + relations).
|
|
|
|
#### `kv_get()` — Exact key lookup
|
|
|
|
```rust
|
|
let val = client.kv_get(Some("autocomplete"), "accepted:000001719000000").await?;
|
|
```
|
|
|
|
#### `kv_list_namespace()` — List all KV entries
|
|
|
|
Enumerate all key-value pairs in a namespace. Useful for trimming, history display, or bulk operations.
|
|
|
|
**Used by**: Autocomplete (trimming beyond 50 entries, settings UI, bulk clear).
|
|
|
|
#### `list_documents()` — List document metadata
|
|
|
|
Returns JSON with document metadata (not full content). Useful for delta analysis or trimming.
|
|
|
|
**Used by**: Subconscious (finding docs updated since last tick), Autocomplete (trimming beyond 200 docs).
|
|
|
|
#### `graph_query()` — Query knowledge graph
|
|
|
|
Find entity relationships. Filter by optional namespace, subject, and/or predicate.
|
|
|
|
```rust
|
|
let relations = client.graph_query(None, None, None).await?;
|
|
```
|
|
|
|
**Used by**: Subconscious (relations for situation report), Frontend (knowledge graph display).
|
|
|
|
---
|
|
|
|
### Deleting Data
|
|
|
|
| Function | When to use |
|
|
|----------|-------------|
|
|
| `delete_document(namespace, id)` | Remove a specific document |
|
|
| `kv_delete(namespace, key)` | Remove a specific KV entry |
|
|
| `clear_skill_memory(skill_id, integration_id)` | Disconnect / revoke: clears skill-scoped memory in the shared `skill-{skill_id}` namespace. Storage is not isolated per integration—multiple integrations share that namespace; `integration_id` identifies the integration in the API contract (see implementation in `MemoryClient::clear_skill_memory`) |
|
|
| `clear_namespace(namespace)` | Wipe an arbitrary namespace |
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
**Fire-and-forget writes** — Spawn a background task to avoid blocking:
|
|
|
|
```rust
|
|
let client = memory_client.clone();
|
|
tokio::spawn(async move {
|
|
if let Err(e) = client.put_doc(input).await {
|
|
log::warn!("memory write failed: {e}");
|
|
}
|
|
});
|
|
```
|
|
|
|
**Trim-after-write** — Cap growth after each insert (e.g., max 50 KV entries, max 200 docs). Use `kv_list_namespace()` or `list_documents()` then delete oldest.
|
|
|
|
**Init on app startup** — Frontend calls `syncMemoryClientToken()` in `CoreStateProvider.tsx` on mount and token refresh. Memory subsystem must be initialized before queries run.
|
|
|
|
---
|
|
|
|
## Namespace Convention
|
|
|
|
| Namespace | Owner | Description |
|
|
|-----------|-------|-------------|
|
|
| `skill-{skill_id}` | Skills event loop | Raw state blobs + per-page docs (e.g., `skill-notion`, `skill-gmail`) |
|
|
| `global` | Skills working_memory.rs | Extracted user facts (preferences, goals, entities) |
|
|
| `conversations` | Agent/inference | Conversation context |
|
|
| `autocomplete` | Autocomplete | Accepted completion records (KV) |
|
|
| `autocomplete-memory` | Autocomplete | Searchable completion documents |
|
|
| `vision` | Screen intelligence | Vision summaries from screen captures |
|
|
|
|
---
|
|
|
|
## MemoryClient API Reference
|
|
|
|
**File**: `src/openhuman/memory/store/client.rs`
|
|
|
|
### Documents
|
|
|
|
| Function | Line | Type | Description |
|
|
|----------|------|------|-------------|
|
|
| `put_doc(NamespaceDocumentInput) -> Result<String>` | 105 | WRITE | Stores document; background graph extraction |
|
|
| `put_doc_light(NamespaceDocumentInput) -> Result<String>` | 125 | WRITE | Lightweight insert; no embedding or extraction |
|
|
| `ingest_doc(MemoryIngestionRequest) -> Result<MemoryIngestionResult>` | 132 | WRITE | Full synchronous ingestion pipeline |
|
|
| `store_skill_sync(...)` | 143 | WRITE | Skill-specific upsert into `skill-{skill_id}` namespace |
|
|
| `list_documents(Option<&str>) -> Result<Value>` | 184 | READ | Lists documents, optionally by namespace |
|
|
| `delete_document(&str, &str) -> Result<Value>` | 197 | DELETE | Deletes by namespace + document ID |
|
|
|
|
### Namespaces
|
|
|
|
| Function | Line | Type | Description |
|
|
|----------|------|------|-------------|
|
|
| `list_namespaces() -> Result<Vec<String>>` | 192 | READ | Lists all namespaces |
|
|
| `clear_namespace(&str) -> Result<()>` | 206 | DELETE | Clears all data in a namespace |
|
|
| `clear_skill_memory(&str, &str) -> Result<()>` | 211 | DELETE | Clears documents in the shared `skill-{skill_id}` namespace; second arg is the integration identifier passed from disconnect flows |
|
|
|
|
### Query / Recall
|
|
|
|
| Function | Line | Type | Description |
|
|
|----------|------|------|-------------|
|
|
| `query_namespace(&str, &str, u32) -> Result<String>` | 234 | READ | Semantic query; returns text |
|
|
| `query_namespace_context_data(&str, &str, u32) -> Result<NamespaceRetrievalContext>` | 246 | READ | Semantic query; returns structured data |
|
|
| `recall_namespace(&str, u32) -> Result<Option<String>>` | 258 | READ | Recent context; returns text |
|
|
| `recall_namespace_context_data(&str, u32) -> Result<NamespaceRetrievalContext>` | 269 | READ | Recent context; returns structured data |
|
|
| `recall_namespace_memories(&str, u32) -> Result<Vec<NamespaceMemoryHit>>` | 280 | READ | Recent context; returns individual hits |
|
|
|
|
### Key-Value
|
|
|
|
| Function | Line | Type | Description |
|
|
|----------|------|------|-------------|
|
|
| `kv_set(Option<&str>, &str, &Value) -> Result<()>` | 289 | WRITE | Sets KV pair (`None` namespace = global) |
|
|
| `kv_get(Option<&str>, &str) -> Result<Option<Value>>` | 302 | READ | Gets KV value |
|
|
| `kv_delete(Option<&str>, &str) -> Result<bool>` | 314 | DELETE | Deletes KV pair |
|
|
| `kv_list_namespace(&str) -> Result<Vec<Value>>` | 322 | READ | Lists all KV pairs in namespace |
|
|
|
|
### Knowledge Graph
|
|
|
|
| Function | Line | Type | Description |
|
|
|----------|------|------|-------------|
|
|
| `graph_upsert(Option<&str>, &str, &str, &str, &Value) -> Result<()>` | 330 | WRITE | Upserts relation triple (`None` namespace = global) |
|
|
| `graph_query(Option<&str>, Option<&str>, Option<&str>) -> Result<Vec<Value>>` | 356 | READ | Queries graph with optional filters |
|
|
|
|
---
|
|
|
|
## RPC Method Names
|
|
|
|
For callers going through JSON-RPC (frontend or external). Each maps 1:1 to a `MemoryClient` method above.
|
|
|
|
| RPC Method | Type | MemoryClient equivalent |
|
|
|------------|------|------------------------|
|
|
| `openhuman.memory_init` | INIT | — (initializes subsystem) |
|
|
| `openhuman.memory_doc_put` | WRITE | `put_doc()` |
|
|
| `openhuman.memory_doc_ingest` | WRITE | `ingest_doc()` |
|
|
| `openhuman.memory_doc_list` | READ | `list_documents()` |
|
|
| `openhuman.memory_doc_delete` | DELETE | `delete_document()` |
|
|
| `openhuman.memory_namespace_list` | READ | `list_namespaces()` |
|
|
| `openhuman.memory_list_namespaces` | READ | `list_namespaces()` (structured envelope) |
|
|
| `openhuman.memory_list_documents` | READ | `list_documents()` (structured envelope) |
|
|
| `openhuman.memory_delete_document` | DELETE | `delete_document()` (structured envelope) |
|
|
| `openhuman.memory_clear_namespace` | DELETE | `clear_namespace()` |
|
|
| `openhuman.memory_context_query` | READ | `query_namespace()` |
|
|
| `openhuman.memory_context_recall` | READ | `recall_namespace()` |
|
|
| `openhuman.memory_query_namespace` | READ | `query_namespace_context_data()` |
|
|
| `openhuman.memory_recall_context` | READ | `recall_namespace_context_data()` |
|
|
| `openhuman.memory_recall_memories` | READ | `recall_namespace_memories()` |
|
|
| `openhuman.memory_kv_set` | WRITE | `kv_set()` |
|
|
| `openhuman.memory_kv_get` | READ | `kv_get()` |
|
|
| `openhuman.memory_kv_delete` | DELETE | `kv_delete()` |
|
|
| `openhuman.memory_kv_list_namespace` | READ | `kv_list_namespace()` |
|
|
| `openhuman.memory_graph_upsert` | WRITE | `graph_upsert()` |
|
|
| `openhuman.memory_graph_query` | READ | `graph_query()` |
|
|
| `openhuman.ai_list_memory_files` | READ | — (file I/O, not MemoryClient) |
|
|
| `openhuman.ai_read_memory_file` | READ | — (file I/O) |
|
|
| `openhuman.ai_write_memory_file` | WRITE | — (file I/O) |
|
|
|
|
---
|
|
|
|
## Frontend TypeScript Wrappers
|
|
|
|
**File**: `app/src/utils/tauriCommands/memory.ts`
|
|
|
|
Each calls the corresponding RPC method above via `core_rpc_relay`.
|
|
|
|
| Function | Line | RPC Method |
|
|
|----------|------|------------|
|
|
| `syncMemoryClientToken(token)` | 82 | `openhuman.memory_init` |
|
|
| `memoryListDocuments(namespace?)` | 102 | `openhuman.memory_list_documents` |
|
|
| `memoryListNamespaces()` | 117 | `openhuman.memory_list_namespaces` |
|
|
| `memoryDeleteDocument(id, ns)` | 132 | `openhuman.memory_delete_document` |
|
|
| `memoryClearNamespace(ns)` | 145 | `openhuman.memory_clear_namespace` |
|
|
| `memoryQueryNamespace(ns, query, max?)` | 158 | `openhuman.memory_query_namespace` |
|
|
| `memoryRecallNamespace(ns, max?)` | 173 | `openhuman.memory_recall_context` |
|
|
| `memoryGraphQuery(ns?, subj?, pred?)` | 187 | `openhuman.memory_graph_query` |
|
|
| `memoryDocIngest(params)` | 210 | `openhuman.memory_doc_ingest` |
|
|
| `aiListMemoryFiles(dir?)` | 229 | `openhuman.ai_list_memory_files` |
|
|
| `aiReadMemoryFile(path)` | 246 | `openhuman.ai_read_memory_file` |
|
|
| `aiWriteMemoryFile(path, content)` | 261 | `openhuman.ai_write_memory_file` |
|
|
|
|
---
|
|
|
|
## Key Data Types
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `NamespaceDocumentInput` | Document upsert payload (namespace, content, metadata, tags, source_type, priority) |
|
|
| `MemoryIngestionRequest` | Full ingestion request with config (chunking, embedding, extraction flags) |
|
|
| `MemoryIngestionResult` | Ingestion result (document_id, chunk_count, entities, relations) |
|
|
| `NamespaceRetrievalContext` | Structured retrieval (hits, entities, relations, chunks) |
|
|
| `NamespaceMemoryHit` | Individual memory item with score breakdown |
|
|
| `GraphRelationRecord` | Knowledge graph triple (subject, predicate, object, evidence, namespace) |
|
|
| `RetrievalScoreBreakdown` | Score components: graph, vector, keyword, episodic, freshness |
|
|
|
|
All types defined in `src/openhuman/memory/store/types.rs`.
|