Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,26 @@ The `local` backend is always available and runs file, command, and code tools a

Memory is a persistent plane parallel to sandbox execution. It is not a tool result and not just prompt text; it is state that can be attached to an agent, recalled before a run, and committed after a run.

The base install includes a zero-dependency local memory backend. It stores data under `.openrath/memory/`, supports lexical BM25 recall without an LLM, and can use embeddings when an embedding provider is configured. OpenViking is available as an optional backend for users who want a richer external memory service.
The base install includes a zero-dependency local memory backend. It stores data under `.openrath/memory/`, supports lexical BM25 recall without an LLM, and can use embeddings when an embedding provider is configured. OpenViking is available as an optional backend for users who want a richer external memory service. Milvus is available as an optional vector memory backend for semantic recall on Milvus Lite, Milvus server, or Zilliz Cloud.

```python
with flow.Agent("You remember useful facts.", model="gpt-5.5", memory="local") as agent:
agent.remember_memory("The user works mostly in Python.")
hits = agent.recall_memory("preferred programming language")
```

Milvus defaults to local Milvus Lite (`./milvus.db`) and uses the existing
OpenAI-compatible embedding settings:

```python
# pip install "openrath[milvus]"
# Optional: export MILVUS_URI=http://localhost:19530 or a Zilliz Cloud URI.
# Optional for cloud: export MILVUS_TOKEN=...
with flow.Agent("You remember useful facts.", model="gpt-5.5", memory="milvus") as agent:
agent.remember_memory("The user evaluates vector databases.")
hits = agent.recall_memory("vector database preference")
```

Agent memory APIs are intentionally discoverable:

- `memory=` binds a store at construction time.
Expand Down Expand Up @@ -326,6 +338,7 @@ Optional sandbox and memory integrations:
```bash
pip install "openrath[opensandbox]"
pip install "openrath[openviking]"
pip install "openrath[milvus]"
```

For source development:
Expand Down
15 changes: 14 additions & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,14 +253,26 @@ session = Session.from_user_message("List files").to("local", spec="./")

Memory 是与 sandbox 执行平行持久平面。它不是 tool result,也不只是 prompt 文本;它是可以绑定到 Agent、在运行前 recall、在运行后 commit 的状态。

基础安装包含一个零依赖的本地 memory 后端。它将数据存储在 `.openrath/memory/` 下,无需 LLM 即可支持 lexical BM25 recall,并在配置了 embedding 提供程序时可以使用 embeddings。OpenViking 作为可选后端,为需要更丰富外部 memory 服务的用户提供。
基础安装包含一个零依赖的本地 memory 后端。它将数据存储在 `.openrath/memory/` 下,无需 LLM 即可支持 lexical BM25 recall,并在配置了 embedding 提供程序时可以使用 embeddings。OpenViking 作为可选后端,为需要更丰富外部 memory 服务的用户提供。Milvus 也作为可选向量 memory 后端提供,可用于 Milvus Lite、Milvus server 或 Zilliz Cloud 上的语义 recall。

```python
with flow.Agent("You remember useful facts.", model="gpt-5.5", memory="local") as agent:
agent.remember_memory("The user works mostly in Python.")
hits = agent.recall_memory("preferred programming language")
```

Milvus 默认使用本地 Milvus Lite(`./milvus.db`),并复用现有的
OpenAI-compatible embedding 配置:

```python
# pip install "openrath[milvus]"
# Optional: export MILVUS_URI=http://localhost:19530 or a Zilliz Cloud URI.
# Optional for cloud: export MILVUS_TOKEN=...
with flow.Agent("You remember useful facts.", model="gpt-5.5", memory="milvus") as agent:
agent.remember_memory("The user evaluates vector databases.")
hits = agent.recall_memory("vector database preference")
```

Agent memory API 有意设计得易于发现:

- `memory=` 在构造时绑定 store。
Expand Down Expand Up @@ -320,6 +332,7 @@ pip install openrath
```bash
pip install "openrath[opensandbox]"
pip install "openrath[openviking]"
pip install "openrath[milvus]"
```

源码开发:
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ otel = [
s3 = [
"boto3>=1.40,<2",
]
milvus = [
"pymilvus[milvus-lite]>=3.0.0",
]

[tool.ruff]
line-length = 88
Expand Down
3 changes: 2 additions & 1 deletion src/rath/config/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Secret layout in ``credentials.json``::

{"version": 1, "llm": {"providers": {"<name>": "<api_key>"}},
"memory": {"providers": {"<name>": "<api_key>"}},
"backend": {"providers": {"<name>": "<api_key>"}}}

Only sections that hold a ``providers`` mapping with an ``api_key`` field are
Expand All @@ -34,7 +35,7 @@
CREDENTIALS_FILENAME = "credentials.json"

# Config sections whose ``providers[*].api_key`` is a secret to externalize.
SECRET_SECTIONS: tuple[str, ...] = ("llm", "backend")
SECRET_SECTIONS: tuple[str, ...] = ("llm", "memory", "backend")


def split_secrets(payload: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
Expand Down
19 changes: 13 additions & 6 deletions src/rath/config/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,26 @@ class MCPConfig(BaseModel):


class MemoryProviderConfig(BaseModel):
"""One named entry under ``memory.providers`` (local backend only).
"""One named entry under ``memory.providers``.

``embedding_provider`` and ``chat_provider`` name entries under
``llm.providers`` used by :class:`~rath.memory.adapters.local.LocalMemoryBackend`
for vector search and commit-time memo extraction respectively.
OpenViking connection settings stay on ``MemoryStoreSpec.options`` or
environment variables — they are not modeled here.
``llm.providers`` used by memory backends for vector search and
commit-time memo extraction respectively. ``api_key`` is externalized to
``credentials.json`` on save and is mapped to backend-specific secret
fields such as the Milvus token at open time.
"""

backend_kind: Literal["local"] = "local"
backend_kind: Literal["local", "milvus"] = "local"
path: str | None = None
embedding_provider: str | None = None
chat_provider: str | None = None
uri: str | None = None
api_key: str | None = None
db_name: str | None = None
collection_name: str | None = None
embedding_model: str | None = None
embedding_dimensions: int | None = None
max_scan: int | None = None

model_config = ConfigDict(extra="allow")

Expand Down
5 changes: 5 additions & 0 deletions src/rath/memory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
except ImportError: # pragma: no cover — optional ``openviking`` extra
pass

try:
from rath.memory.adapters import milvus as _milvus # noqa: F401
except ImportError: # pragma: no cover — optional ``milvus`` extra
pass

# The local backend has zero runtime dependencies; pin it as the default
# so ``rath.memory.current()`` works out of the box.
_set_default("local")
Expand Down
19 changes: 16 additions & 3 deletions src/rath/memory/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ def from_config(
"""Build a :class:`MemoryStoreSpec` from ``~/.openrath/config.json``.

Looks up ``name`` (or ``memory.default_provider`` when ``name=None``)
under ``memory.providers``. Only **local** presets are modeled in
config today; OpenViking stores should be built explicitly via
:class:`MemoryStoreSpec` kwargs / ``options``.
under ``memory.providers``. OpenViking stores should be built
explicitly via :class:`MemoryStoreSpec` kwargs / ``options``.

Lazy-imports :mod:`rath.config` so ``import rath.memory`` never
touches the filesystem.
Expand All @@ -72,6 +71,20 @@ def from_config(
options["embedding_provider"] = entry.embedding_provider
if entry.chat_provider is not None:
options["chat_provider"] = entry.chat_provider
if entry.uri is not None:
options["uri"] = entry.uri
if entry.api_key is not None:
options["token"] = entry.api_key
if entry.db_name is not None:
options["db_name"] = entry.db_name
if entry.collection_name is not None:
options["collection_name"] = entry.collection_name
if entry.embedding_model is not None:
options["embedding_model"] = entry.embedding_model
if entry.embedding_dimensions is not None:
options["embedding_dimensions"] = entry.embedding_dimensions
if entry.max_scan is not None:
options["max_scan"] = entry.max_scan
base = cls(
options=MappingProxyType(options) if options else None,
)
Expand Down
Loading