Add generic memory gateway v1
This commit is contained in:
@ -1,54 +1,211 @@
|
||||
---
|
||||
name: memory-gateway
|
||||
description: Use this skill when an agent or harness needs reusable memory: search prior context, retrieve OpenViking resources, upload documents into knowledge, summarize arbitrary content with the Memory Gateway LLM, commit final conclusions, or cite related Obsidian notes. This skill is domain-neutral.
|
||||
version: 2.0.0
|
||||
description: Use this skill when Hermes needs shared long-term memory, user-scoped preferences/profile, workspace memory, session episode capture, Memory Gateway retrieval, OpenViking context search, Obsidian document upload/review, or session commit through the standalone EverMemOS service. This skill is domain-neutral.
|
||||
version: 3.1.0
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [memory, openviking, obsidian, knowledge, retrieval, summarization, document-ingestion, agent-context]
|
||||
tags: [memory, memory-gateway, openviking, obsidian, evermemos, long-term-memory, retrieval, agent-context]
|
||||
---
|
||||
|
||||
# Memory Gateway
|
||||
|
||||
Use this skill as a generic memory layer for any agent / harness. It connects Hermes to the local Memory Gateway at `http://127.0.0.1:1934`, which fronts OpenViking and an Obsidian vault.
|
||||
Use this skill as Hermes' generic memory layer. It connects Hermes to the local Memory Gateway at `http://127.0.0.1:1934`.
|
||||
|
||||
## Trigger Rule
|
||||
The gateway provides:
|
||||
|
||||
Use this skill when the user asks to:
|
||||
- search prior memory or retrieve related context
|
||||
- upload a document and make it reusable knowledge
|
||||
- summarize content and store it as memory/resource
|
||||
- commit final conclusions, decisions, lessons learned, or research notes
|
||||
- cite related OpenViking resources or Obsidian notes
|
||||
- prepare context for another agent or workflow
|
||||
|
||||
Do not assume any domain-specific workflow. Treat Memory Gateway as a reusable memory and knowledge entrypoint.
|
||||
- v1 user/agent/workspace/session aware memory APIs backed by SQLite metadata.
|
||||
- ACL and namespace routing before retrieval.
|
||||
- OpenViking fan-out search for visible namespaces.
|
||||
- Session episode capture and commit through the standalone EverMemOS HTTP service, with Gateway local fallback only when configured.
|
||||
- Obsidian review drafts for high-value or conflicting long-term memory candidates.
|
||||
- Legacy summary/document upload endpoints for LLM summarization and Obsidian knowledge ingestion.
|
||||
|
||||
## Environment
|
||||
|
||||
Defaults:
|
||||
|
||||
- Memory Gateway URL: `http://127.0.0.1:1934`
|
||||
- EverMemOS URL through Gateway config: `http://127.0.0.1:1995`
|
||||
- Obsidian vault: `/home/tom/memory-gateway/obsidian-vault`
|
||||
- Default namespace: `memory-gateway`
|
||||
- Default review queue: `/home/tom/memory-gateway/obsidian-vault/Reviews/Queue`
|
||||
|
||||
Optional env vars:
|
||||
|
||||
- `MEMORY_GATEWAY_URL`
|
||||
- `MEMORY_GATEWAY_API_KEY`
|
||||
- `MEMORY_GATEWAY_OBSIDIAN_VAULT`
|
||||
|
||||
## Core Workflows
|
||||
## Recommended Hermes Workflow
|
||||
|
||||
### 1. Retrieve Context
|
||||
For normal agent work:
|
||||
|
||||
1. Search memory before answering if prior context may matter.
|
||||
2. Append important session episodes while working.
|
||||
3. Commit the session at the end so EverMemOS can promote stable memories.
|
||||
4. Use feedback to mark incorrect, duplicate, outdated, or useful memories.
|
||||
5. Upload documents only when they are reusable knowledge, not raw noisy logs.
|
||||
|
||||
Do not write full transcripts to long-term memory. Use episodes for temporary process capture and commit only stable conclusions.
|
||||
|
||||
## v1 Memory Commands
|
||||
|
||||
### Check EverMemOS
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/retrieve_memory.py \
|
||||
--query "project decision memory gateway LLM summary" \
|
||||
--uri viking://resources \
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/evermemos_health.py
|
||||
```
|
||||
|
||||
Expected healthy response includes `status: ok` and `response.service: evermemos-local`.
|
||||
|
||||
### Create User
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_create_user.py \
|
||||
--user-id user_tom \
|
||||
--display-name "Tom" \
|
||||
--preference language=zh-CN
|
||||
```
|
||||
|
||||
### Search ACL-Aware Memory
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_search.py \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--query "namespace ACL decision" \
|
||||
--limit 5
|
||||
```
|
||||
|
||||
Use retrieval before answering when prior context may materially improve correctness.
|
||||
Equivalent backward-compatible command:
|
||||
|
||||
### 2. Summarize And Commit
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/retrieve_memory.py \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--query "namespace ACL decision" \
|
||||
--limit 5
|
||||
```
|
||||
|
||||
If `retrieve_memory.py` is called without `--user-id`, it falls back to the legacy `/api/search` endpoint.
|
||||
|
||||
### Upsert Long-Term Memory
|
||||
|
||||
Use this only for stable, concise, reusable memory.
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_upsert.py \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--memory-type preference \
|
||||
--visibility private \
|
||||
--importance 0.8 \
|
||||
--confidence 0.9 \
|
||||
--tag preference \
|
||||
--summary "中文、结构化、轻量 POC 优先" \
|
||||
--text "用户偏好中文输出,结构化但不要过度工程化。"
|
||||
```
|
||||
|
||||
### Append Session Episode
|
||||
|
||||
Use this during a task to record useful process notes without immediately polluting long-term memory.
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_append_episode.py \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--session-id sess_demo \
|
||||
--tag decision \
|
||||
--text "结论:这个项目必须保留用户隔离和 namespace ACL。"
|
||||
```
|
||||
|
||||
### Commit Session Through EverMemOS
|
||||
|
||||
This asks Memory Gateway to call the standalone EverMemOS service configured in `config.yaml`.
|
||||
For local POC the default service is `http://127.0.0.1:1995`. If `evermemos.fallback_to_local` is true and the service is unavailable, Gateway returns `evermemos_backend: local-fallback`.
|
||||
|
||||
- extracts candidate memories from session episodes
|
||||
- deduplicates exact repeated candidates
|
||||
- detects simple conflicts
|
||||
- promotes normal stable memories into SQLite long-term memory
|
||||
- sends high-value or conflicting candidates to Obsidian review drafts
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_commit_session.py \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--session-id sess_demo \
|
||||
--min-importance 0.6
|
||||
```
|
||||
|
||||
Review drafts are written under:
|
||||
|
||||
```text
|
||||
/home/tom/memory-gateway/obsidian-vault/Reviews/Queue/
|
||||
```
|
||||
|
||||
### Get Profile
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_get_profile.py \
|
||||
--user-id user_tom
|
||||
```
|
||||
|
||||
### List Visible Namespaces
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_list_namespaces.py \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--session-id sess_demo
|
||||
```
|
||||
|
||||
### Patch Memory
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_patch.py \
|
||||
--memory-id mem_xxx \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--summary "用户偏好中文、结构化、少废话。" \
|
||||
--importance 0.9 \
|
||||
--tag preference \
|
||||
--tag confirmed
|
||||
```
|
||||
|
||||
### Feedback
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_feedback.py \
|
||||
--memory-id mem_xxx \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway \
|
||||
--feedback incorrect \
|
||||
--comment "这是临时偏好,不应长期保留。"
|
||||
```
|
||||
|
||||
### Delete Memory
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/memory_delete.py \
|
||||
--memory-id mem_xxx \
|
||||
--user-id user_tom \
|
||||
--agent-id agent_hermes \
|
||||
--workspace-id ws_memory_gateway
|
||||
```
|
||||
|
||||
## Knowledge And Obsidian Commands
|
||||
|
||||
### Summarize And Commit Via Legacy LLM Endpoint
|
||||
|
||||
Use this for high-value text that should become an OpenViking resource or summarized memory.
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/commit_summary.py \
|
||||
@ -60,9 +217,9 @@ python /home/tom/.hermes/skills/memory-gateway/scripts/commit_summary.py \
|
||||
--text "<final conclusion or reusable knowledge>"
|
||||
```
|
||||
|
||||
This calls `POST /api/summary`, which uses the configured LLM and writes to OpenViking when `persist-as` is not `none`.
|
||||
This calls `POST /api/summary`.
|
||||
|
||||
### 3. Upload Document As Knowledge
|
||||
### Upload Document As Knowledge
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/upload_knowledge.py \
|
||||
@ -76,7 +233,7 @@ python /home/tom/.hermes/skills/memory-gateway/scripts/upload_knowledge.py \
|
||||
|
||||
This calls `POST /api/knowledge/upload`: document -> MarkItDown Markdown -> Obsidian note -> LLM summary -> OpenViking resource.
|
||||
|
||||
### 4. Search Obsidian Notes
|
||||
### Search Local Obsidian Notes
|
||||
|
||||
```bash
|
||||
python /home/tom/.hermes/skills/memory-gateway/scripts/search_obsidian.py \
|
||||
@ -84,6 +241,21 @@ python /home/tom/.hermes/skills/memory-gateway/scripts/search_obsidian.py \
|
||||
--limit 5
|
||||
```
|
||||
|
||||
## MCP Tool Names
|
||||
|
||||
The gateway also exposes these v1 tools through `/mcp/rpc`:
|
||||
|
||||
- `memory_search`
|
||||
- `memory_upsert`
|
||||
- `memory_append_episode`
|
||||
- `memory_commit_session`
|
||||
- `memory_get_profile`
|
||||
- `memory_list_namespaces`
|
||||
- `memory_delete`
|
||||
- `memory_feedback`
|
||||
|
||||
Use MCP tools when Hermes has an MCP bridge available. Use the scripts above when Hermes runs skills as shell commands.
|
||||
|
||||
## Output Template
|
||||
|
||||
When using this skill, answer with:
|
||||
@ -92,25 +264,27 @@ When using this skill, answer with:
|
||||
## Answer
|
||||
<direct answer or synthesis>
|
||||
|
||||
## Memory / Resource References
|
||||
- `<title or URI>` — `<viking://...>` — why it matters
|
||||
## Memory References
|
||||
- `<memory_id or URI>` — `<namespace>` — why it matters
|
||||
|
||||
## Obsidian References
|
||||
- `<note.md>` — `<relative path>` — why it matters
|
||||
## Obsidian Review
|
||||
- `<draft path>` — why it needs review
|
||||
|
||||
## Suggested Memory Commit
|
||||
- commit: yes/no
|
||||
- namespace:
|
||||
- memory_type:
|
||||
- tags:
|
||||
- resource_uri: if committed
|
||||
## Memory Action
|
||||
- searched: yes/no
|
||||
- appended_episode: yes/no
|
||||
- committed_session: yes/no
|
||||
- promoted_memory_count:
|
||||
- review_draft_count:
|
||||
```
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Do not store raw noisy data as long-term memory when a concise summary is enough.
|
||||
- Prefer LLM summaries and structured artifacts over full chat transcripts.
|
||||
- Do not store raw noisy data as long-term memory.
|
||||
- Use `memory_append_episode.py` for temporary process notes.
|
||||
- Use `memory_commit_session.py` at task end to let EverMemOS decide what should persist.
|
||||
- Use `memory_upsert.py` directly only for stable, concise, user-approved memory.
|
||||
- Do not commit secrets, credentials, tokens, private keys, or unnecessary personal data.
|
||||
- If content is sensitive, summarize and redact before committing.
|
||||
- If retrieval quality looks noisy, state that and cite only useful results.
|
||||
- Always report whether a commit/upload actually succeeded and include the returned resource URI when available.
|
||||
- High-value or conflicting candidates should go to Obsidian review drafts before becoming durable memory.
|
||||
- Always report whether retrieval, episode append, session commit, or upload actually succeeded.
|
||||
|
||||
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
@ -17,3 +18,35 @@ def post_json(path: str, payload: dict[str, Any], gateway_url: str = DEFAULT_GAT
|
||||
req.add_header("X-API-Key", api_key)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def get_json(path: str, params: dict[str, Any] | None = None, gateway_url: str = DEFAULT_GATEWAY_URL, api_key: str = DEFAULT_GATEWAY_API_KEY, timeout: int = 120) -> dict[str, Any] | list[Any]:
|
||||
query = urllib.parse.urlencode({k: v for k, v in (params or {}).items() if v not in (None, "")})
|
||||
url = gateway_url.rstrip("/") + path + (f"?{query}" if query else "")
|
||||
req = urllib.request.Request(url, method="GET")
|
||||
if api_key:
|
||||
req.add_header("X-API-Key", api_key)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def patch_json(path: str, payload: dict[str, Any], params: dict[str, Any] | None = None, gateway_url: str = DEFAULT_GATEWAY_URL, api_key: str = DEFAULT_GATEWAY_API_KEY, timeout: int = 120) -> dict[str, Any]:
|
||||
query = urllib.parse.urlencode({k: v for k, v in (params or {}).items() if v not in (None, "")})
|
||||
url = gateway_url.rstrip("/") + path + (f"?{query}" if query else "")
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=data, method="PATCH")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
if api_key:
|
||||
req.add_header("X-API-Key", api_key)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def delete_json(path: str, params: dict[str, Any] | None = None, gateway_url: str = DEFAULT_GATEWAY_URL, api_key: str = DEFAULT_GATEWAY_API_KEY, timeout: int = 120) -> dict[str, Any]:
|
||||
query = urllib.parse.urlencode({k: v for k, v in (params or {}).items() if v not in (None, "")})
|
||||
url = gateway_url.rstrip("/") + path + (f"?{query}" if query else "")
|
||||
req = urllib.request.Request(url, method="DELETE")
|
||||
if api_key:
|
||||
req.add_header("X-API-Key", api_key)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, get_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Check standalone EverMemOS health through Memory Gateway.")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(get_json("/v1/evermemos/health", gateway_url=args.gateway_url, api_key=args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def load_content(args: argparse.Namespace) -> str:
|
||||
if args.file:
|
||||
return Path(args.file).read_text(encoding="utf-8")
|
||||
if args.text:
|
||||
return args.text
|
||||
return sys.stdin.read().strip()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Append session episode memory without directly promoting it.")
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--session-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--namespace", default="")
|
||||
parser.add_argument("--text", default="")
|
||||
parser.add_argument("--file", default="")
|
||||
parser.add_argument("--tag", action="append", default=[])
|
||||
parser.add_argument("--source", default="conversation")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
content = load_content(args)
|
||||
if not content:
|
||||
parser.error("No episode content provided via --text, --file, or stdin")
|
||||
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id or None,
|
||||
"workspace_id": args.workspace_id or None,
|
||||
"session_id": args.session_id,
|
||||
"namespace": args.namespace or None,
|
||||
"content": content,
|
||||
"tags": args.tag,
|
||||
"source": args.source,
|
||||
}
|
||||
print(json.dumps(post_json("/v1/episodes", payload, args.gateway_url, args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Commit a session through the minimal EverMemOS consolidation worker.")
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--session-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--target-namespace", default="")
|
||||
parser.add_argument("--min-importance", type=float, default=0.6)
|
||||
parser.add_argument("--no-promote", action="store_true")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id or None,
|
||||
"workspace_id": args.workspace_id or None,
|
||||
"session_id": args.session_id,
|
||||
"promote": not args.no_promote,
|
||||
"min_importance": args.min_importance,
|
||||
"target_namespace": args.target_namespace or None,
|
||||
}
|
||||
print(json.dumps(post_json(f"/v1/sessions/{args.session_id}/commit", payload, args.gateway_url, args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Create or replace a Memory Gateway v1 user.")
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--display-name", required=True)
|
||||
parser.add_argument("--preference", action="append", default=[], help="Preference as key=value; repeatable")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
preferences = {}
|
||||
for item in args.preference:
|
||||
if "=" not in item:
|
||||
parser.error(f"Invalid --preference {item!r}; expected key=value")
|
||||
key, value = item.split("=", 1)
|
||||
preferences[key.strip()] = value.strip()
|
||||
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"display_name": args.display_name,
|
||||
"preferences": preferences,
|
||||
}
|
||||
print(json.dumps(post_json("/v1/users", payload, args.gateway_url, args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
31
integrations/hermes/memory-gateway/scripts/memory_delete.py
Normal file
31
integrations/hermes/memory-gateway/scripts/memory_delete.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, delete_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Delete a MemoryRecord if the caller has access.")
|
||||
parser.add_argument("--memory-id", required=True)
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
params = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id,
|
||||
"workspace_id": args.workspace_id,
|
||||
"session_id": args.session_id,
|
||||
}
|
||||
print(json.dumps(delete_json(f"/v1/memory/{args.memory_id}", params=params, gateway_url=args.gateway_url, api_key=args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Attach quality feedback to a MemoryRecord.")
|
||||
parser.add_argument("--memory-id", required=True)
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--feedback", required=True, choices=["useful", "not_useful", "incorrect", "duplicate", "outdated"])
|
||||
parser.add_argument("--comment", default="")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id or None,
|
||||
"workspace_id": args.workspace_id or None,
|
||||
"session_id": args.session_id or None,
|
||||
"feedback": args.feedback,
|
||||
"comment": args.comment or None,
|
||||
}
|
||||
print(json.dumps(post_json(f"/v1/memory/{args.memory_id}/feedback", payload, args.gateway_url, args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, get_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Get a user's Memory Gateway profile.")
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(get_json(f"/v1/users/{args.user_id}/profile", gateway_url=args.gateway_url, api_key=args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, get_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="List namespaces visible to a user/agent/workspace/session context.")
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
params = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id,
|
||||
"workspace_id": args.workspace_id,
|
||||
"session_id": args.session_id,
|
||||
}
|
||||
print(json.dumps(get_json("/v1/namespaces", params=params, gateway_url=args.gateway_url, api_key=args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
54
integrations/hermes/memory-gateway/scripts/memory_patch.py
Normal file
54
integrations/hermes/memory-gateway/scripts/memory_patch.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, patch_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Patch a MemoryRecord.")
|
||||
parser.add_argument("--memory-id", required=True)
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--content", default="")
|
||||
parser.add_argument("--summary", default="")
|
||||
parser.add_argument("--tag", action="append", default=None)
|
||||
parser.add_argument("--importance", type=float, default=None)
|
||||
parser.add_argument("--confidence", type=float, default=None)
|
||||
parser.add_argument("--visibility", choices=["private", "agent-only", "workspace-shared", "global"], default=None)
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = {}
|
||||
if args.content:
|
||||
payload["content"] = args.content
|
||||
if args.summary:
|
||||
payload["summary"] = args.summary
|
||||
if args.tag is not None:
|
||||
payload["tags"] = args.tag
|
||||
if args.importance is not None:
|
||||
payload["importance"] = args.importance
|
||||
if args.confidence is not None:
|
||||
payload["confidence"] = args.confidence
|
||||
if args.visibility:
|
||||
payload["visibility"] = args.visibility
|
||||
if not payload:
|
||||
parser.error("No patch fields provided")
|
||||
|
||||
params = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id,
|
||||
"workspace_id": args.workspace_id,
|
||||
"session_id": args.session_id,
|
||||
}
|
||||
print(json.dumps(patch_json(f"/v1/memory/{args.memory_id}", payload, params=params, gateway_url=args.gateway_url, api_key=args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
41
integrations/hermes/memory-gateway/scripts/memory_search.py
Normal file
41
integrations/hermes/memory-gateway/scripts/memory_search.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Search v1 Memory Gateway with user/agent/workspace/session ACL.")
|
||||
parser.add_argument("--query", required=True)
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--namespace", action="append", default=[], help="Allowed namespace to search; repeatable")
|
||||
parser.add_argument("--memory-type", action="append", default=[], help="Memory type filter; repeatable")
|
||||
parser.add_argument("--tag", action="append", default=[], help="Tag filter; repeatable")
|
||||
parser.add_argument("--limit", type=int, default=5)
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id or None,
|
||||
"workspace_id": args.workspace_id or None,
|
||||
"session_id": args.session_id or None,
|
||||
"query": args.query,
|
||||
"namespaces": args.namespace,
|
||||
"memory_types": args.memory_type,
|
||||
"tags": args.tag,
|
||||
"limit": args.limit,
|
||||
}
|
||||
print(json.dumps(post_json("/v1/memory/search", payload, args.gateway_url, args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
64
integrations/hermes/memory-gateway/scripts/memory_upsert.py
Normal file
64
integrations/hermes/memory-gateway/scripts/memory_upsert.py
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def load_content(args: argparse.Namespace) -> str:
|
||||
if args.file:
|
||||
return Path(args.file).read_text(encoding="utf-8")
|
||||
if args.text:
|
||||
return args.text
|
||||
return sys.stdin.read().strip()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Create a v1 MemoryRecord through Memory Gateway.")
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--namespace", default="")
|
||||
parser.add_argument("--memory-type", default="fact")
|
||||
parser.add_argument("--text", default="")
|
||||
parser.add_argument("--file", default="")
|
||||
parser.add_argument("--summary", default="")
|
||||
parser.add_argument("--tag", action="append", default=[])
|
||||
parser.add_argument("--importance", type=float, default=0.5)
|
||||
parser.add_argument("--confidence", type=float, default=0.8)
|
||||
parser.add_argument("--visibility", choices=["private", "agent-only", "workspace-shared", "global"], default="private")
|
||||
parser.add_argument("--source", default="manual")
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
content = load_content(args)
|
||||
if not content:
|
||||
parser.error("No memory content provided via --text, --file, or stdin")
|
||||
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id or None,
|
||||
"workspace_id": args.workspace_id or None,
|
||||
"session_id": args.session_id or None,
|
||||
"namespace": args.namespace or None,
|
||||
"memory_type": args.memory_type,
|
||||
"content": content,
|
||||
"summary": args.summary or None,
|
||||
"tags": args.tag,
|
||||
"importance": args.importance,
|
||||
"confidence": args.confidence,
|
||||
"visibility": args.visibility,
|
||||
"source": args.source,
|
||||
}
|
||||
print(json.dumps(post_json("/v1/memory", payload, args.gateway_url, args.api_key), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -7,21 +7,37 @@ from _client import DEFAULT_GATEWAY_API_KEY, DEFAULT_GATEWAY_URL, post_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Retrieve memory/resources from Memory Gateway.")
|
||||
parser = argparse.ArgumentParser(description="Retrieve memory/resources from Memory Gateway. Defaults to v1 ACL-aware search when --user-id is provided.")
|
||||
parser.add_argument("--query", required=True, help="Search query")
|
||||
parser.add_argument("--uri", default="", help="Optional OpenViking URI scope, e.g. viking://resources/project")
|
||||
parser.add_argument("--namespace", default="", help="Optional namespace if URI is not provided")
|
||||
parser.add_argument("--user-id", default="", help="Use v1 ACL-aware search when provided")
|
||||
parser.add_argument("--agent-id", default="")
|
||||
parser.add_argument("--workspace-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--limit", type=int, default=5)
|
||||
parser.add_argument("--gateway-url", default=DEFAULT_GATEWAY_URL)
|
||||
parser.add_argument("--api-key", default=DEFAULT_GATEWAY_API_KEY)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = {"query": args.query, "limit": args.limit}
|
||||
if args.uri:
|
||||
payload["uri"] = args.uri
|
||||
if args.namespace:
|
||||
payload["namespace"] = args.namespace
|
||||
result = post_json("/api/search", payload, args.gateway_url, args.api_key)
|
||||
if args.user_id:
|
||||
payload = {
|
||||
"user_id": args.user_id,
|
||||
"agent_id": args.agent_id or None,
|
||||
"workspace_id": args.workspace_id or None,
|
||||
"session_id": args.session_id or None,
|
||||
"query": args.query,
|
||||
"namespaces": [args.namespace] if args.namespace else [],
|
||||
"limit": args.limit,
|
||||
}
|
||||
result = post_json("/v1/memory/search", payload, args.gateway_url, args.api_key)
|
||||
else:
|
||||
payload = {"query": args.query, "limit": args.limit}
|
||||
if args.uri:
|
||||
payload["uri"] = args.uri
|
||||
if args.namespace:
|
||||
payload["namespace"] = args.namespace
|
||||
result = post_json("/api/search", payload, args.gateway_url, args.api_key)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user