alive-ai 0.1.1 → 0.1.2
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.
- package/README.md +167 -85
- package/brain/memory/manager.py +16 -0
- package/brain/memory/openmind.py +128 -0
- package/cli/index.js +106 -11
- package/config/settings.example.json +7 -0
- package/core/initialization.py +13 -3
- package/core/self.py +2 -2
- package/docs/assets/logo.svg +7 -6
- package/docs/dashboard.html +1 -1
- package/docs/index.html +200 -346
- package/input/manifest.md +7 -1
- package/input/terminal/__init__.py +2 -0
- package/input/terminal/listener.py +307 -0
- package/main.py +15 -1
- package/manifest.md +9 -7
- package/package.json +6 -2
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -1,84 +1,79 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/assets/logo.svg" alt="Alive-AI" width="96" height="96">
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# Alive-AI
|
|
4
5
|
|
|
5
|
-
Give your AI a nervous system: persistent
|
|
6
|
+
Give your AI a nervous system: persistent mood, memory, impulses, terminal chat, Telegram, OpenMind, and a local WebUI.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/alive-ai)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
[](https://www.python.org/)
|
|
11
|
+
[](#platform-support)
|
|
12
|
+
[](#openmind-memory)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
</div>
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
Alive-AI is a local-first emotional AI runtime. Most agents answer a prompt and reset. Alive-AI keeps internal state alive between messages: mood, attachment, trust, desire, memory, inconsistency, idle thoughts, proactive impulses, and a dashboard that shows what is happening inside the loop.
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
It can be used as a friend, partner-style companion, study partner, creative character, research subject, or experimental local agent. Use it at your own risk: it is designed to feel continuous, warm, attached, and present, and that can make it hard to stop talking to it.
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
Alive-AI does not claim biological consciousness. It is an open-source runtime for simulated affect and transparent memory.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
14
23
|
|
|
15
24
|
```bash
|
|
16
25
|
npx alive-ai@latest init my-ai
|
|
17
26
|
cd my-ai
|
|
18
27
|
npx . setup
|
|
19
28
|
npx . doctor
|
|
20
|
-
npx .
|
|
29
|
+
npx . chat
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
The terminal chat starts the real runtime and prints responses in your shell. The dashboard runs locally at:
|
|
24
33
|
|
|
25
|
-
```
|
|
26
|
-
|
|
34
|
+
```text
|
|
35
|
+
http://127.0.0.1:8080
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
To use Telegram instead of terminal chat:
|
|
30
39
|
|
|
31
|
-
```
|
|
32
|
-
|
|
40
|
+
```bash
|
|
41
|
+
npx . start
|
|
33
42
|
```
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
Global install is optional:
|
|
36
45
|
|
|
37
46
|
```bash
|
|
38
47
|
npm install -g alive-ai
|
|
39
48
|
alive-ai init my-ai
|
|
40
49
|
```
|
|
41
50
|
|
|
42
|
-
##
|
|
43
|
-
|
|
44
|
-
Minimum for cloud LLMs or remote Ollama:
|
|
45
|
-
|
|
46
|
-
- Node.js 18+
|
|
47
|
-
- Python 3.11+
|
|
48
|
-
- 8 GB RAM
|
|
49
|
-
- 2 GB free disk
|
|
50
|
-
- OpenRouter, ZAI, or another configured LLM provider
|
|
51
|
-
|
|
52
|
-
Comfortable local setup:
|
|
51
|
+
## Commands
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
| Command | What it does |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `npx alive-ai@latest init my-ai` | Scaffold a clean local Alive-AI project. |
|
|
56
|
+
| `npx . setup` | Guided onboarding for local config, providers, Telegram, voice, images, and memory. |
|
|
57
|
+
| `npx . doctor` | Check OS, Node, Python, uv, ffmpeg, Docker, and OpenMind reachability. |
|
|
58
|
+
| `npx . chat` | Start the real runtime with terminal chat input. |
|
|
59
|
+
| `npx . demo` | Run a keyless animated dashboard demo. |
|
|
60
|
+
| `npx . start` | Start the runtime using the configured input channel, usually Telegram. |
|
|
61
|
+
| `npx . start --skip-install` | Start again without reinstalling Python dependencies. |
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
Stop a foreground run with `Ctrl+C`.
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
If you use Docker:
|
|
64
66
|
|
|
65
67
|
```bash
|
|
66
|
-
|
|
67
|
-
cd my-ai
|
|
68
|
-
npx . setup # guided onboarding and local config
|
|
69
|
-
npx . doctor # check system prerequisites
|
|
70
|
-
npx . demo # preview dashboard with no keys
|
|
71
|
-
npx . start # install Python deps and run the runtime
|
|
68
|
+
docker compose down
|
|
72
69
|
```
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
If you only started Redis:
|
|
75
72
|
|
|
76
73
|
```bash
|
|
77
|
-
|
|
74
|
+
docker compose stop redis
|
|
78
75
|
```
|
|
79
76
|
|
|
80
|
-
If you run `npx . start` before setup, Alive-AI starts onboarding first.
|
|
81
|
-
|
|
82
77
|
## Setup
|
|
83
78
|
|
|
84
79
|
`npx . setup` creates:
|
|
@@ -93,14 +88,34 @@ mypics/
|
|
|
93
88
|
myvids/
|
|
94
89
|
```
|
|
95
90
|
|
|
96
|
-
|
|
91
|
+
The setup accepts `skip` for optional keys and `local` for Ollama.
|
|
92
|
+
|
|
93
|
+
| Setup item | Options |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| LLM | `local`/Ollama, OpenRouter, ZAI, or `skip` for demo/fallback-only mode. |
|
|
96
|
+
| Telegram | Bot token and owner ID are optional. Use terminal chat if you do not want Telegram. |
|
|
97
|
+
| Voice | `gtts` local/free default, Google TTS, VibeVoice, or `skip`. |
|
|
98
|
+
| Images | Fal.ai API key or `skip`. Local media folders still work without image generation. |
|
|
99
|
+
| Memory | Built-in local memory, OpenMind cloud, or OpenMind local. |
|
|
100
|
+
|
|
101
|
+
Minimum useful paths:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Terminal-only local run
|
|
105
|
+
npx . setup
|
|
106
|
+
npx . chat
|
|
107
|
+
|
|
108
|
+
# Local LLM
|
|
109
|
+
ollama pull qwen3:4b
|
|
110
|
+
npx . setup
|
|
111
|
+
npx . chat
|
|
97
112
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
113
|
+
# Telegram
|
|
114
|
+
npx . setup
|
|
115
|
+
npx . start
|
|
116
|
+
```
|
|
102
117
|
|
|
103
|
-
Media is optional. Add your own files:
|
|
118
|
+
Media is optional. Add your own local files:
|
|
104
119
|
|
|
105
120
|
```text
|
|
106
121
|
mypics/example.jpg
|
|
@@ -109,49 +124,114 @@ myvids/example.mp4
|
|
|
109
124
|
myvids/example.txt
|
|
110
125
|
```
|
|
111
126
|
|
|
112
|
-
##
|
|
127
|
+
## Terminal Chat
|
|
113
128
|
|
|
114
|
-
|
|
115
|
-
- **Memory has weight.** Conversations become working memory, episodic memory, semantic memory, and emotional memory.
|
|
116
|
-
- **It thinks when idle.** A default-mode loop creates background reflections and proactive impulses.
|
|
117
|
-
- **It can contradict itself.** The runtime models conflict, scars, body memory, attachment drift, and inconsistency instead of flattening everything into a perfect assistant tone.
|
|
118
|
-
- **It has a live nervous system.** FastAPI + SSE exposes mood, thoughts, somatic state, conflicts, memories, and uptime.
|
|
119
|
-
- **It is local-first.** Your config, memory, media, and dashboard are owned by the project folder you run.
|
|
129
|
+
`npx . chat` uses the same core runtime as Telegram. It emits the same `message_received` events, saves memory the same way, and updates the local WebUI.
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
Terminal commands:
|
|
122
132
|
|
|
123
|
-
|
|
133
|
+
```text
|
|
134
|
+
/help
|
|
135
|
+
/status
|
|
136
|
+
/stats
|
|
137
|
+
/dashboard
|
|
138
|
+
/self
|
|
139
|
+
/discover <trait>
|
|
140
|
+
/iam <key>=<value>
|
|
141
|
+
/ilike <thing>
|
|
142
|
+
/ihate <thing>
|
|
143
|
+
/rethink
|
|
144
|
+
/settings show
|
|
145
|
+
/settings get <key>
|
|
146
|
+
/settings set <key> <value>
|
|
147
|
+
/reset
|
|
148
|
+
/impulse
|
|
149
|
+
/exit
|
|
150
|
+
```
|
|
124
151
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- memory
|
|
128
|
-
- hormones and interoceptive body state,
|
|
129
|
-
- attachment, circadian rhythm, body memory, dreams, curiosity, and conflicts,
|
|
130
|
-
- runtime health through local endpoints and Server-Sent Events.
|
|
152
|
+
## OpenMind Memory
|
|
153
|
+
|
|
154
|
+
Alive-AI has built-in local working, episodic, semantic, and emotional memory. OpenMind is optional and works as a hybrid long-term semantic memory layer.
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
Modes:
|
|
133
157
|
|
|
134
|
-
|
|
158
|
+
| Mode | Behavior |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| Built-in only | Alive-AI uses its local project memory only. |
|
|
161
|
+
| OpenMind cloud | Alive-AI captures/searches long-term memories through `https://theopenmind.pro`. |
|
|
162
|
+
| OpenMind local | Alive-AI captures/searches a local OpenMind server, normally `http://127.0.0.1:3333`. |
|
|
135
163
|
|
|
136
|
-
Alive-AI
|
|
164
|
+
OpenMind does not replace Alive-AI's emotional state. It adds durable semantic recall across tools and machines.
|
|
165
|
+
|
|
166
|
+
Cloud setup:
|
|
137
167
|
|
|
138
168
|
```text
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
OPENMIND_ENABLED=true
|
|
170
|
+
OPENMIND_MODE=hybrid
|
|
171
|
+
OPENMIND_BASE_URL=https://theopenmind.pro
|
|
172
|
+
OPENMIND_API_KEY=om_...
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Local setup:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx @vindepemarte/openmind init --local
|
|
179
|
+
# or run your local OpenMind stack, then configure Alive-AI:
|
|
180
|
+
OPENMIND_BASE_URL=http://127.0.0.1:3333
|
|
146
181
|
```
|
|
147
182
|
|
|
148
|
-
|
|
183
|
+
## Requirements
|
|
184
|
+
|
|
185
|
+
Minimum for cloud LLMs or remote Ollama:
|
|
186
|
+
|
|
187
|
+
| Requirement | Minimum |
|
|
188
|
+
| --- | --- |
|
|
189
|
+
| Node.js | 18+ |
|
|
190
|
+
| Python | 3.11+ |
|
|
191
|
+
| RAM | 8 GB |
|
|
192
|
+
| Disk | 2 GB free |
|
|
193
|
+
| LLM | OpenRouter, ZAI, remote Ollama, or local Ollama already installed |
|
|
194
|
+
|
|
195
|
+
Comfortable local setup:
|
|
196
|
+
|
|
197
|
+
| Requirement | Recommended |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| Node.js | 20+ |
|
|
200
|
+
| RAM | 16 GB for 3B-4B local models |
|
|
201
|
+
| RAM for bigger models | 32 GB for 7B+ local models, Redis, voice, and long sessions |
|
|
202
|
+
| Disk | 10 GB+, more if you keep local models/media |
|
|
203
|
+
| Optional tools | `uv`, `ffmpeg`, Docker, Ollama |
|
|
149
204
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
-
|
|
205
|
+
`npx . start` creates `.alive-ai/venv` and installs Python dependencies. System-level packages such as Node, Python, Ollama, Docker, and ffmpeg must already exist on the machine.
|
|
206
|
+
|
|
207
|
+
## Platform Support
|
|
208
|
+
|
|
209
|
+
Alive-AI is designed for macOS, Windows, and Linux.
|
|
210
|
+
|
|
211
|
+
| Platform | Notes |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| macOS | First-class local development path. Use Homebrew for Python, uv, ffmpeg, Docker, and Ollama. |
|
|
214
|
+
| Windows | Supported from PowerShell with Node 18+ and Python 3.11+. WSL is recommended for heavier local model and Docker workflows. |
|
|
215
|
+
| Linux | Supported with distro packages for Python/venv, ffmpeg, Docker, and Ollama. |
|
|
216
|
+
|
|
217
|
+
Local model quality and speed depend on your machine. Cloud LLMs reduce RAM pressure.
|
|
218
|
+
|
|
219
|
+
## Dashboard
|
|
220
|
+
|
|
221
|
+
The real WebUI streams local runtime state over Server-Sent Events and shows:
|
|
222
|
+
|
|
223
|
+
- emotional state,
|
|
224
|
+
- recent thoughts and idle processing,
|
|
225
|
+
- memory counters and uptime,
|
|
226
|
+
- hormones and interoceptive body state,
|
|
227
|
+
- attachment, circadian rhythm, body memory, dreams, curiosity, and conflicts,
|
|
228
|
+
- runtime health through local endpoints.
|
|
229
|
+
|
|
230
|
+
GitHub Pages cannot run the Python/FastAPI backend, so the public page includes a static export of the actual WebUI with mocked state:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
https://vindepemarte.github.io/alive-ai/
|
|
234
|
+
```
|
|
155
235
|
|
|
156
236
|
## Docker
|
|
157
237
|
|
|
@@ -179,18 +259,20 @@ Implemented:
|
|
|
179
259
|
- [x] Attachment, circadian rhythm, body memory, curiosity, dreams, and internal conflicts
|
|
180
260
|
- [x] Per-user memory/state isolation
|
|
181
261
|
- [x] Telegram input/output runtime
|
|
262
|
+
- [x] Terminal chat runtime with owner-style slash commands
|
|
182
263
|
- [x] Local WebUI dashboard with live state streaming
|
|
183
|
-
- [x]
|
|
264
|
+
- [x] Optional hybrid OpenMind cloud/local semantic memory
|
|
265
|
+
- [x] npm/npx CLI scaffold, setup, doctor, demo, chat, and start commands
|
|
184
266
|
- [x] Clean public repo with private personas, media, runtime data, and multi-AI orchestration removed
|
|
185
|
-
- [x] GitHub Pages site and full WebUI
|
|
267
|
+
- [x] GitHub Pages site and full static WebUI export
|
|
186
268
|
|
|
187
269
|
Next:
|
|
188
270
|
|
|
189
271
|
- [ ] One-command local model bootstrap through Ollama profiles
|
|
190
272
|
- [ ] Desktop app wrapper with tray controls and local service lifecycle
|
|
191
273
|
- [ ] Browser-based onboarding wizard for personality, boundaries, LLM provider, and memory settings
|
|
192
|
-
- [ ]
|
|
193
|
-
- [ ] More input channels beyond Telegram
|
|
274
|
+
- [ ] Better guided system install commands per OS
|
|
275
|
+
- [ ] More input channels beyond terminal and Telegram
|
|
194
276
|
- [ ] Import/export for memories and personality snapshots
|
|
195
277
|
- [ ] Plugin API for new senses, skills, and output modalities
|
|
196
278
|
- [ ] Evaluation harness for emotional continuity, memory drift, and unhealthy attachment risk
|
package/brain/memory/manager.py
CHANGED
|
@@ -37,11 +37,19 @@ class Memory:
|
|
|
37
37
|
self.fact_extractor = FactExtractor(self.data_path / "facts.json")
|
|
38
38
|
self.summarizer = ConversationSummarizer(self.data_path)
|
|
39
39
|
self.vector_store = None
|
|
40
|
+
self.openmind = None
|
|
40
41
|
self.bot_id = bot_id.lower()
|
|
41
42
|
if embedding_service:
|
|
42
43
|
self.vector_store = VectorMemoryStore(embedding_service, user_id=user_id, bot_id=bot_id)
|
|
43
44
|
if self.vector_store.connect():
|
|
44
45
|
print(f"[Memory] Vector store ready for user {user_id} on bot {bot_id}! {self.vector_store.count()} memories")
|
|
46
|
+
try:
|
|
47
|
+
from .openmind import OpenMindMemoryBridge
|
|
48
|
+
if OpenMindMemoryBridge.enabled():
|
|
49
|
+
self.openmind = OpenMindMemoryBridge(nervous, user_id=user_id, bot_id=bot_id)
|
|
50
|
+
print(f"[Memory] OpenMind bridge enabled for user {user_id} on bot {bot_id}")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"[Memory] OpenMind bridge unavailable: {e}")
|
|
45
53
|
self.turn_count = 0
|
|
46
54
|
nervous.on("memory_save", self._on_save)
|
|
47
55
|
|
|
@@ -123,6 +131,14 @@ class Memory:
|
|
|
123
131
|
related = ""
|
|
124
132
|
if current_message and self.vector_store:
|
|
125
133
|
related = self.search_relevant_memories(current_message, limit=3)
|
|
134
|
+
if current_message and self.openmind:
|
|
135
|
+
openmind_related = await self.openmind.search_context(current_message, limit=3)
|
|
136
|
+
if openmind_related:
|
|
137
|
+
related = (
|
|
138
|
+
f"{related}\n\nOpenMind long-term memory:\n{openmind_related}"
|
|
139
|
+
if related else
|
|
140
|
+
f"OpenMind long-term memory:\n{openmind_related}"
|
|
141
|
+
)
|
|
126
142
|
|
|
127
143
|
# Get working memory (empty after restart)
|
|
128
144
|
history = self.working.get_history()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Optional OpenMind semantic memory bridge."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from core.settings import get as settings_get
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenMindMemoryBridge:
|
|
13
|
+
"""Syncs Alive-AI turns to OpenMind and retrieves long-term semantic context."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, nervous, user_id: str, bot_id: str):
|
|
16
|
+
self.nervous = nervous
|
|
17
|
+
self.user_id = str(user_id or "default")
|
|
18
|
+
self.bot_id = str(bot_id or "alive_ai").lower()
|
|
19
|
+
nervous.on("memory_save", self._on_memory_save)
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def enabled() -> bool:
|
|
23
|
+
return str(settings_get("OPENMIND_ENABLED", os.environ.get("OPENMIND_ENABLED", "false"))).lower() in (
|
|
24
|
+
"1", "true", "yes", "on"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def base_url() -> str:
|
|
29
|
+
return str(settings_get("OPENMIND_BASE_URL", os.environ.get("OPENMIND_BASE_URL", "https://theopenmind.pro"))).rstrip("/")
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def api_key() -> str:
|
|
33
|
+
return str(settings_get("OPENMIND_API_KEY", os.environ.get("OPENMIND_API_KEY", ""))).strip()
|
|
34
|
+
|
|
35
|
+
def _headers(self) -> Dict[str, str]:
|
|
36
|
+
headers = {"content-type": "application/json"}
|
|
37
|
+
key = self.api_key()
|
|
38
|
+
if key:
|
|
39
|
+
headers["authorization"] = f"Bearer {key}"
|
|
40
|
+
return headers
|
|
41
|
+
|
|
42
|
+
def _on_memory_save(self, data: dict):
|
|
43
|
+
if not self.enabled() or data.get("type") != "conversation":
|
|
44
|
+
return
|
|
45
|
+
event_user_id = data.get("user_id")
|
|
46
|
+
if event_user_id:
|
|
47
|
+
if str(event_user_id) != self.user_id:
|
|
48
|
+
return
|
|
49
|
+
elif self.user_id != "default":
|
|
50
|
+
return
|
|
51
|
+
asyncio.ensure_future(self.capture_turn(data))
|
|
52
|
+
|
|
53
|
+
async def capture_turn(self, data: dict) -> Optional[dict]:
|
|
54
|
+
user_msg = (data.get("user_message") or "").strip()
|
|
55
|
+
ai_msg = (data.get("ai_response") or "").strip()
|
|
56
|
+
if not user_msg and not ai_msg:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
emotion = data.get("emotion") or {}
|
|
60
|
+
mood = emotion.get("mood", "unknown")
|
|
61
|
+
content = "\n".join(
|
|
62
|
+
part for part in [
|
|
63
|
+
f"Alive-AI agent: {self.bot_id}",
|
|
64
|
+
f"User id: {self.user_id}",
|
|
65
|
+
f"Mood: {mood}",
|
|
66
|
+
f"User: {user_msg}" if user_msg else "",
|
|
67
|
+
f"Alive-AI: {ai_msg}" if ai_msg else "",
|
|
68
|
+
] if part
|
|
69
|
+
)
|
|
70
|
+
tags = ["alive-ai", self.bot_id, f"user-{self.user_id}", f"mood-{mood}"]
|
|
71
|
+
payload = {
|
|
72
|
+
"content": content,
|
|
73
|
+
"source": "alive-ai",
|
|
74
|
+
"type": "conversation",
|
|
75
|
+
"tags": tags,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
timeout = aiohttp.ClientTimeout(total=12)
|
|
80
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
81
|
+
async with session.post(f"{self.base_url()}/capture", json=payload, headers=self._headers()) as resp:
|
|
82
|
+
if resp.status >= 400:
|
|
83
|
+
body = await resp.text()
|
|
84
|
+
print(f"[OpenMind] Capture failed ({resp.status}): {body[:200]}")
|
|
85
|
+
return None
|
|
86
|
+
result = await resp.json()
|
|
87
|
+
print(f"[OpenMind] Captured memory: {result.get('status', 'ok')}")
|
|
88
|
+
return result
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
print(f"[OpenMind] Capture unavailable: {exc}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
async def search_context(self, query: str, limit: int = 3) -> str:
|
|
94
|
+
if not self.enabled() or not query.strip():
|
|
95
|
+
return ""
|
|
96
|
+
try:
|
|
97
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
98
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
99
|
+
async with session.get(
|
|
100
|
+
f"{self.base_url()}/search",
|
|
101
|
+
params={"q": query, "limit": str(limit)},
|
|
102
|
+
headers=self._headers(),
|
|
103
|
+
) as resp:
|
|
104
|
+
if resp.status >= 400:
|
|
105
|
+
body = await resp.text()
|
|
106
|
+
print(f"[OpenMind] Search failed ({resp.status}): {body[:200]}")
|
|
107
|
+
return ""
|
|
108
|
+
rows = await resp.json()
|
|
109
|
+
except Exception as exc:
|
|
110
|
+
print(f"[OpenMind] Search unavailable: {exc}")
|
|
111
|
+
return ""
|
|
112
|
+
|
|
113
|
+
if not isinstance(rows, list):
|
|
114
|
+
return ""
|
|
115
|
+
return self._format_results(rows[:limit])
|
|
116
|
+
|
|
117
|
+
def _format_results(self, rows: List[Dict[str, Any]]) -> str:
|
|
118
|
+
lines = []
|
|
119
|
+
for row in rows:
|
|
120
|
+
content = str(row.get("content") or row.get("summary") or "").strip()
|
|
121
|
+
if not content:
|
|
122
|
+
continue
|
|
123
|
+
similarity = row.get("similarity")
|
|
124
|
+
prefix = "OpenMind"
|
|
125
|
+
if isinstance(similarity, (int, float)):
|
|
126
|
+
prefix = f"OpenMind {round(similarity * 100)}%"
|
|
127
|
+
lines.append(f"{prefix}: {content[:700]}")
|
|
128
|
+
return "\n".join(lines)
|