rememhq 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rememhq-0.1.5/.gitignore +147 -0
- rememhq-0.1.5/PKG-INFO +208 -0
- rememhq-0.1.5/README.md +169 -0
- rememhq-0.1.5/pyproject.toml +55 -0
- rememhq-0.1.5/rememhq/__init__.py +7 -0
- rememhq-0.1.5/rememhq/client.py +269 -0
- rememhq-0.1.5/rememhq/config.py +32 -0
- rememhq-0.1.5/rememhq/models.py +90 -0
rememhq-0.1.5/.gitignore
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# ========================
|
|
2
|
+
# Rust
|
|
3
|
+
# ========================
|
|
4
|
+
/target/
|
|
5
|
+
**/*.rs.bk
|
|
6
|
+
# Note: Cargo.lock is tracked intentionally — this workspace produces binary crates
|
|
7
|
+
|
|
8
|
+
# ========================
|
|
9
|
+
# C++ (libremem — v0.2+)
|
|
10
|
+
# ========================
|
|
11
|
+
/build/
|
|
12
|
+
/libremem/build/
|
|
13
|
+
*.o
|
|
14
|
+
*.a
|
|
15
|
+
*.so
|
|
16
|
+
*.dylib
|
|
17
|
+
*.dll
|
|
18
|
+
*.lib
|
|
19
|
+
*.exe
|
|
20
|
+
cmake-build-*/
|
|
21
|
+
CMakeFiles/
|
|
22
|
+
CMakeCache.txt
|
|
23
|
+
|
|
24
|
+
# ========================
|
|
25
|
+
# Python SDK
|
|
26
|
+
# ========================
|
|
27
|
+
__pycache__/
|
|
28
|
+
*.py[cod]
|
|
29
|
+
*$py.class
|
|
30
|
+
*.egg-info/
|
|
31
|
+
*.egg
|
|
32
|
+
dist/
|
|
33
|
+
.eggs/
|
|
34
|
+
.venv/
|
|
35
|
+
venv/
|
|
36
|
+
ENV/
|
|
37
|
+
.pytest_cache/
|
|
38
|
+
.mypy_cache/
|
|
39
|
+
.ruff_cache/
|
|
40
|
+
htmlcov/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
*.cover
|
|
44
|
+
|
|
45
|
+
# ========================
|
|
46
|
+
# TypeScript SDK
|
|
47
|
+
# ========================
|
|
48
|
+
node_modules/
|
|
49
|
+
dist/
|
|
50
|
+
*.tsbuildinfo
|
|
51
|
+
.turbo/
|
|
52
|
+
coverage/
|
|
53
|
+
*.js.map
|
|
54
|
+
|
|
55
|
+
# ========================
|
|
56
|
+
# Models (large binaries)
|
|
57
|
+
# ========================
|
|
58
|
+
models/*.gguf
|
|
59
|
+
models/*.onnx
|
|
60
|
+
models/*.bin
|
|
61
|
+
models/*.safetensors
|
|
62
|
+
|
|
63
|
+
# ========================
|
|
64
|
+
# remem runtime data
|
|
65
|
+
# ========================
|
|
66
|
+
.remem/
|
|
67
|
+
*.db
|
|
68
|
+
*.db-wal
|
|
69
|
+
*.db-shm
|
|
70
|
+
*.index.json
|
|
71
|
+
|
|
72
|
+
# ========================
|
|
73
|
+
# Docker
|
|
74
|
+
# ========================
|
|
75
|
+
.docker/
|
|
76
|
+
|
|
77
|
+
# ========================
|
|
78
|
+
# IDE / Editor
|
|
79
|
+
# ========================
|
|
80
|
+
.vscode/
|
|
81
|
+
!.vscode/settings.json
|
|
82
|
+
!.vscode/extensions.json
|
|
83
|
+
.idea/
|
|
84
|
+
*.swp
|
|
85
|
+
*.swo
|
|
86
|
+
*~
|
|
87
|
+
*.sublime-workspace
|
|
88
|
+
*.sublime-project
|
|
89
|
+
|
|
90
|
+
# ========================
|
|
91
|
+
# OS generated
|
|
92
|
+
# ========================
|
|
93
|
+
.DS_Store
|
|
94
|
+
.DS_Store?
|
|
95
|
+
._*
|
|
96
|
+
.Spotlight-V100
|
|
97
|
+
.Trashes
|
|
98
|
+
ehthumbs.db
|
|
99
|
+
Thumbs.db
|
|
100
|
+
desktop.ini
|
|
101
|
+
|
|
102
|
+
# ========================
|
|
103
|
+
# Environment / Secrets
|
|
104
|
+
# ========================
|
|
105
|
+
.env
|
|
106
|
+
.env.*
|
|
107
|
+
!.env.example
|
|
108
|
+
|
|
109
|
+
# ========================
|
|
110
|
+
# Agent / AI tool configs
|
|
111
|
+
# ========================
|
|
112
|
+
.claude/**/*
|
|
113
|
+
!.claude/config.json
|
|
114
|
+
.codex/
|
|
115
|
+
.gemini/
|
|
116
|
+
.cursor/**/*
|
|
117
|
+
!.cursor/hooks.json
|
|
118
|
+
|
|
119
|
+
# ========================
|
|
120
|
+
# Misc
|
|
121
|
+
# ========================
|
|
122
|
+
*.log
|
|
123
|
+
*.tmp
|
|
124
|
+
*.bak
|
|
125
|
+
*.orig
|
|
126
|
+
|
|
127
|
+
# ========================
|
|
128
|
+
# Swift Package Manager
|
|
129
|
+
# ========================
|
|
130
|
+
bindings/swift/.build/
|
|
131
|
+
bindings/swift/.swiftpm/
|
|
132
|
+
bindings/swift/Package.resolved
|
|
133
|
+
|
|
134
|
+
# ========================
|
|
135
|
+
# React Native / Expo module (bindings/react-native)
|
|
136
|
+
# ========================
|
|
137
|
+
bindings/react-native/build/
|
|
138
|
+
bindings/react-native/ios/Pods/
|
|
139
|
+
bindings/react-native/ios/build/
|
|
140
|
+
bindings/react-native/android/build/
|
|
141
|
+
bindings/react-native/android/.gradle/
|
|
142
|
+
bindings/react-native/android/.cxx/
|
|
143
|
+
bindings/react-native/android/rust/target/
|
|
144
|
+
bindings/react-native/example/node_modules/
|
|
145
|
+
bindings/react-native/example/ios/
|
|
146
|
+
bindings/react-native/example/android/
|
|
147
|
+
bindings/react-native/example/.expo/
|
rememhq-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rememhq
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Reasoning memory layer for AI agents — Python SDK
|
|
5
|
+
Project-URL: Homepage, https://github.com/remem-io/remem
|
|
6
|
+
Project-URL: Repository, https://github.com/remem-io/remem
|
|
7
|
+
Project-URL: Issues, https://github.com/remem-io/remem/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/remem-io/remem/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Documentation, https://github.com/remem-io/remem/tree/main/sdk/python
|
|
10
|
+
Author-email: remem-io <thrive-spectrexq@users.noreply.github.com>
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Provides-Extra: autogen
|
|
26
|
+
Requires-Dist: autogen-agentchat>=0.4; extra == 'autogen'
|
|
27
|
+
Provides-Extra: crewai
|
|
28
|
+
Requires-Dist: crewai>=0.80; extra == 'crewai'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
34
|
+
Provides-Extra: langchain
|
|
35
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
36
|
+
Provides-Extra: llamaindex
|
|
37
|
+
Requires-Dist: llama-index-core>=0.11; extra == 'llamaindex'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# remem Python SDK
|
|
41
|
+
|
|
42
|
+
This is the Python SDK for remem, providing a typed interface over the REST API.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install rememhq
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from rememhq import Memory
|
|
54
|
+
|
|
55
|
+
memory = Memory(base_url="http://localhost:7474")
|
|
56
|
+
|
|
57
|
+
# Store a memory
|
|
58
|
+
await memory.store("User prefers Python", tags=["language"])
|
|
59
|
+
|
|
60
|
+
# Recall memories
|
|
61
|
+
results = await memory.recall("what language does the user prefer?")
|
|
62
|
+
for result in results:
|
|
63
|
+
print(f"Content: {result.content}")
|
|
64
|
+
print(f"Reasoning: {result.reasoning}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
### Prerequisites
|
|
70
|
+
|
|
71
|
+
- **Python** 3.11+
|
|
72
|
+
- **pip** or **uv**
|
|
73
|
+
|
|
74
|
+
### Setup
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cd sdk/python
|
|
78
|
+
|
|
79
|
+
# Install in development mode with dev dependencies
|
|
80
|
+
pip install -e ".[dev]"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Testing
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Run all tests
|
|
87
|
+
pytest tests/ -v
|
|
88
|
+
|
|
89
|
+
# Run with coverage
|
|
90
|
+
pytest tests/ --cov=rememhq
|
|
91
|
+
|
|
92
|
+
# Run specific test
|
|
93
|
+
pytest tests/test_memory.py -v
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Code Quality
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Format code
|
|
100
|
+
black rememhq/ tests/
|
|
101
|
+
|
|
102
|
+
# Lint
|
|
103
|
+
ruff check rememhq/ tests/
|
|
104
|
+
|
|
105
|
+
# Type checking
|
|
106
|
+
mypy rememhq/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Building
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Build distribution
|
|
113
|
+
python -m build
|
|
114
|
+
|
|
115
|
+
# Install locally from build
|
|
116
|
+
pip install dist/rememhq-*.whl
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Running the API Server
|
|
120
|
+
|
|
121
|
+
Before using the SDK, start the remem API server from the repository root:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cargo run -p rememhq-api -- --project default
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The API will be available at `http://localhost:7474` by default.
|
|
128
|
+
|
|
129
|
+
## API Reference
|
|
130
|
+
|
|
131
|
+
### Memory
|
|
132
|
+
|
|
133
|
+
#### `store(content: str, tags: Optional[List[str]] = None, importance: Optional[float] = None) -> StoreResponse`
|
|
134
|
+
|
|
135
|
+
Store a memory with optional tags and importance score.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
response = await memory.store(
|
|
139
|
+
"Production database is PostgreSQL 15",
|
|
140
|
+
tags=["infra", "database"],
|
|
141
|
+
importance=0.95
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `recall(query: str, limit: int = 8) -> List[RecallResult]`
|
|
146
|
+
|
|
147
|
+
Retrieve memories most relevant to the query, with reasoning traces.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
results = await memory.recall("what database are we using?", limit=8)
|
|
151
|
+
for result in results:
|
|
152
|
+
print(f"Content: {result.content}")
|
|
153
|
+
print(f"Score: {result.score}")
|
|
154
|
+
print(f"Reasoning: {result.reasoning}")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `search(query: str, limit: int = 10) -> List[SearchResult]`
|
|
158
|
+
|
|
159
|
+
Full-text search over all memories.
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
results = await memory.search("deployment", limit=10)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### `update(memory_id: str, content: str) -> UpdateResponse`
|
|
166
|
+
|
|
167
|
+
Update an existing memory's content.
|
|
168
|
+
|
|
169
|
+
#### `forget(memory_id: str) -> ForgetResponse`
|
|
170
|
+
|
|
171
|
+
Delete a memory.
|
|
172
|
+
|
|
173
|
+
#### `consolidate(session_id: str) -> ConsolidateResponse`
|
|
174
|
+
|
|
175
|
+
Consolidate session logs into durable facts.
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
Configure the SDK via constructor parameters or environment variables:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from rememhq import Memory
|
|
183
|
+
|
|
184
|
+
memory = Memory(
|
|
185
|
+
base_url="http://localhost:7474",
|
|
186
|
+
project="my-agent",
|
|
187
|
+
reasoning_model="claude-sonnet-4-6",
|
|
188
|
+
timeout=30.0
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Environment variables:**
|
|
193
|
+
- `REMEM_API_URL` — API server URL (default: `http://localhost:7474`)
|
|
194
|
+
- `REMEM_PROJECT` — Project name (default: `default`)
|
|
195
|
+
- `REMEM_REASONING_MODEL` — Reasoning model (default: `claude-sonnet-4-6`)
|
|
196
|
+
- `REMEM_TIMEOUT` — Request timeout in seconds (default: `30`)
|
|
197
|
+
|
|
198
|
+
## Examples
|
|
199
|
+
|
|
200
|
+
Check the `examples/` directory for complete working scripts.
|
|
201
|
+
|
|
202
|
+
## Contributing
|
|
203
|
+
|
|
204
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines on contributing to the Python SDK.
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
Apache License 2.0 — See [LICENSE](../../LICENSE) for details.
|
rememhq-0.1.5/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# remem Python SDK
|
|
2
|
+
|
|
3
|
+
This is the Python SDK for remem, providing a typed interface over the REST API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install rememhq
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from rememhq import Memory
|
|
15
|
+
|
|
16
|
+
memory = Memory(base_url="http://localhost:7474")
|
|
17
|
+
|
|
18
|
+
# Store a memory
|
|
19
|
+
await memory.store("User prefers Python", tags=["language"])
|
|
20
|
+
|
|
21
|
+
# Recall memories
|
|
22
|
+
results = await memory.recall("what language does the user prefer?")
|
|
23
|
+
for result in results:
|
|
24
|
+
print(f"Content: {result.content}")
|
|
25
|
+
print(f"Reasoning: {result.reasoning}")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
### Prerequisites
|
|
31
|
+
|
|
32
|
+
- **Python** 3.11+
|
|
33
|
+
- **pip** or **uv**
|
|
34
|
+
|
|
35
|
+
### Setup
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd sdk/python
|
|
39
|
+
|
|
40
|
+
# Install in development mode with dev dependencies
|
|
41
|
+
pip install -e ".[dev]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Testing
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Run all tests
|
|
48
|
+
pytest tests/ -v
|
|
49
|
+
|
|
50
|
+
# Run with coverage
|
|
51
|
+
pytest tests/ --cov=rememhq
|
|
52
|
+
|
|
53
|
+
# Run specific test
|
|
54
|
+
pytest tests/test_memory.py -v
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Code Quality
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Format code
|
|
61
|
+
black rememhq/ tests/
|
|
62
|
+
|
|
63
|
+
# Lint
|
|
64
|
+
ruff check rememhq/ tests/
|
|
65
|
+
|
|
66
|
+
# Type checking
|
|
67
|
+
mypy rememhq/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Building
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Build distribution
|
|
74
|
+
python -m build
|
|
75
|
+
|
|
76
|
+
# Install locally from build
|
|
77
|
+
pip install dist/rememhq-*.whl
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Running the API Server
|
|
81
|
+
|
|
82
|
+
Before using the SDK, start the remem API server from the repository root:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cargo run -p rememhq-api -- --project default
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The API will be available at `http://localhost:7474` by default.
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### Memory
|
|
93
|
+
|
|
94
|
+
#### `store(content: str, tags: Optional[List[str]] = None, importance: Optional[float] = None) -> StoreResponse`
|
|
95
|
+
|
|
96
|
+
Store a memory with optional tags and importance score.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
response = await memory.store(
|
|
100
|
+
"Production database is PostgreSQL 15",
|
|
101
|
+
tags=["infra", "database"],
|
|
102
|
+
importance=0.95
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `recall(query: str, limit: int = 8) -> List[RecallResult]`
|
|
107
|
+
|
|
108
|
+
Retrieve memories most relevant to the query, with reasoning traces.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
results = await memory.recall("what database are we using?", limit=8)
|
|
112
|
+
for result in results:
|
|
113
|
+
print(f"Content: {result.content}")
|
|
114
|
+
print(f"Score: {result.score}")
|
|
115
|
+
print(f"Reasoning: {result.reasoning}")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### `search(query: str, limit: int = 10) -> List[SearchResult]`
|
|
119
|
+
|
|
120
|
+
Full-text search over all memories.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
results = await memory.search("deployment", limit=10)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `update(memory_id: str, content: str) -> UpdateResponse`
|
|
127
|
+
|
|
128
|
+
Update an existing memory's content.
|
|
129
|
+
|
|
130
|
+
#### `forget(memory_id: str) -> ForgetResponse`
|
|
131
|
+
|
|
132
|
+
Delete a memory.
|
|
133
|
+
|
|
134
|
+
#### `consolidate(session_id: str) -> ConsolidateResponse`
|
|
135
|
+
|
|
136
|
+
Consolidate session logs into durable facts.
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
Configure the SDK via constructor parameters or environment variables:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from rememhq import Memory
|
|
144
|
+
|
|
145
|
+
memory = Memory(
|
|
146
|
+
base_url="http://localhost:7474",
|
|
147
|
+
project="my-agent",
|
|
148
|
+
reasoning_model="claude-sonnet-4-6",
|
|
149
|
+
timeout=30.0
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Environment variables:**
|
|
154
|
+
- `REMEM_API_URL` — API server URL (default: `http://localhost:7474`)
|
|
155
|
+
- `REMEM_PROJECT` — Project name (default: `default`)
|
|
156
|
+
- `REMEM_REASONING_MODEL` — Reasoning model (default: `claude-sonnet-4-6`)
|
|
157
|
+
- `REMEM_TIMEOUT` — Request timeout in seconds (default: `30`)
|
|
158
|
+
|
|
159
|
+
## Examples
|
|
160
|
+
|
|
161
|
+
Check the `examples/` directory for complete working scripts.
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
|
|
165
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines on contributing to the Python SDK.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
Apache License 2.0 — See [LICENSE](../../LICENSE) for details.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rememhq"
|
|
3
|
+
version = "0.1.5"
|
|
4
|
+
description = "Reasoning memory layer for AI agents — Python SDK"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "remem-io", email = "thrive-spectrexq@users.noreply.github.com" },
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: Apache Software License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
"Topic :: Database",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.27",
|
|
25
|
+
"pydantic>=2.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/remem-io/remem"
|
|
30
|
+
Repository = "https://github.com/remem-io/remem"
|
|
31
|
+
Issues = "https://github.com/remem-io/remem/issues"
|
|
32
|
+
Changelog = "https://github.com/remem-io/remem/blob/main/CHANGELOG.md"
|
|
33
|
+
Documentation = "https://github.com/remem-io/remem/tree/main/sdk/python"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.0",
|
|
38
|
+
"pytest-asyncio>=0.23",
|
|
39
|
+
"pytest-cov>=5.0",
|
|
40
|
+
"respx>=0.21",
|
|
41
|
+
]
|
|
42
|
+
langchain = ["langchain-core>=0.3"]
|
|
43
|
+
llamaindex = ["llama-index-core>=0.11"]
|
|
44
|
+
autogen = ["autogen-agentchat>=0.4"]
|
|
45
|
+
crewai = ["crewai>=0.80"]
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["hatchling"]
|
|
49
|
+
build-backend = "hatchling.build"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.sdist]
|
|
52
|
+
include = ["rememhq/"]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["rememhq"]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""remem — reasoning memory layer for AI agents."""
|
|
2
|
+
|
|
3
|
+
from rememhq.client import Memory
|
|
4
|
+
from rememhq.models import MemoryResult, ConsolidationReport, StoreResponse
|
|
5
|
+
|
|
6
|
+
__all__ = ["Memory", "MemoryResult", "ConsolidationReport", "StoreResponse"]
|
|
7
|
+
__version__ = "0.1.5"
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Async-first Python client for the remem REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from rememhq.config import RememConfig
|
|
11
|
+
from rememhq.models import (
|
|
12
|
+
ConsolidationReport,
|
|
13
|
+
CompactResponse,
|
|
14
|
+
ForgetMode,
|
|
15
|
+
MemoryResult,
|
|
16
|
+
MemoryType,
|
|
17
|
+
StoreResponse,
|
|
18
|
+
MemoryStoreRecord,
|
|
19
|
+
MemoryVersionRecord,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Memory:
|
|
24
|
+
"""Async client for remem — reasoning memory layer for AI agents.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
>>> m = Memory(project="my-agent", reasoning_model="claude-sonnet-4-5")
|
|
28
|
+
>>> await m.store("User prefers dark mode", tags=["preferences"])
|
|
29
|
+
>>> results = await m.recall("what are the user's preferences?")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
project: str = "default",
|
|
35
|
+
reasoning_model: str = "claude-sonnet-4-5",
|
|
36
|
+
scoring_model: str = "claude-haiku-4-5",
|
|
37
|
+
base_url: str | None = None,
|
|
38
|
+
api_key: str | None = None,
|
|
39
|
+
timeout: float = 30.0,
|
|
40
|
+
):
|
|
41
|
+
config = RememConfig(
|
|
42
|
+
project=project,
|
|
43
|
+
reasoning_model=reasoning_model,
|
|
44
|
+
scoring_model=scoring_model,
|
|
45
|
+
timeout=timeout,
|
|
46
|
+
)
|
|
47
|
+
if base_url:
|
|
48
|
+
config.base_url = base_url
|
|
49
|
+
if api_key:
|
|
50
|
+
config.api_key = api_key
|
|
51
|
+
|
|
52
|
+
self._config = config
|
|
53
|
+
headers: dict[str, str] = {"Content-Type": "application/json"}
|
|
54
|
+
if config.api_key:
|
|
55
|
+
headers["Authorization"] = f"Bearer {config.api_key}"
|
|
56
|
+
|
|
57
|
+
self._client = httpx.AsyncClient(
|
|
58
|
+
base_url=config.base_url,
|
|
59
|
+
headers=headers,
|
|
60
|
+
timeout=config.timeout,
|
|
61
|
+
)
|
|
62
|
+
self.stores = MemoryStoresClient(self._client)
|
|
63
|
+
|
|
64
|
+
async def store(
|
|
65
|
+
self,
|
|
66
|
+
content: str,
|
|
67
|
+
*,
|
|
68
|
+
tags: list[str] | None = None,
|
|
69
|
+
importance: float | None = None,
|
|
70
|
+
ttl_days: int | None = None,
|
|
71
|
+
memory_type: MemoryType = MemoryType.FACT,
|
|
72
|
+
) -> StoreResponse:
|
|
73
|
+
"""Store a new memory with automatic LLM importance scoring."""
|
|
74
|
+
payload: dict = {
|
|
75
|
+
"content": content,
|
|
76
|
+
"tags": tags or [],
|
|
77
|
+
"memory_type": memory_type.value,
|
|
78
|
+
}
|
|
79
|
+
if importance is not None:
|
|
80
|
+
payload["importance"] = importance
|
|
81
|
+
if ttl_days is not None:
|
|
82
|
+
payload["ttl_days"] = ttl_days
|
|
83
|
+
|
|
84
|
+
resp = await self._client.post("/v1/memories", json=payload)
|
|
85
|
+
resp.raise_for_status()
|
|
86
|
+
return StoreResponse.model_validate(resp.json())
|
|
87
|
+
|
|
88
|
+
async def recall(
|
|
89
|
+
self,
|
|
90
|
+
query: str,
|
|
91
|
+
*,
|
|
92
|
+
limit: int = 8,
|
|
93
|
+
filter_tags: list[str] | None = None,
|
|
94
|
+
since: datetime | None = None,
|
|
95
|
+
memory_type: MemoryType | None = None,
|
|
96
|
+
) -> list[MemoryResult]:
|
|
97
|
+
"""Guided recall — LLM re-ranks candidates for relevance."""
|
|
98
|
+
params: dict = {"q": query, "limit": limit}
|
|
99
|
+
if filter_tags:
|
|
100
|
+
params["filter_tags"] = ",".join(filter_tags)
|
|
101
|
+
if since:
|
|
102
|
+
params["since"] = since.isoformat()
|
|
103
|
+
if memory_type:
|
|
104
|
+
params["memory_type"] = memory_type.value
|
|
105
|
+
|
|
106
|
+
resp = await self._client.get("/v1/memories/recall", params=params)
|
|
107
|
+
resp.raise_for_status()
|
|
108
|
+
return [MemoryResult.model_validate(r) for r in resp.json()]
|
|
109
|
+
|
|
110
|
+
async def search(
|
|
111
|
+
self,
|
|
112
|
+
query: str,
|
|
113
|
+
*,
|
|
114
|
+
limit: int = 20,
|
|
115
|
+
filter_tags: list[str] | None = None,
|
|
116
|
+
) -> list[MemoryResult]:
|
|
117
|
+
"""Hybrid vector + keyword search without LLM re-ranking."""
|
|
118
|
+
params: dict = {"q": query, "limit": limit}
|
|
119
|
+
if filter_tags:
|
|
120
|
+
params["filter_tags"] = ",".join(filter_tags)
|
|
121
|
+
|
|
122
|
+
resp = await self._client.get("/v1/memories/search", params=params)
|
|
123
|
+
resp.raise_for_status()
|
|
124
|
+
return [MemoryResult.model_validate(r) for r in resp.json()]
|
|
125
|
+
|
|
126
|
+
async def update(
|
|
127
|
+
self,
|
|
128
|
+
id: UUID | str,
|
|
129
|
+
*,
|
|
130
|
+
content: str | None = None,
|
|
131
|
+
importance: float | None = None,
|
|
132
|
+
tags: list[str] | None = None,
|
|
133
|
+
) -> dict:
|
|
134
|
+
"""Update an existing memory."""
|
|
135
|
+
payload: dict = {}
|
|
136
|
+
if content is not None:
|
|
137
|
+
payload["content"] = content
|
|
138
|
+
if importance is not None:
|
|
139
|
+
payload["importance"] = importance
|
|
140
|
+
if tags is not None:
|
|
141
|
+
payload["tags"] = tags
|
|
142
|
+
|
|
143
|
+
resp = await self._client.patch(f"/v1/memories/{id}", json=payload)
|
|
144
|
+
resp.raise_for_status()
|
|
145
|
+
return resp.json()
|
|
146
|
+
|
|
147
|
+
async def forget(
|
|
148
|
+
self,
|
|
149
|
+
id: UUID | str,
|
|
150
|
+
*,
|
|
151
|
+
mode: ForgetMode = ForgetMode.DELETE,
|
|
152
|
+
) -> dict:
|
|
153
|
+
"""Delete, decay, or archive a memory."""
|
|
154
|
+
resp = await self._client.delete(
|
|
155
|
+
f"/v1/memories/{id}", params={"mode": mode.value}
|
|
156
|
+
)
|
|
157
|
+
resp.raise_for_status()
|
|
158
|
+
return resp.json()
|
|
159
|
+
|
|
160
|
+
async def consolidate(
|
|
161
|
+
self,
|
|
162
|
+
session_id: str,
|
|
163
|
+
*,
|
|
164
|
+
model: str | None = None,
|
|
165
|
+
) -> ConsolidationReport:
|
|
166
|
+
"""Trigger consolidation over a session's working memory."""
|
|
167
|
+
payload: dict = {}
|
|
168
|
+
if model:
|
|
169
|
+
payload["model"] = model
|
|
170
|
+
|
|
171
|
+
resp = await self._client.post(
|
|
172
|
+
f"/v1/sessions/{session_id}/consolidate", json=payload
|
|
173
|
+
)
|
|
174
|
+
resp.raise_for_status()
|
|
175
|
+
return ConsolidationReport.model_validate(resp.json())
|
|
176
|
+
|
|
177
|
+
async def decay(self, factor: float = 0.9) -> dict:
|
|
178
|
+
"""Apply importance-weighted decay to all active memories."""
|
|
179
|
+
resp = await self._client.post("/v1/memories/decay", json={"factor": factor})
|
|
180
|
+
resp.raise_for_status()
|
|
181
|
+
return resp.json()
|
|
182
|
+
|
|
183
|
+
async def compact_context(
|
|
184
|
+
self,
|
|
185
|
+
conversation_text: str,
|
|
186
|
+
*,
|
|
187
|
+
focus_areas: list[str] | None = None,
|
|
188
|
+
) -> CompactResponse:
|
|
189
|
+
"""Compact a conversation trace to save context window tokens."""
|
|
190
|
+
payload: dict = {"conversation_text": conversation_text}
|
|
191
|
+
if focus_areas is not None:
|
|
192
|
+
payload["focus_areas"] = focus_areas
|
|
193
|
+
|
|
194
|
+
resp = await self._client.post("/v1/memories/compact", json=payload)
|
|
195
|
+
resp.raise_for_status()
|
|
196
|
+
return CompactResponse.model_validate(resp.json())
|
|
197
|
+
|
|
198
|
+
async def close(self) -> None:
|
|
199
|
+
"""Close the HTTP client."""
|
|
200
|
+
await self._client.aclose()
|
|
201
|
+
|
|
202
|
+
async def __aenter__(self) -> "Memory":
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
async def __aexit__(self, *args) -> None:
|
|
206
|
+
await self.close()
|
|
207
|
+
|
|
208
|
+
class StoreMemoriesClient:
|
|
209
|
+
def __init__(self, client: httpx.AsyncClient):
|
|
210
|
+
self._client = client
|
|
211
|
+
|
|
212
|
+
async def list(self, store_id: str | UUID) -> list[MemoryResult]:
|
|
213
|
+
resp = await self._client.get(f"/v1/memory_stores/{store_id}/memories")
|
|
214
|
+
resp.raise_for_status()
|
|
215
|
+
return [MemoryResult.model_validate(r) for r in resp.json()]
|
|
216
|
+
|
|
217
|
+
async def create(self, store_id: str | UUID, path: str, content: str) -> MemoryResult:
|
|
218
|
+
resp = await self._client.post(
|
|
219
|
+
f"/v1/memory_stores/{store_id}/memories",
|
|
220
|
+
json={"path": path, "content": content}
|
|
221
|
+
)
|
|
222
|
+
resp.raise_for_status()
|
|
223
|
+
return MemoryResult.model_validate(resp.json())
|
|
224
|
+
|
|
225
|
+
async def get(self, store_id: str | UUID, path_or_id: str | UUID) -> MemoryResult:
|
|
226
|
+
resp = await self._client.get(f"/v1/memory_stores/{store_id}/memories/{path_or_id}")
|
|
227
|
+
resp.raise_for_status()
|
|
228
|
+
return MemoryResult.model_validate(resp.json())
|
|
229
|
+
|
|
230
|
+
async def update(self, store_id: str | UUID, path_or_id: str | UUID, content: str) -> MemoryResult:
|
|
231
|
+
resp = await self._client.post(
|
|
232
|
+
f"/v1/memory_stores/{store_id}/memories/{path_or_id}",
|
|
233
|
+
json={"content": content}
|
|
234
|
+
)
|
|
235
|
+
resp.raise_for_status()
|
|
236
|
+
return MemoryResult.model_validate(resp.json())
|
|
237
|
+
|
|
238
|
+
async def list_versions(self, store_id: str | UUID, path_or_id: str | UUID) -> list[MemoryVersionRecord]:
|
|
239
|
+
resp = await self._client.get(f"/v1/memory_stores/{store_id}/memories/{path_or_id}/versions")
|
|
240
|
+
resp.raise_for_status()
|
|
241
|
+
return [MemoryVersionRecord.model_validate(r) for r in resp.json()]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class MemoryStoresClient:
|
|
245
|
+
def __init__(self, client: httpx.AsyncClient):
|
|
246
|
+
self._client = client
|
|
247
|
+
self.memories = StoreMemoriesClient(client)
|
|
248
|
+
|
|
249
|
+
async def create(self, name: str, description: str | None = None) -> MemoryStoreRecord:
|
|
250
|
+
payload = {"name": name}
|
|
251
|
+
if description:
|
|
252
|
+
payload["description"] = description
|
|
253
|
+
resp = await self._client.post("/v1/memory_stores", json=payload)
|
|
254
|
+
resp.raise_for_status()
|
|
255
|
+
return MemoryStoreRecord.model_validate(resp.json())
|
|
256
|
+
|
|
257
|
+
async def list(self) -> list[MemoryStoreRecord]:
|
|
258
|
+
resp = await self._client.get("/v1/memory_stores")
|
|
259
|
+
resp.raise_for_status()
|
|
260
|
+
return [MemoryStoreRecord.model_validate(r) for r in resp.json()]
|
|
261
|
+
|
|
262
|
+
async def get(self, store_id: str | UUID) -> MemoryStoreRecord:
|
|
263
|
+
resp = await self._client.get(f"/v1/memory_stores/{store_id}")
|
|
264
|
+
resp.raise_for_status()
|
|
265
|
+
return MemoryStoreRecord.model_validate(resp.json())
|
|
266
|
+
|
|
267
|
+
async def archive(self, store_id: str | UUID) -> None:
|
|
268
|
+
resp = await self._client.post(f"/v1/memory_stores/{store_id}/archive")
|
|
269
|
+
resp.raise_for_status()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Configuration for the remem Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class RememConfig:
|
|
11
|
+
"""Configuration for connecting to a remem server."""
|
|
12
|
+
|
|
13
|
+
base_url: str = field(
|
|
14
|
+
default_factory=lambda: os.environ.get(
|
|
15
|
+
"REMEM_BASE_URL", "http://localhost:7474"
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
api_key: str | None = field(default_factory=lambda: os.environ.get("REMEM_API_KEY"))
|
|
19
|
+
project: str = field(
|
|
20
|
+
default_factory=lambda: os.environ.get("REMEM_PROJECT", "default")
|
|
21
|
+
)
|
|
22
|
+
reasoning_model: str = field(
|
|
23
|
+
default_factory=lambda: os.environ.get(
|
|
24
|
+
"REMEM_REASONING_MODEL", "claude-sonnet-4-5"
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
scoring_model: str = field(
|
|
28
|
+
default_factory=lambda: os.environ.get(
|
|
29
|
+
"REMEM_SCORING_MODEL", "claude-haiku-4-5"
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
timeout: float = 30.0
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Pydantic v2 models for the remem REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryType(str, Enum):
|
|
14
|
+
FACT = "fact"
|
|
15
|
+
PROCEDURE = "procedure"
|
|
16
|
+
PREFERENCE = "preference"
|
|
17
|
+
DECISION = "decision"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ForgetMode(str, Enum):
|
|
21
|
+
DELETE = "delete"
|
|
22
|
+
DECAY = "decay"
|
|
23
|
+
ARCHIVE = "archive"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StoreResponse(BaseModel):
|
|
27
|
+
id: UUID
|
|
28
|
+
importance: float
|
|
29
|
+
tags: list[str]
|
|
30
|
+
created_at: datetime
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CompactResponse(BaseModel):
|
|
34
|
+
compressed_context: str
|
|
35
|
+
original_length: int
|
|
36
|
+
compressed_length: int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MemoryResult(BaseModel):
|
|
40
|
+
id: UUID
|
|
41
|
+
content: str
|
|
42
|
+
importance: float
|
|
43
|
+
tags: list[str]
|
|
44
|
+
memory_type: MemoryType
|
|
45
|
+
created_at: datetime
|
|
46
|
+
source_session: Optional[str] = None
|
|
47
|
+
similarity: float = 0.0
|
|
48
|
+
decay_score: float = 1.0
|
|
49
|
+
reasoning: Optional[str] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ConsolidationReport(BaseModel):
|
|
53
|
+
session_id: str
|
|
54
|
+
new_facts: int
|
|
55
|
+
updated_facts: int
|
|
56
|
+
contradictions: list[Contradiction] = Field(default_factory=list)
|
|
57
|
+
knowledge_graph_updates: list[KnowledgeGraphUpdate] = Field(default_factory=list)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Contradiction(BaseModel):
|
|
61
|
+
existing_memory_id: UUID
|
|
62
|
+
new_content: str
|
|
63
|
+
existing_content: str
|
|
64
|
+
explanation: str
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class KnowledgeGraphUpdate(BaseModel):
|
|
68
|
+
subject: str
|
|
69
|
+
predicate: str
|
|
70
|
+
object: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Fix forward references
|
|
74
|
+
ConsolidationReport.model_rebuild()
|
|
75
|
+
|
|
76
|
+
class MemoryStoreRecord(BaseModel):
|
|
77
|
+
id: str
|
|
78
|
+
name: str
|
|
79
|
+
description: Optional[str] = None
|
|
80
|
+
created_at: datetime
|
|
81
|
+
archived_at: Optional[datetime] = None
|
|
82
|
+
|
|
83
|
+
class MemoryVersionRecord(BaseModel):
|
|
84
|
+
id: str
|
|
85
|
+
store_id: str
|
|
86
|
+
memory_id: UUID
|
|
87
|
+
operation: str
|
|
88
|
+
content: str
|
|
89
|
+
content_sha256: str
|
|
90
|
+
created_at: datetime
|