fluctlightdb 0.4.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Voxmastery
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluctlightdb
3
+ Version: 0.4.1
4
+ Summary: Python client for FluctlightDB — brain-native memory for AI agents
5
+ Author-email: Voxmastery <voxmastery@roppashreeganesh.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/voxmastery/FluctlightDB
8
+ Project-URL: Documentation, https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md
9
+ Project-URL: Repository, https://github.com/voxmastery/FluctlightDB
10
+ Project-URL: Issues, https://github.com/voxmastery/FluctlightDB/issues
11
+ Keywords: ai,agents,memory,database,llm,recall
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: native
26
+ Requires-Dist: fluctlightdb-native>=0.4.0; extra == "native"
27
+ Dynamic: license-file
28
+
29
+ # fluctlightdb
30
+
31
+ Python client for [FluctlightDB](https://github.com/voxmastery/FluctlightDB) — a brain-native memory store for AI agents.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install fluctlightdb
37
+ ```
38
+
39
+ Optional in-process recall (Rust extension, when wheels are available for your platform):
40
+
41
+ ```bash
42
+ pip install "fluctlightdb[native]"
43
+ # or: pip install fluctlightdb-native
44
+ ```
45
+
46
+ No `cargo` or Rust toolchain required for the HTTP client.
47
+
48
+ ## Quick start (HTTP — like `qdrant-client`)
49
+
50
+ Run a FluctlightDB server (download a [release binary](https://github.com/voxmastery/FluctlightDB/releases) or use your own deployment), then:
51
+
52
+ ```python
53
+ from fluctlightdb import FluctlightClient
54
+
55
+ client = FluctlightClient(
56
+ base_url="http://127.0.0.1:8792",
57
+ api_key="your-key",
58
+ )
59
+
60
+ client.experience("user prefers dark mode", context="settings")
61
+ print(client.activate_lite("theme preference"))
62
+ ```
63
+
64
+ Or use environment variables:
65
+
66
+ ```bash
67
+ export FLUCTLIGHT_SERVE_URL=http://127.0.0.1:8792
68
+ export FLUCTLIGHT_API_KEY=your-key
69
+ ```
70
+
71
+ ```python
72
+ from fluctlightdb import FluctlightClient
73
+
74
+ client = FluctlightClient.from_env()
75
+ print(client.activate("dark mode"))
76
+ ```
77
+
78
+ ## In-process recall (optional)
79
+
80
+ When `fluctlightdb-native` is installed:
81
+
82
+ ```python
83
+ from fluctlightdb import get_recall_client
84
+
85
+ brain = get_recall_client("~/.fluctlight/tenants/default/brain")
86
+ print(brain.activate("dark mode"))
87
+ ```
88
+
89
+ ## Docs
90
+
91
+ - [Getting started](https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md)
92
+ - [HTTP API (OpenAPI)](https://github.com/voxmastery/FluctlightDB/blob/main/docs/openapi.yaml)
93
+
94
+ ## License
95
+
96
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,68 @@
1
+ # fluctlightdb
2
+
3
+ Python client for [FluctlightDB](https://github.com/voxmastery/FluctlightDB) — a brain-native memory store for AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install fluctlightdb
9
+ ```
10
+
11
+ Optional in-process recall (Rust extension, when wheels are available for your platform):
12
+
13
+ ```bash
14
+ pip install "fluctlightdb[native]"
15
+ # or: pip install fluctlightdb-native
16
+ ```
17
+
18
+ No `cargo` or Rust toolchain required for the HTTP client.
19
+
20
+ ## Quick start (HTTP — like `qdrant-client`)
21
+
22
+ Run a FluctlightDB server (download a [release binary](https://github.com/voxmastery/FluctlightDB/releases) or use your own deployment), then:
23
+
24
+ ```python
25
+ from fluctlightdb import FluctlightClient
26
+
27
+ client = FluctlightClient(
28
+ base_url="http://127.0.0.1:8792",
29
+ api_key="your-key",
30
+ )
31
+
32
+ client.experience("user prefers dark mode", context="settings")
33
+ print(client.activate_lite("theme preference"))
34
+ ```
35
+
36
+ Or use environment variables:
37
+
38
+ ```bash
39
+ export FLUCTLIGHT_SERVE_URL=http://127.0.0.1:8792
40
+ export FLUCTLIGHT_API_KEY=your-key
41
+ ```
42
+
43
+ ```python
44
+ from fluctlightdb import FluctlightClient
45
+
46
+ client = FluctlightClient.from_env()
47
+ print(client.activate("dark mode"))
48
+ ```
49
+
50
+ ## In-process recall (optional)
51
+
52
+ When `fluctlightdb-native` is installed:
53
+
54
+ ```python
55
+ from fluctlightdb import get_recall_client
56
+
57
+ brain = get_recall_client("~/.fluctlight/tenants/default/brain")
58
+ print(brain.activate("dark mode"))
59
+ ```
60
+
61
+ ## Docs
62
+
63
+ - [Getting started](https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md)
64
+ - [HTTP API (OpenAPI)](https://github.com/voxmastery/FluctlightDB/blob/main/docs/openapi.yaml)
65
+
66
+ ## License
67
+
68
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,289 @@
1
+ """FluctlightDB Python client — agent memory HTTP API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import http.client
6
+ import json
7
+ import os
8
+ import urllib.error
9
+ import urllib.parse
10
+ import urllib.request
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Optional
13
+
14
+ from .worker import FluctlightNative, FluctlightWorker, get_recall_client, get_worker
15
+
16
+ __all__ = [
17
+ "FluctlightClient",
18
+ "FluctlightNative",
19
+ "FluctlightWorker",
20
+ "get_recall_client",
21
+ "get_worker",
22
+ ]
23
+
24
+
25
+ @dataclass
26
+ class FluctlightClient:
27
+ base_url: str = "http://127.0.0.1:8792"
28
+ api_key: str = ""
29
+ timeout: float = 60.0
30
+ _http: Optional[http.client.HTTPConnection] = field(default=None, repr=False, compare=False)
31
+
32
+ @classmethod
33
+ def from_env(cls) -> "FluctlightClient":
34
+ return cls(
35
+ base_url=os.environ.get("FLUCTLIGHT_SERVE_URL", "http://127.0.0.1:8792").rstrip("/"),
36
+ api_key=os.environ.get("FLUCTLIGHT_API_KEY", ""),
37
+ timeout=float(os.environ.get("FLUCTLIGHT_HTTP_TIMEOUT", "60")),
38
+ )
39
+
40
+ def _headers(self) -> dict[str, str]:
41
+ headers = {"Content-Type": "application/json", "Connection": "keep-alive"}
42
+ if self.api_key:
43
+ headers["Authorization"] = f"Bearer {self.api_key}"
44
+ return headers
45
+
46
+ def _conn(self) -> http.client.HTTPConnection:
47
+ if self._http is not None:
48
+ return self._http
49
+ parsed = urllib.parse.urlparse(self.base_url)
50
+ host = parsed.hostname or "127.0.0.1"
51
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
52
+ if parsed.scheme == "https":
53
+ self._http = http.client.HTTPSConnection(host, port, timeout=self.timeout)
54
+ else:
55
+ self._http = http.client.HTTPConnection(host, port, timeout=self.timeout)
56
+ return self._http
57
+
58
+ def _post(self, path: str, payload: Optional[dict[str, Any]] = None) -> dict[str, Any]:
59
+ body = json.dumps(payload or {})
60
+ headers = self._headers()
61
+ try:
62
+ conn = self._conn()
63
+ conn.request("POST", path, body=body, headers=headers)
64
+ resp = conn.getresponse()
65
+ raw = resp.read().decode("utf-8")
66
+ if resp.status >= 400:
67
+ raise RuntimeError(f"Fluctlight HTTP {resp.status}: {raw}")
68
+ return json.loads(raw) if raw else {}
69
+ except (http.client.HTTPException, OSError, RuntimeError):
70
+ self._http = None
71
+ url = f"{self.base_url}{path}"
72
+ req = urllib.request.Request(
73
+ url, data=body.encode("utf-8"), headers=self._headers(), method="POST"
74
+ )
75
+ try:
76
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
77
+ text = resp.read().decode("utf-8")
78
+ return json.loads(text) if text else {}
79
+ except urllib.error.HTTPError as e:
80
+ err_body = e.read().decode("utf-8", errors="replace")
81
+ raise RuntimeError(f"Fluctlight HTTP {e.code}: {err_body}") from e
82
+
83
+ def health(self) -> bool:
84
+ req = urllib.request.Request(f"{self.base_url}/health", method="GET")
85
+ try:
86
+ with urllib.request.urlopen(req, timeout=min(self.timeout, 5.0)) as resp:
87
+ return resp.status == 200
88
+ except Exception:
89
+ return False
90
+
91
+ def status(self) -> dict[str, Any]:
92
+ return self._post("/api/v1/status")
93
+
94
+ def replica_status(self) -> dict[str, Any]:
95
+ return self._post("/api/v1/replica-status")
96
+
97
+ def experience(
98
+ self,
99
+ content: str,
100
+ context: str = "api",
101
+ salience: float = 0.5,
102
+ outcome: Optional[str] = None,
103
+ semantic_vector: Optional[list[float]] = None,
104
+ agent_id: Optional[str] = None,
105
+ doc_id: Optional[str] = None,
106
+ chunk_id: Optional[str] = None,
107
+ source_uri: Optional[str] = None,
108
+ **extra: Any,
109
+ ) -> dict[str, Any]:
110
+ payload: dict[str, Any] = {
111
+ "content": content,
112
+ "context": context,
113
+ "salience": salience,
114
+ }
115
+ if outcome is not None:
116
+ payload["outcome"] = outcome
117
+ if semantic_vector is not None:
118
+ payload["semantic_vector"] = semantic_vector
119
+ if agent_id is not None:
120
+ payload["agent_id"] = agent_id
121
+ if doc_id is not None:
122
+ payload["doc_id"] = doc_id
123
+ if chunk_id is not None:
124
+ payload["chunk_id"] = chunk_id
125
+ if source_uri is not None:
126
+ payload["source_uri"] = source_uri
127
+ payload.update(extra)
128
+ return self._post("/api/v1/experience", payload)
129
+
130
+ def ingest_chunk(
131
+ self,
132
+ content: str,
133
+ doc_id: str,
134
+ chunk_id: str,
135
+ source_uri: Optional[str] = None,
136
+ semantic_vector: Optional[list[float]] = None,
137
+ salience: float = 0.55,
138
+ agent_id: Optional[str] = None,
139
+ outcome: Optional[str] = None,
140
+ ) -> dict[str, Any]:
141
+ payload: dict[str, Any] = {
142
+ "content": content,
143
+ "doc_id": doc_id,
144
+ "chunk_id": chunk_id,
145
+ "salience": salience,
146
+ }
147
+ if source_uri:
148
+ payload["source_uri"] = source_uri
149
+ if semantic_vector is not None:
150
+ payload["semantic_vector"] = semantic_vector
151
+ if agent_id:
152
+ payload["agent_id"] = agent_id
153
+ if outcome:
154
+ payload["outcome"] = outcome
155
+ return self._post("/api/v1/ingest-chunk", payload)
156
+
157
+ def activate(
158
+ self,
159
+ cue: str,
160
+ semantic_vector: Optional[list[float]] = None,
161
+ agent_id: Optional[str] = None,
162
+ ) -> dict[str, Any]:
163
+ payload: dict[str, Any] = {"cue": cue}
164
+ if semantic_vector is not None:
165
+ payload["semantic_vector"] = semantic_vector
166
+ if agent_id is not None:
167
+ payload["agent_id"] = agent_id
168
+ return self._post("/api/v1/activate", payload)
169
+
170
+ def activate_lite(
171
+ self,
172
+ cue: str,
173
+ semantic_vector: Optional[list[float]] = None,
174
+ agent_id: Optional[str] = None,
175
+ ) -> dict[str, Any]:
176
+ """Top-1 recall — minimal JSON (~200 B) for HTTP agent hot paths."""
177
+ payload: dict[str, Any] = {"cue": cue, "limit": 1}
178
+ if semantic_vector is not None:
179
+ payload["semantic_vector"] = semantic_vector
180
+ if agent_id is not None:
181
+ payload["agent_id"] = agent_id
182
+ return self._post("/api/v1/activate-lite", payload)
183
+
184
+ def activate_batch(
185
+ self,
186
+ items: list[dict[str, Any]],
187
+ ) -> dict[str, Any]:
188
+ return self._post("/api/v1/activate-batch", {"batch": items})
189
+
190
+ def query(self, op: dict[str, Any]) -> dict[str, Any]:
191
+ return self._post("/api/v1/query", {"query": op})
192
+
193
+ def search_by_rag(
194
+ self,
195
+ doc_id: str,
196
+ chunk_id: Optional[str] = None,
197
+ page: int = 0,
198
+ page_size: int = 50,
199
+ ) -> dict[str, Any]:
200
+ q: dict[str, Any] = {
201
+ "op": "search_by_rag",
202
+ "doc_id": doc_id,
203
+ "page": page,
204
+ "page_size": page_size,
205
+ }
206
+ if chunk_id is not None:
207
+ q["chunk_id"] = chunk_id
208
+ return self.query(q)
209
+
210
+ def tick(self, n: int = 1) -> Any:
211
+ return self._post("/api/v1/tick", {"n": int(n)})
212
+
213
+ def compact(self) -> dict[str, Any]:
214
+ return self._post("/api/v1/compact")
215
+
216
+ def export_viz(self) -> dict[str, Any]:
217
+ return self._post("/api/v1/export-viz")
218
+
219
+ def export_graph(self, *, lite: bool = False) -> dict[str, Any]:
220
+ path = "/api/v1/export-graph-lite" if lite else "/api/v1/export-graph"
221
+ return self._post(path)
222
+
223
+ def export_raw(self) -> dict[str, Any]:
224
+ return self._post("/api/v1/export-raw")
225
+
226
+ def reward(self, magnitude: float = 0.5) -> dict[str, Any]:
227
+ return self._post("/api/v1/reward", {"magnitude": float(magnitude)})
228
+
229
+ def death(self, cause: str = "api") -> dict[str, Any]:
230
+ return self._post("/api/v1/death", {"cause": cause[:200]})
231
+
232
+ def mark_core(self, engram_id: str, key: str = "core") -> dict[str, Any]:
233
+ return self._post("/api/v1/mark-core", {"engram_id": str(engram_id), "key": key})
234
+
235
+ def verify_fact(
236
+ self,
237
+ engram_id: str,
238
+ *,
239
+ provenance_kind: str = "ledger_verified",
240
+ source_uri: Optional[str] = None,
241
+ confidence: float = 0.95,
242
+ ) -> dict[str, Any]:
243
+ payload: dict[str, Any] = {
244
+ "engram_id": str(engram_id),
245
+ "provenance_kind": provenance_kind,
246
+ "confidence": confidence,
247
+ }
248
+ if source_uri:
249
+ payload["source_uri"] = source_uri
250
+ return self._post("/api/v1/verify-fact", payload)
251
+
252
+ def ground_wallet(
253
+ self,
254
+ balance: float,
255
+ level: int = 1,
256
+ wallet_path: Optional[str] = None,
257
+ ) -> dict[str, Any]:
258
+ uri = wallet_path or "file://wallet.json"
259
+ content = (
260
+ f"ledger verified: agent wallet balance is ${balance:.2f} at level {level} "
261
+ f"(source: wallet.json — ground truth, not chat memory)"
262
+ )
263
+ rep = self.experience(
264
+ content[:500],
265
+ context="ledger:wallet",
266
+ salience=0.98,
267
+ doc_id="ledger",
268
+ chunk_id="wallet-balance",
269
+ source_uri=uri,
270
+ verified=True,
271
+ provenance_kind="ledger_verified",
272
+ confidence=0.99,
273
+ )
274
+ eid = rep.get("engram_id")
275
+ if eid and not rep.get("deduplicated"):
276
+ self.mark_core(str(eid), "ledger-wallet-balance")
277
+ return rep
278
+
279
+ def preplay(self, goal: str, steps: int = 4) -> dict[str, Any]:
280
+ return self._post("/api/v1/preplay", {"goal": goal, "steps": int(steps)})
281
+
282
+ def neurogenesis(self) -> dict[str, Any]:
283
+ return self._post("/api/v1/neurogenesis", {})
284
+
285
+ def verified_context(self, limit: int = 12) -> dict[str, Any]:
286
+ return self._post("/api/v1/verified-context", {"limit": int(limit)})
287
+
288
+ def stage_report(self) -> dict[str, Any]:
289
+ return self._post("/api/v1/stage-report", {})
@@ -0,0 +1,233 @@
1
+ """Persistent in-process recall — native library (best) or worker subprocess."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ import threading
10
+ from pathlib import Path
11
+ from typing import Any, Optional, Protocol
12
+
13
+
14
+ def _default_fluctlight_bin() -> str:
15
+ env = os.environ.get("FLUCTLIGHT_BIN")
16
+ if env:
17
+ return env
18
+ repo_root = Path(__file__).resolve().parents[3]
19
+ release = repo_root / "target" / "release" / "fluctlight"
20
+ if release.is_file():
21
+ return str(release)
22
+ debug = repo_root / "target" / "debug" / "fluctlight"
23
+ if debug.is_file():
24
+ return str(debug)
25
+ found = shutil.which("fluctlight")
26
+ return found if found else "fluctlight"
27
+
28
+
29
+ class RecallClient(Protocol):
30
+ def activate(
31
+ self,
32
+ cue: str,
33
+ semantic_vector: Optional[list[float]] = None,
34
+ agent_id: Optional[str] = None,
35
+ limit: Optional[int] = None,
36
+ ) -> dict[str, Any]: ...
37
+
38
+ def activate_batch(
39
+ self,
40
+ items: list[dict[str, Any]],
41
+ limit: Optional[int] = None,
42
+ ) -> dict[str, Any]: ...
43
+
44
+ def status(self) -> dict[str, Any]: ...
45
+
46
+ def verified_context(self, limit: int = 12) -> dict[str, Any]: ...
47
+
48
+
49
+ class FluctlightNative:
50
+ """Direct Rust library call via PyO3 — same process as Python (like sqlite3)."""
51
+
52
+ def __init__(self, brain_path: str, readonly: bool = True) -> None:
53
+ import fluctlightdb_native as native # type: ignore
54
+
55
+ self._native = native
56
+ if readonly:
57
+ self._brain = native.Brain.open_readonly(brain_path)
58
+ else:
59
+ self._brain = native.Brain.open(brain_path)
60
+ self.brain_path = brain_path
61
+
62
+ def activate(
63
+ self,
64
+ cue: str,
65
+ semantic_vector: Optional[list[float]] = None,
66
+ agent_id: Optional[str] = None,
67
+ limit: Optional[int] = None,
68
+ ) -> dict[str, Any]:
69
+ return self._brain.activate(cue, semantic_vector, agent_id, limit)
70
+
71
+ def activate_batch(
72
+ self,
73
+ items: list[dict[str, Any]],
74
+ limit: Optional[int] = None,
75
+ ) -> dict[str, Any]:
76
+ return self._brain.activate_batch_json(json.dumps(items), limit)
77
+
78
+ def status(self) -> dict[str, Any]:
79
+ return self._brain.status()
80
+
81
+ def verified_context(self, limit: int = 12) -> dict[str, Any]:
82
+ return self._brain.verified_context(limit)
83
+
84
+ def has_sidecar_index(self) -> bool:
85
+ return bool(self._brain.has_sidecar_index())
86
+
87
+
88
+ class FluctlightWorker:
89
+ """Long-lived `fluctlight worker` subprocess — brain loaded once, sub-ms recall."""
90
+
91
+ def __init__(
92
+ self,
93
+ brain_path: str,
94
+ bin_path: Optional[str] = None,
95
+ ) -> None:
96
+ self.brain_path = brain_path
97
+ self.bin_path = bin_path or _default_fluctlight_bin()
98
+ self._lock = threading.Lock()
99
+ self._id = 0
100
+ self._proc: Optional[subprocess.Popen[str]] = None
101
+ self._start()
102
+
103
+ def _start(self) -> None:
104
+ if not os.path.isfile(self.bin_path):
105
+ raise FileNotFoundError(self.bin_path)
106
+ self._proc = subprocess.Popen(
107
+ [self.bin_path, "worker", "--path", self.brain_path],
108
+ stdin=subprocess.PIPE,
109
+ stdout=subprocess.PIPE,
110
+ stderr=subprocess.PIPE,
111
+ text=True,
112
+ bufsize=1,
113
+ )
114
+ if self._proc.stdin is None or self._proc.stdout is None:
115
+ raise RuntimeError("worker pipes unavailable")
116
+
117
+ def close(self) -> None:
118
+ with self._lock:
119
+ if self._proc and self._proc.poll() is None:
120
+ try:
121
+ self._call("shutdown")
122
+ except Exception:
123
+ pass
124
+ self._proc.terminate()
125
+ self._proc = None
126
+
127
+ def _call(self, op: str, **kwargs: Any) -> dict[str, Any]:
128
+ with self._lock:
129
+ if self._proc is None or self._proc.poll() is not None:
130
+ self._start()
131
+ assert self._proc is not None
132
+ assert self._proc.stdin is not None
133
+ assert self._proc.stdout is not None
134
+ self._id += 1
135
+ req = {"op": op, "id": self._id, **kwargs}
136
+ self._proc.stdin.write(json.dumps(req) + "\n")
137
+ self._proc.stdin.flush()
138
+ line = self._proc.stdout.readline()
139
+ if not line:
140
+ err = (self._proc.stderr.read() if self._proc.stderr else "")[:300]
141
+ raise RuntimeError(f"worker closed: {err}")
142
+ resp = json.loads(line)
143
+ if not resp.get("ok"):
144
+ raise RuntimeError(resp.get("error") or "worker error")
145
+ return resp
146
+
147
+ def ping(self) -> bool:
148
+ return bool(self._call("ping").get("pong"))
149
+
150
+ def activate(
151
+ self,
152
+ cue: str,
153
+ semantic_vector: Optional[list[float]] = None,
154
+ agent_id: Optional[str] = None,
155
+ limit: Optional[int] = None,
156
+ ) -> dict[str, Any]:
157
+ payload: dict[str, Any] = {"cue": cue}
158
+ if semantic_vector is not None:
159
+ payload["semantic_vector"] = semantic_vector
160
+ if agent_id is not None:
161
+ payload["agent_id"] = agent_id
162
+ if limit is not None:
163
+ payload["limit"] = limit
164
+ return self._call("activate", **payload)["result"]
165
+
166
+ def activate_batch(
167
+ self,
168
+ items: list[dict[str, Any]],
169
+ limit: Optional[int] = None,
170
+ ) -> dict[str, Any]:
171
+ payload: dict[str, Any] = {"batch": items}
172
+ if limit is not None:
173
+ payload["limit"] = limit
174
+ resp = self._call("activate_batch", **payload)
175
+ return {"results": resp.get("results") or [], "count": resp.get("count", 0)}
176
+
177
+ def status(self) -> dict[str, Any]:
178
+ return self._call("status")["status"]
179
+
180
+ def verified_context(self, limit: int = 12) -> dict[str, Any]:
181
+ return self._call("verified_context", limit=limit)["context"]
182
+
183
+ def reload(self) -> dict[str, Any]:
184
+ return self._call("reload")
185
+
186
+
187
+ _worker_singleton: Optional[FluctlightWorker] = None
188
+ _native_singleton: Optional[FluctlightNative] = None
189
+ _client_lock = threading.Lock()
190
+
191
+
192
+ def get_recall_client(
193
+ brain_path: Optional[str] = None,
194
+ bin_path: Optional[str] = None,
195
+ ) -> RecallClient:
196
+ """Best available in-process recall: native library > worker subprocess."""
197
+ global _native_singleton, _worker_singleton
198
+ path = brain_path or os.environ.get(
199
+ "FLUCTLIGHT_BRAIN",
200
+ os.environ.get(
201
+ "FLUCTLIGHT_BRAIN_PATH",
202
+ os.path.expanduser("~/.fluctlight/tenants/default/brain"),
203
+ ),
204
+ )
205
+ prefer_native = os.environ.get("FLUCTLIGHT_NATIVE", "1").lower() not in (
206
+ "0",
207
+ "false",
208
+ "no",
209
+ )
210
+ with _client_lock:
211
+ if prefer_native and _native_singleton is None:
212
+ try:
213
+ _native_singleton = FluctlightNative(path, readonly=True)
214
+ return _native_singleton
215
+ except ImportError:
216
+ pass
217
+ except Exception:
218
+ _native_singleton = None
219
+ if _native_singleton is not None:
220
+ return _native_singleton
221
+ if _worker_singleton is None:
222
+ _worker_singleton = FluctlightWorker(path, bin_path=bin_path)
223
+ return _worker_singleton
224
+
225
+
226
+ def get_worker(
227
+ brain_path: Optional[str] = None,
228
+ bin_path: Optional[str] = None,
229
+ ) -> FluctlightWorker:
230
+ client = get_recall_client(brain_path=brain_path, bin_path=bin_path)
231
+ if isinstance(client, FluctlightWorker):
232
+ return client
233
+ raise TypeError("native client active — use get_recall_client() instead")
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluctlightdb
3
+ Version: 0.4.1
4
+ Summary: Python client for FluctlightDB — brain-native memory for AI agents
5
+ Author-email: Voxmastery <voxmastery@roppashreeganesh.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/voxmastery/FluctlightDB
8
+ Project-URL: Documentation, https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md
9
+ Project-URL: Repository, https://github.com/voxmastery/FluctlightDB
10
+ Project-URL: Issues, https://github.com/voxmastery/FluctlightDB/issues
11
+ Keywords: ai,agents,memory,database,llm,recall
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: native
26
+ Requires-Dist: fluctlightdb-native>=0.4.0; extra == "native"
27
+ Dynamic: license-file
28
+
29
+ # fluctlightdb
30
+
31
+ Python client for [FluctlightDB](https://github.com/voxmastery/FluctlightDB) — a brain-native memory store for AI agents.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install fluctlightdb
37
+ ```
38
+
39
+ Optional in-process recall (Rust extension, when wheels are available for your platform):
40
+
41
+ ```bash
42
+ pip install "fluctlightdb[native]"
43
+ # or: pip install fluctlightdb-native
44
+ ```
45
+
46
+ No `cargo` or Rust toolchain required for the HTTP client.
47
+
48
+ ## Quick start (HTTP — like `qdrant-client`)
49
+
50
+ Run a FluctlightDB server (download a [release binary](https://github.com/voxmastery/FluctlightDB/releases) or use your own deployment), then:
51
+
52
+ ```python
53
+ from fluctlightdb import FluctlightClient
54
+
55
+ client = FluctlightClient(
56
+ base_url="http://127.0.0.1:8792",
57
+ api_key="your-key",
58
+ )
59
+
60
+ client.experience("user prefers dark mode", context="settings")
61
+ print(client.activate_lite("theme preference"))
62
+ ```
63
+
64
+ Or use environment variables:
65
+
66
+ ```bash
67
+ export FLUCTLIGHT_SERVE_URL=http://127.0.0.1:8792
68
+ export FLUCTLIGHT_API_KEY=your-key
69
+ ```
70
+
71
+ ```python
72
+ from fluctlightdb import FluctlightClient
73
+
74
+ client = FluctlightClient.from_env()
75
+ print(client.activate("dark mode"))
76
+ ```
77
+
78
+ ## In-process recall (optional)
79
+
80
+ When `fluctlightdb-native` is installed:
81
+
82
+ ```python
83
+ from fluctlightdb import get_recall_client
84
+
85
+ brain = get_recall_client("~/.fluctlight/tenants/default/brain")
86
+ print(brain.activate("dark mode"))
87
+ ```
88
+
89
+ ## Docs
90
+
91
+ - [Getting started](https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md)
92
+ - [HTTP API (OpenAPI)](https://github.com/voxmastery/FluctlightDB/blob/main/docs/openapi.yaml)
93
+
94
+ ## License
95
+
96
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ fluctlightdb/__init__.py
5
+ fluctlightdb/worker.py
6
+ fluctlightdb.egg-info/PKG-INFO
7
+ fluctlightdb.egg-info/SOURCES.txt
8
+ fluctlightdb.egg-info/dependency_links.txt
9
+ fluctlightdb.egg-info/requires.txt
10
+ fluctlightdb.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+
2
+ [native]
3
+ fluctlightdb-native>=0.4.0
@@ -0,0 +1 @@
1
+ fluctlightdb
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fluctlightdb"
7
+ version = "0.4.1"
8
+ description = "Python client for FluctlightDB — brain-native memory for AI agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Voxmastery", email = "voxmastery@roppashreeganesh.com" }]
14
+ keywords = ["ai", "agents", "memory", "database", "llm", "recall"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Database",
24
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/voxmastery/FluctlightDB"
30
+ Documentation = "https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md"
31
+ Repository = "https://github.com/voxmastery/FluctlightDB"
32
+ Issues = "https://github.com/voxmastery/FluctlightDB/issues"
33
+
34
+ [project.optional-dependencies]
35
+ native = ["fluctlightdb-native>=0.4.0"]
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ include = ["fluctlightdb*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+