fluctlightdb 0.4.2__tar.gz → 0.4.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluctlightdb
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Python client for FluctlightDB — brain-native memory for AI agents
5
5
  Author-email: Voxmastery <voxmastery@roppashreeganesh.com>
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Requires-Python: >=3.9
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Provides-Extra: native
26
- Requires-Dist: fluctlightdb-native>=0.4.2; extra == "native"
26
+ Requires-Dist: fluctlightdb-native>=0.4.3; extra == "native"
27
27
  Dynamic: license-file
28
28
 
29
29
  # fluctlightdb
@@ -32,10 +32,20 @@ Python client for [FluctlightDB](https://github.com/voxmastery/FluctlightDB) —
32
32
 
33
33
  ## Install
34
34
 
35
+ On **Debian/Ubuntu 23.04+**, **Debian 12+**, and **Fedora 38+**, system Python is [PEP 668](https://peps.python.org/pep-0668/) *externally managed* — bare `pip install` fails with `externally-managed-environment`. Use a **virtual environment** (same as any other PyPI library):
36
+
35
37
  ```bash
38
+ python3 -m venv .venv
39
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
36
40
  pip install fluctlightdb
37
41
  ```
38
42
 
43
+ From a clone of this repo you can also run:
44
+
45
+ ```bash
46
+ ./scripts/install-python-client.sh
47
+ ```
48
+
39
49
  Optional in-process recall (Rust extension, when wheels are available for your platform):
40
50
 
41
51
  ```bash
@@ -75,17 +85,22 @@ client = FluctlightClient.from_env()
75
85
  print(client.activate("dark mode"))
76
86
  ```
77
87
 
78
- ## In-process recall (optional)
88
+ ## In-process brain (like `sqlite3`)
79
89
 
80
90
  When `fluctlightdb-native` is installed:
81
91
 
82
92
  ```python
83
- from fluctlightdb import get_recall_client
93
+ from fluctlightdb import connect
84
94
 
85
- brain = get_recall_client("~/.fluctlight/tenants/default/brain")
95
+ brain = connect("/tmp/my-agent-brain")
96
+ brain.experience("user prefers dark mode", context="settings")
86
97
  print(brain.activate("dark mode"))
87
98
  ```
88
99
 
100
+ Read-only recall path: `get_recall_client(path)`.
101
+
102
+ ## In-process recall (read-only helper)
103
+
89
104
  ## Docs
90
105
 
91
106
  - [Getting started](https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md)
@@ -4,10 +4,20 @@ Python client for [FluctlightDB](https://github.com/voxmastery/FluctlightDB) —
4
4
 
5
5
  ## Install
6
6
 
7
+ On **Debian/Ubuntu 23.04+**, **Debian 12+**, and **Fedora 38+**, system Python is [PEP 668](https://peps.python.org/pep-0668/) *externally managed* — bare `pip install` fails with `externally-managed-environment`. Use a **virtual environment** (same as any other PyPI library):
8
+
7
9
  ```bash
10
+ python3 -m venv .venv
11
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
8
12
  pip install fluctlightdb
9
13
  ```
10
14
 
15
+ From a clone of this repo you can also run:
16
+
17
+ ```bash
18
+ ./scripts/install-python-client.sh
19
+ ```
20
+
11
21
  Optional in-process recall (Rust extension, when wheels are available for your platform):
12
22
 
13
23
  ```bash
@@ -47,17 +57,22 @@ client = FluctlightClient.from_env()
47
57
  print(client.activate("dark mode"))
48
58
  ```
49
59
 
50
- ## In-process recall (optional)
60
+ ## In-process brain (like `sqlite3`)
51
61
 
52
62
  When `fluctlightdb-native` is installed:
53
63
 
54
64
  ```python
55
- from fluctlightdb import get_recall_client
65
+ from fluctlightdb import connect
56
66
 
57
- brain = get_recall_client("~/.fluctlight/tenants/default/brain")
67
+ brain = connect("/tmp/my-agent-brain")
68
+ brain.experience("user prefers dark mode", context="settings")
58
69
  print(brain.activate("dark mode"))
59
70
  ```
60
71
 
72
+ Read-only recall path: `get_recall_client(path)`.
73
+
74
+ ## In-process recall (read-only helper)
75
+
61
76
  ## Docs
62
77
 
63
78
  - [Getting started](https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md)
@@ -11,12 +11,15 @@ import urllib.request
11
11
  from dataclasses import dataclass, field
12
12
  from typing import Any, Optional
13
13
 
14
+ from .brain import FluctlightBrain, connect
14
15
  from .worker import FluctlightNative, FluctlightWorker, get_recall_client, get_worker
15
16
 
16
17
  __all__ = [
17
18
  "FluctlightClient",
19
+ "FluctlightBrain",
18
20
  "FluctlightNative",
19
21
  "FluctlightWorker",
22
+ "connect",
20
23
  "get_recall_client",
21
24
  "get_worker",
22
25
  ]
@@ -0,0 +1,151 @@
1
+ """Embedded brain client — sqlite3-style in-process API when native is installed."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from typing import Any, Optional
8
+
9
+
10
+ class FluctlightBrain:
11
+ """In-process Fluctlight brain (like ``sqlite3.connect``). Requires ``fluctlightdb-native``."""
12
+
13
+ def __init__(self, brain: Any, *, readonly: bool = False) -> None:
14
+ self._brain = brain
15
+ self.readonly = readonly
16
+ self.brain_path: Optional[str] = getattr(brain, "brain_path", None)
17
+
18
+ @classmethod
19
+ def connect(cls, path: str, *, readonly: bool = False) -> "FluctlightBrain":
20
+ import fluctlightdb_native as native # type: ignore
21
+
22
+ brain = native.Brain.open_readonly(path) if readonly else native.Brain.open(path)
23
+ obj = cls(brain, readonly=readonly)
24
+ obj.brain_path = path
25
+ return obj
26
+
27
+ @classmethod
28
+ def new(cls) -> "FluctlightBrain":
29
+ import fluctlightdb_native as native # type: ignore
30
+
31
+ return cls(native.Brain.new(), readonly=False)
32
+
33
+ def experience(
34
+ self,
35
+ content: str,
36
+ *,
37
+ context: str = "api",
38
+ salience: float = 0.5,
39
+ outcome: Optional[str] = None,
40
+ semantic_vector: Optional[list[float]] = None,
41
+ agent_id: Optional[str] = None,
42
+ tenant_id: Optional[str] = None,
43
+ verified: Optional[bool] = None,
44
+ provenance_kind: Optional[str] = None,
45
+ source_uri: Optional[str] = None,
46
+ confidence: Optional[float] = None,
47
+ doc_id: Optional[str] = None,
48
+ chunk_id: Optional[str] = None,
49
+ **extra: Any,
50
+ ) -> dict[str, Any]:
51
+ payload: dict[str, Any] = {
52
+ "content": content,
53
+ "context": context,
54
+ "salience_hint": salience,
55
+ }
56
+ if outcome is not None:
57
+ payload["outcome"] = outcome
58
+ if semantic_vector is not None:
59
+ payload["semantic_vector"] = semantic_vector
60
+ if agent_id is not None:
61
+ payload["agent_id"] = agent_id
62
+ if tenant_id is not None:
63
+ payload["tenant_id"] = tenant_id
64
+ if doc_id or chunk_id or source_uri:
65
+ payload["rag"] = {
66
+ "doc_id": doc_id,
67
+ "chunk_id": chunk_id,
68
+ "source_uri": source_uri,
69
+ }
70
+ if verified is not None or provenance_kind or source_uri:
71
+ payload["provenance"] = {
72
+ "kind": provenance_kind or "ledger_verified",
73
+ "source_uri": source_uri,
74
+ "confidence": confidence if confidence is not None else 0.95,
75
+ "verified": bool(verified),
76
+ }
77
+ payload.update(extra)
78
+ return self._brain.experience(json.dumps(payload))
79
+
80
+ def activate(
81
+ self,
82
+ cue: str,
83
+ semantic_vector: Optional[list[float]] = None,
84
+ agent_id: Optional[str] = None,
85
+ limit: Optional[int] = None,
86
+ ) -> dict[str, Any]:
87
+ return self._brain.activate(cue, semantic_vector, agent_id, limit)
88
+
89
+ def activate_batch(
90
+ self,
91
+ items: list[dict[str, Any]],
92
+ limit: Optional[int] = None,
93
+ ) -> dict[str, Any]:
94
+ return self._brain.activate_batch_json(json.dumps(items), limit)
95
+
96
+ def verify_fact(
97
+ self,
98
+ engram_id: str,
99
+ *,
100
+ provenance_kind: str = "ledger_verified",
101
+ source_uri: Optional[str] = None,
102
+ confidence: float = 0.95,
103
+ ) -> None:
104
+ self._brain.verify_fact(engram_id, provenance_kind, source_uri, confidence)
105
+
106
+ def sleep(self) -> dict[str, Any]:
107
+ return self._brain.sleep()
108
+
109
+ def tick(self, n: int = 1) -> list[dict[str, Any]]:
110
+ return self._brain.tick(n)
111
+
112
+ def preplay(self, goal: str, steps: int = 4) -> dict[str, Any]:
113
+ return self._brain.preplay(goal, steps)
114
+
115
+ def neurogenesis(self) -> dict[str, Any]:
116
+ return self._brain.neurogenesis_pulse()
117
+
118
+ def compact(self) -> dict[str, Any]:
119
+ return self._brain.compact()
120
+
121
+ def reward(self, magnitude: float = 0.5) -> None:
122
+ self._brain.reward(magnitude)
123
+
124
+ def mark_core(self, engram_id: str, key: str) -> None:
125
+ self._brain.mark_core(engram_id, key)
126
+
127
+ def death(self, cause: str = "api") -> str:
128
+ return str(self._brain.death(cause))
129
+
130
+ def status(self) -> dict[str, Any]:
131
+ return self._brain.status()
132
+
133
+ def stage_report(self) -> dict[str, Any]:
134
+ return self._brain.stage_report()
135
+
136
+ def verified_context(self, limit: int = 12) -> dict[str, Any]:
137
+ return self._brain.verified_context(limit)
138
+
139
+ def stage(self) -> str:
140
+ return str(self._brain.stage())
141
+
142
+ def checkpoint(self) -> None:
143
+ self._brain.checkpoint()
144
+
145
+ def has_sidecar_index(self) -> bool:
146
+ return bool(self._brain.has_sidecar_index())
147
+
148
+
149
+ def connect(path: str, *, readonly: bool = False) -> FluctlightBrain:
150
+ """Open a brain directory like ``sqlite3.connect(path)``."""
151
+ return FluctlightBrain.connect(path, readonly=readonly)
@@ -58,6 +58,7 @@ class FluctlightNative:
58
58
  else:
59
59
  self._brain = native.Brain.open(brain_path)
60
60
  self.brain_path = brain_path
61
+ self.readonly = readonly
61
62
 
62
63
  def activate(
63
64
  self,
@@ -75,15 +76,55 @@ class FluctlightNative:
75
76
  ) -> dict[str, Any]:
76
77
  return self._brain.activate_batch_json(json.dumps(items), limit)
77
78
 
79
+ def experience(self, episode_json: str) -> dict[str, Any]:
80
+ if self.readonly:
81
+ raise RuntimeError("brain opened readonly — reopen with readonly=False")
82
+ return self._brain.experience(episode_json)
83
+
84
+ def experience_dict(self, payload: dict[str, Any]) -> dict[str, Any]:
85
+ return self.experience(json.dumps(payload))
86
+
87
+ def verify_fact(
88
+ self,
89
+ engram_id: str,
90
+ provenance_kind: str = "ledger_verified",
91
+ source_uri: Optional[str] = None,
92
+ confidence: float = 0.95,
93
+ ) -> None:
94
+ if self.readonly:
95
+ raise RuntimeError("brain opened readonly")
96
+ self._brain.verify_fact(engram_id, provenance_kind, source_uri, confidence)
97
+
98
+ def sleep(self) -> dict[str, Any]:
99
+ if self.readonly:
100
+ raise RuntimeError("brain opened readonly")
101
+ return self._brain.sleep()
102
+
103
+ def tick(self, n: int = 1) -> list[dict[str, Any]]:
104
+ if self.readonly:
105
+ raise RuntimeError("brain opened readonly")
106
+ return self._brain.tick(n)
107
+
108
+ def preplay(self, goal: str, steps: int = 4) -> dict[str, Any]:
109
+ return self._brain.preplay(goal, steps)
110
+
78
111
  def status(self) -> dict[str, Any]:
79
112
  return self._brain.status()
80
113
 
114
+ def stage_report(self) -> dict[str, Any]:
115
+ return self._brain.stage_report()
116
+
81
117
  def verified_context(self, limit: int = 12) -> dict[str, Any]:
82
118
  return self._brain.verified_context(limit)
83
119
 
84
120
  def has_sidecar_index(self) -> bool:
85
121
  return bool(self._brain.has_sidecar_index())
86
122
 
123
+ def checkpoint(self) -> None:
124
+ if self.readonly:
125
+ raise RuntimeError("brain opened readonly")
126
+ self._brain.checkpoint()
127
+
87
128
 
88
129
  class FluctlightWorker:
89
130
  """Long-lived `fluctlight worker` subprocess — brain loaded once, sub-ms recall."""
@@ -118,7 +159,8 @@ class FluctlightWorker:
118
159
  with self._lock:
119
160
  if self._proc and self._proc.poll() is None:
120
161
  try:
121
- self._call("shutdown")
162
+ self._proc.stdin.write('{"op":"shutdown"}\n')
163
+ self._proc.stdin.flush()
122
164
  except Exception:
123
165
  pass
124
166
  self._proc.terminate()
@@ -128,24 +170,18 @@ class FluctlightWorker:
128
170
  with self._lock:
129
171
  if self._proc is None or self._proc.poll() is not None:
130
172
  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
173
+ assert self._proc and self._proc.stdin and self._proc.stdout
134
174
  self._id += 1
135
- req = {"op": op, "id": self._id, **kwargs}
175
+ req = {"id": self._id, "op": op, **kwargs}
136
176
  self._proc.stdin.write(json.dumps(req) + "\n")
137
177
  self._proc.stdin.flush()
138
178
  line = self._proc.stdout.readline()
139
179
  if not line:
140
- err = (self._proc.stderr.read() if self._proc.stderr else "")[:300]
141
- raise RuntimeError(f"worker closed: {err}")
180
+ raise RuntimeError("worker closed stdout")
142
181
  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"))
182
+ if "error" in resp:
183
+ raise RuntimeError(resp["error"])
184
+ return resp.get("result", resp)
149
185
 
150
186
  def activate(
151
187
  self,
@@ -161,21 +197,17 @@ class FluctlightWorker:
161
197
  payload["agent_id"] = agent_id
162
198
  if limit is not None:
163
199
  payload["limit"] = limit
164
- return self._call("activate", **payload)["result"]
200
+ return self._call("activate", **payload)
165
201
 
166
202
  def activate_batch(
167
203
  self,
168
204
  items: list[dict[str, Any]],
169
205
  limit: Optional[int] = None,
170
206
  ) -> 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)}
207
+ return self._call("activate_batch", batch=items, limit=limit)
176
208
 
177
209
  def status(self) -> dict[str, Any]:
178
- return self._call("status")["status"]
210
+ return self._call("status")
179
211
 
180
212
  def verified_context(self, limit: int = 12) -> dict[str, Any]:
181
213
  return self._call("verified_context", limit=limit)["context"]
@@ -192,6 +224,8 @@ _client_lock = threading.Lock()
192
224
  def get_recall_client(
193
225
  brain_path: Optional[str] = None,
194
226
  bin_path: Optional[str] = None,
227
+ *,
228
+ readonly: bool = True,
195
229
  ) -> RecallClient:
196
230
  """Best available in-process recall: native library > worker subprocess."""
197
231
  global _native_singleton, _worker_singleton
@@ -210,7 +244,7 @@ def get_recall_client(
210
244
  with _client_lock:
211
245
  if prefer_native and _native_singleton is None:
212
246
  try:
213
- _native_singleton = FluctlightNative(path, readonly=True)
247
+ _native_singleton = FluctlightNative(path, readonly=readonly)
214
248
  return _native_singleton
215
249
  except ImportError:
216
250
  pass
@@ -227,7 +261,7 @@ def get_worker(
227
261
  brain_path: Optional[str] = None,
228
262
  bin_path: Optional[str] = None,
229
263
  ) -> FluctlightWorker:
230
- client = get_recall_client(brain_path=brain_path, bin_path=bin_path)
264
+ client = get_recall_client(brain_path=brain_path, bin_path=bin_path, readonly=True)
231
265
  if isinstance(client, FluctlightWorker):
232
266
  return client
233
267
  raise TypeError("native client active — use get_recall_client() instead")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluctlightdb
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Python client for FluctlightDB — brain-native memory for AI agents
5
5
  Author-email: Voxmastery <voxmastery@roppashreeganesh.com>
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Requires-Python: >=3.9
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Provides-Extra: native
26
- Requires-Dist: fluctlightdb-native>=0.4.2; extra == "native"
26
+ Requires-Dist: fluctlightdb-native>=0.4.3; extra == "native"
27
27
  Dynamic: license-file
28
28
 
29
29
  # fluctlightdb
@@ -32,10 +32,20 @@ Python client for [FluctlightDB](https://github.com/voxmastery/FluctlightDB) —
32
32
 
33
33
  ## Install
34
34
 
35
+ On **Debian/Ubuntu 23.04+**, **Debian 12+**, and **Fedora 38+**, system Python is [PEP 668](https://peps.python.org/pep-0668/) *externally managed* — bare `pip install` fails with `externally-managed-environment`. Use a **virtual environment** (same as any other PyPI library):
36
+
35
37
  ```bash
38
+ python3 -m venv .venv
39
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
36
40
  pip install fluctlightdb
37
41
  ```
38
42
 
43
+ From a clone of this repo you can also run:
44
+
45
+ ```bash
46
+ ./scripts/install-python-client.sh
47
+ ```
48
+
39
49
  Optional in-process recall (Rust extension, when wheels are available for your platform):
40
50
 
41
51
  ```bash
@@ -75,17 +85,22 @@ client = FluctlightClient.from_env()
75
85
  print(client.activate("dark mode"))
76
86
  ```
77
87
 
78
- ## In-process recall (optional)
88
+ ## In-process brain (like `sqlite3`)
79
89
 
80
90
  When `fluctlightdb-native` is installed:
81
91
 
82
92
  ```python
83
- from fluctlightdb import get_recall_client
93
+ from fluctlightdb import connect
84
94
 
85
- brain = get_recall_client("~/.fluctlight/tenants/default/brain")
95
+ brain = connect("/tmp/my-agent-brain")
96
+ brain.experience("user prefers dark mode", context="settings")
86
97
  print(brain.activate("dark mode"))
87
98
  ```
88
99
 
100
+ Read-only recall path: `get_recall_client(path)`.
101
+
102
+ ## In-process recall (read-only helper)
103
+
89
104
  ## Docs
90
105
 
91
106
  - [Getting started](https://github.com/voxmastery/FluctlightDB/blob/main/docs/GETTING_STARTED.md)
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  fluctlightdb/__init__.py
5
+ fluctlightdb/brain.py
5
6
  fluctlightdb/worker.py
6
7
  fluctlightdb.egg-info/PKG-INFO
7
8
  fluctlightdb.egg-info/SOURCES.txt
@@ -0,0 +1,3 @@
1
+
2
+ [native]
3
+ fluctlightdb-native>=0.4.3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fluctlightdb"
7
- version = "0.4.2"
7
+ version = "0.4.3"
8
8
  description = "Python client for FluctlightDB — brain-native memory for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -32,7 +32,7 @@ Repository = "https://github.com/voxmastery/FluctlightDB"
32
32
  Issues = "https://github.com/voxmastery/FluctlightDB/issues"
33
33
 
34
34
  [project.optional-dependencies]
35
- native = ["fluctlightdb-native>=0.4.2"]
35
+ native = ["fluctlightdb-native>=0.4.3"]
36
36
 
37
37
  [tool.setuptools.packages.find]
38
38
  where = ["."]
@@ -1,3 +0,0 @@
1
-
2
- [native]
3
- fluctlightdb-native>=0.4.2
File without changes
File without changes