seren-loci 0.1.0__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.
- seren_loci-0.1.0/PKG-INFO +169 -0
- seren_loci-0.1.0/README.md +143 -0
- seren_loci-0.1.0/SerenLoci.pyproj +82 -0
- seren_loci-0.1.0/pyproject.toml +91 -0
- seren_loci-0.1.0/seren-loci.service.sample +47 -0
- seren_loci-0.1.0/seren-loci.yaml.sample +61 -0
- seren_loci-0.1.0/seren_loci/__init__.py +17 -0
- seren_loci-0.1.0/seren_loci/__main__.py +79 -0
- seren_loci-0.1.0/seren_loci/_version.py +24 -0
- seren_loci-0.1.0/seren_loci/app.py +165 -0
- seren_loci-0.1.0/seren_loci/config.py +101 -0
- seren_loci-0.1.0/seren_loci/mcp/__init__.py +15 -0
- seren_loci-0.1.0/seren_loci/mcp/server.py +161 -0
- seren_loci-0.1.0/seren_loci/mcp/tools.py +179 -0
- seren_loci-0.1.0/seren_loci/models/__init__.py +20 -0
- seren_loci-0.1.0/seren_loci/models/schemas.py +120 -0
- seren_loci-0.1.0/seren_loci/routes/__init__.py +1 -0
- seren_loci-0.1.0/seren_loci/routes/fact.py +71 -0
- seren_loci-0.1.0/seren_loci/routes/facts.py +31 -0
- seren_loci-0.1.0/seren_loci/routes/search.py +29 -0
- seren_loci-0.1.0/seren_loci/store.py +614 -0
- seren_loci-0.1.0/seren_loci/tests/conftest.py +44 -0
- seren_loci-0.1.0/seren_loci/tests/test_embedder_reconcile.py +203 -0
- seren_loci-0.1.0/seren_loci/tests/test_mcp_mount.py +60 -0
- seren_loci-0.1.0/seren_loci/tests/test_mcp_tools.py +114 -0
- seren_loci-0.1.0/seren_loci/tests/test_routes.py +104 -0
- seren_loci-0.1.0/seren_loci/tests/test_store.py +178 -0
- seren_loci-0.1.0/seren_loci/tests/test_vector_sql.py +65 -0
- seren_loci-0.1.0/seren_loci/viewer/loci.html +504 -0
- seren_loci-0.1.0/seren_loci.egg-info/PKG-INFO +169 -0
- seren_loci-0.1.0/seren_loci.egg-info/SOURCES.txt +41 -0
- seren_loci-0.1.0/seren_loci.egg-info/dependency_links.txt +1 -0
- seren_loci-0.1.0/seren_loci.egg-info/entry_points.txt +2 -0
- seren_loci-0.1.0/seren_loci.egg-info/requires.txt +20 -0
- seren_loci-0.1.0/seren_loci.egg-info/top_level.txt +1 -0
- seren_loci-0.1.0/setup.cfg +4 -0
- seren_loci-0.1.0/tests/conftest.py +44 -0
- seren_loci-0.1.0/tests/test_embedder_reconcile.py +203 -0
- seren_loci-0.1.0/tests/test_mcp_mount.py +60 -0
- seren_loci-0.1.0/tests/test_mcp_tools.py +114 -0
- seren_loci-0.1.0/tests/test_routes.py +104 -0
- seren_loci-0.1.0/tests/test_store.py +178 -0
- seren_loci-0.1.0/tests/test_vector_sql.py +65 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seren-loci
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Keyed facts/logic memory for Seren. The left brain - addressable, deterministic, one live value per key.
|
|
5
|
+
Author: Chad Roesler
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Keywords: llm,memory,facts,logic,sqlite,sqlite-vec,seren
|
|
8
|
+
Requires-Python: <3.13,>=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: fastapi>=0.115.0
|
|
11
|
+
Requires-Dist: uvicorn[standard]>=0.32.0
|
|
12
|
+
Requires-Dist: pydantic>=2.9.0
|
|
13
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
14
|
+
Requires-Dist: httpx>=0.27.0
|
|
15
|
+
Provides-Extra: vector
|
|
16
|
+
Requires-Dist: sqlite-vec>=0.1.6; extra == "vector"
|
|
17
|
+
Requires-Dist: sentence-transformers>=3.0; extra == "vector"
|
|
18
|
+
Provides-Extra: mcp
|
|
19
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
20
|
+
Provides-Extra: corp
|
|
21
|
+
Requires-Dist: truststore>=0.9; extra == "corp"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
25
|
+
Requires-Dist: httpx2>=2.3; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# SerenLoci
|
|
28
|
+
|
|
29
|
+
**The left brain.** A keyed facts-and-logic store for the Seren constellation —
|
|
30
|
+
addressable, deterministic, exactly one live value per key.
|
|
31
|
+
|
|
32
|
+
Where [SerenMemory](https://github.com/ChadRoesler/SerenMemory) (the right
|
|
33
|
+
brain) holds fuzzy, episodic memory — *"we ground on the embedder migration for
|
|
34
|
+
a week, it was a slog"* — Loci holds **facts**:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
{ project, key, value, why }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`camelCase is life`. `braces on a new line in posh`. `GGML_CUDA_NO_VMM=ON must
|
|
41
|
+
be set at compile time` — *because the env var isn't honored at runtime on
|
|
42
|
+
Jetson*. A locus has an **address**. You go *to* it and get *the* thing — you
|
|
43
|
+
don't grope around for something that rhymes.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Why it's shaped like this
|
|
48
|
+
|
|
49
|
+
**One live value per key, enforced by the database.** Set a new value for a key
|
|
50
|
+
and the old one is *superseded* — kept as history, pointed at by the new row,
|
|
51
|
+
**never blended**. A vibed-together fact is worse than no fact, so the rule is
|
|
52
|
+
strict, and it's not enforced by hope: a `PARTIAL UNIQUE INDEX` makes sqlite
|
|
53
|
+
physically refuse a second live row per `(project, key)`.
|
|
54
|
+
|
|
55
|
+
**Two tiers.** A reserved project of `*` is the *fundamentals* tier —
|
|
56
|
+
cross-project truths. A concrete project name (`seren-memory`) is the
|
|
57
|
+
per-project tier — the targeted override. Same split as nano/xavier/dgx
|
|
58
|
+
prebuilts: the platform-wide truth and the per-board variant.
|
|
59
|
+
|
|
60
|
+
**The `why` is the point.** A logic store without rationale is just a
|
|
61
|
+
dictionary. The value tells you *what*; the why is what stops you re-learning it
|
|
62
|
+
the painful way — and it's searchable, so "that CUDA thing" finds a fact whose
|
|
63
|
+
*reason* mentions CUDA even when the key doesn't.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## The floor is free
|
|
68
|
+
|
|
69
|
+
Three access rungs, cheapest first:
|
|
70
|
+
|
|
71
|
+
1. **Exact** — `get_fact(project, key)` returns the live value, deterministically.
|
|
72
|
+
No embedding, no ranking. You know the address, you get the thing.
|
|
73
|
+
2. **Lexical** — FTS5 full-text over `(key, value, why)` of the live rows. The
|
|
74
|
+
"I sort of remember the words" path.
|
|
75
|
+
3. **Vector** *(additive)* — a [sqlite-vec](https://github.com/asg017/sqlite-vec)
|
|
76
|
+
index for the "this smells like that CUDA thing" associative jump. Built
|
|
77
|
+
**only** when you name an embedder.
|
|
78
|
+
|
|
79
|
+
Rungs 1 and 2 need nothing but sqlite (stdlib) and the web stack — **no torch,
|
|
80
|
+
no GPU, the 4GB-laptop floor**. The vector finder is the ceiling, opted into by
|
|
81
|
+
install, never required. *Where does it sit? How much does it need?* — the floor
|
|
82
|
+
needs almost nothing.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Install
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install seren-loci # the floor: exact + FTS5 lexical
|
|
90
|
+
pip install seren-loci[vector] # + the sqlite-vec associative finder (pulls torch)
|
|
91
|
+
pip install seren-loci[mcp] # + the MCP surface (model reaches facts directly)
|
|
92
|
+
pip install seren-loci[corp] # + OS-trust-store TLS for corp-proxied boxes
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Extras stack: `pip install seren-loci[vector,mcp]`.
|
|
96
|
+
|
|
97
|
+
## Run
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
seren-loci # or: python -m seren_loci
|
|
101
|
+
seren-loci --config ./seren-loci.yaml
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Listens on **7422** by default (neighbor convention: memory 7420, margin 7421,
|
|
105
|
+
loci 7422).
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# set a fundamental truth
|
|
109
|
+
curl -X POST localhost:7422/fact -H 'content-type: application/json' \
|
|
110
|
+
-d '{"key":"posh.brace_style","value":"curly brackets on a new line","why":"readability"}'
|
|
111
|
+
|
|
112
|
+
# get it back, deterministically
|
|
113
|
+
curl 'localhost:7422/fact?key=posh.brace_style'
|
|
114
|
+
|
|
115
|
+
# discovery search (exact-key first, then the finder)
|
|
116
|
+
curl -X POST localhost:7422/search -H 'content-type: application/json' \
|
|
117
|
+
-d '{"query":"cuda runtime"}'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## API
|
|
123
|
+
|
|
124
|
+
| Method | Path | What |
|
|
125
|
+
|--------|------------------|--------------------------------------------------------|
|
|
126
|
+
| POST | `/fact` | Set/replace a fact (strict supersede). Names the superseded id, or null. |
|
|
127
|
+
| GET | `/fact` | The live value for `?project=&key=` (project defaults to `*`). 404 if none. |
|
|
128
|
+
| GET | `/fact/history` | Every value a key has held, newest first. |
|
|
129
|
+
| DELETE | `/fact` | Retire (soft-supersede) the live value for a key. |
|
|
130
|
+
| GET | `/facts` | List facts in scope (`?project=&include_superseded=`). |
|
|
131
|
+
| GET | `/counts` | `{live, history, projects}`. |
|
|
132
|
+
| POST | `/search` | Exact + finder discovery, ranked. |
|
|
133
|
+
| GET | `/` `/health` | Service info / liveness. |
|
|
134
|
+
|
|
135
|
+
Every search hit carries a normalized **0–1 score** (`exact`→1.0,
|
|
136
|
+
`vector`→`1/(1+distance)`, `lexical`→bm25 mapped above 0). That's the common
|
|
137
|
+
currency a future **SerenCorpusCallosum** uses to merge left-brain and
|
|
138
|
+
right-brain results on one axis instead of comparing cosines to key-hits.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Config
|
|
143
|
+
|
|
144
|
+
`seren-loci.yaml` (all optional — defaults are a working zero-config dev setup).
|
|
145
|
+
Env vars (`SEREN_LOCI_*`) override the file.
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
server:
|
|
149
|
+
host: 0.0.0.0
|
|
150
|
+
port: 7422
|
|
151
|
+
bearer_token: "" # empty = no auth (trusted LAN)
|
|
152
|
+
storage:
|
|
153
|
+
db_path: ~/.seren-loci/loci.db
|
|
154
|
+
embedding_model: # null = floor (no torch). name one to light the vector finder.
|
|
155
|
+
embedding_device: cpu
|
|
156
|
+
tls:
|
|
157
|
+
trust_system_store: false # true (+ [corp]) for TLS-intercepting corp proxies
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Where it sits in the constellation
|
|
163
|
+
|
|
164
|
+
- **SerenMemory** — the right brain. Fuzzy, consolidated, episodic. General-purpose AI memory protocol.
|
|
165
|
+
- **SerenLoci** — *this*. The left brain. Keyed facts, deterministic, strict-supersede.
|
|
166
|
+
- **SerenMargin** — opinionated private notes, opt-in by deploy.
|
|
167
|
+
- **SerenCorpusCallosum** *(planned)* — fans a query across both hemispheres and merges on the shared score currency.
|
|
168
|
+
|
|
169
|
+
Build for the floor, not the ceiling. The Nano is the floor, not the cap. GPL-3.0-or-later. Rip it and win. 🌭🔧
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SerenLoci
|
|
2
|
+
|
|
3
|
+
**The left brain.** A keyed facts-and-logic store for the Seren constellation —
|
|
4
|
+
addressable, deterministic, exactly one live value per key.
|
|
5
|
+
|
|
6
|
+
Where [SerenMemory](https://github.com/ChadRoesler/SerenMemory) (the right
|
|
7
|
+
brain) holds fuzzy, episodic memory — *"we ground on the embedder migration for
|
|
8
|
+
a week, it was a slog"* — Loci holds **facts**:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
{ project, key, value, why }
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`camelCase is life`. `braces on a new line in posh`. `GGML_CUDA_NO_VMM=ON must
|
|
15
|
+
be set at compile time` — *because the env var isn't honored at runtime on
|
|
16
|
+
Jetson*. A locus has an **address**. You go *to* it and get *the* thing — you
|
|
17
|
+
don't grope around for something that rhymes.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Why it's shaped like this
|
|
22
|
+
|
|
23
|
+
**One live value per key, enforced by the database.** Set a new value for a key
|
|
24
|
+
and the old one is *superseded* — kept as history, pointed at by the new row,
|
|
25
|
+
**never blended**. A vibed-together fact is worse than no fact, so the rule is
|
|
26
|
+
strict, and it's not enforced by hope: a `PARTIAL UNIQUE INDEX` makes sqlite
|
|
27
|
+
physically refuse a second live row per `(project, key)`.
|
|
28
|
+
|
|
29
|
+
**Two tiers.** A reserved project of `*` is the *fundamentals* tier —
|
|
30
|
+
cross-project truths. A concrete project name (`seren-memory`) is the
|
|
31
|
+
per-project tier — the targeted override. Same split as nano/xavier/dgx
|
|
32
|
+
prebuilts: the platform-wide truth and the per-board variant.
|
|
33
|
+
|
|
34
|
+
**The `why` is the point.** A logic store without rationale is just a
|
|
35
|
+
dictionary. The value tells you *what*; the why is what stops you re-learning it
|
|
36
|
+
the painful way — and it's searchable, so "that CUDA thing" finds a fact whose
|
|
37
|
+
*reason* mentions CUDA even when the key doesn't.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## The floor is free
|
|
42
|
+
|
|
43
|
+
Three access rungs, cheapest first:
|
|
44
|
+
|
|
45
|
+
1. **Exact** — `get_fact(project, key)` returns the live value, deterministically.
|
|
46
|
+
No embedding, no ranking. You know the address, you get the thing.
|
|
47
|
+
2. **Lexical** — FTS5 full-text over `(key, value, why)` of the live rows. The
|
|
48
|
+
"I sort of remember the words" path.
|
|
49
|
+
3. **Vector** *(additive)* — a [sqlite-vec](https://github.com/asg017/sqlite-vec)
|
|
50
|
+
index for the "this smells like that CUDA thing" associative jump. Built
|
|
51
|
+
**only** when you name an embedder.
|
|
52
|
+
|
|
53
|
+
Rungs 1 and 2 need nothing but sqlite (stdlib) and the web stack — **no torch,
|
|
54
|
+
no GPU, the 4GB-laptop floor**. The vector finder is the ceiling, opted into by
|
|
55
|
+
install, never required. *Where does it sit? How much does it need?* — the floor
|
|
56
|
+
needs almost nothing.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install seren-loci # the floor: exact + FTS5 lexical
|
|
64
|
+
pip install seren-loci[vector] # + the sqlite-vec associative finder (pulls torch)
|
|
65
|
+
pip install seren-loci[mcp] # + the MCP surface (model reaches facts directly)
|
|
66
|
+
pip install seren-loci[corp] # + OS-trust-store TLS for corp-proxied boxes
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Extras stack: `pip install seren-loci[vector,mcp]`.
|
|
70
|
+
|
|
71
|
+
## Run
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
seren-loci # or: python -m seren_loci
|
|
75
|
+
seren-loci --config ./seren-loci.yaml
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Listens on **7422** by default (neighbor convention: memory 7420, margin 7421,
|
|
79
|
+
loci 7422).
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# set a fundamental truth
|
|
83
|
+
curl -X POST localhost:7422/fact -H 'content-type: application/json' \
|
|
84
|
+
-d '{"key":"posh.brace_style","value":"curly brackets on a new line","why":"readability"}'
|
|
85
|
+
|
|
86
|
+
# get it back, deterministically
|
|
87
|
+
curl 'localhost:7422/fact?key=posh.brace_style'
|
|
88
|
+
|
|
89
|
+
# discovery search (exact-key first, then the finder)
|
|
90
|
+
curl -X POST localhost:7422/search -H 'content-type: application/json' \
|
|
91
|
+
-d '{"query":"cuda runtime"}'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## API
|
|
97
|
+
|
|
98
|
+
| Method | Path | What |
|
|
99
|
+
|--------|------------------|--------------------------------------------------------|
|
|
100
|
+
| POST | `/fact` | Set/replace a fact (strict supersede). Names the superseded id, or null. |
|
|
101
|
+
| GET | `/fact` | The live value for `?project=&key=` (project defaults to `*`). 404 if none. |
|
|
102
|
+
| GET | `/fact/history` | Every value a key has held, newest first. |
|
|
103
|
+
| DELETE | `/fact` | Retire (soft-supersede) the live value for a key. |
|
|
104
|
+
| GET | `/facts` | List facts in scope (`?project=&include_superseded=`). |
|
|
105
|
+
| GET | `/counts` | `{live, history, projects}`. |
|
|
106
|
+
| POST | `/search` | Exact + finder discovery, ranked. |
|
|
107
|
+
| GET | `/` `/health` | Service info / liveness. |
|
|
108
|
+
|
|
109
|
+
Every search hit carries a normalized **0–1 score** (`exact`→1.0,
|
|
110
|
+
`vector`→`1/(1+distance)`, `lexical`→bm25 mapped above 0). That's the common
|
|
111
|
+
currency a future **SerenCorpusCallosum** uses to merge left-brain and
|
|
112
|
+
right-brain results on one axis instead of comparing cosines to key-hits.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Config
|
|
117
|
+
|
|
118
|
+
`seren-loci.yaml` (all optional — defaults are a working zero-config dev setup).
|
|
119
|
+
Env vars (`SEREN_LOCI_*`) override the file.
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
server:
|
|
123
|
+
host: 0.0.0.0
|
|
124
|
+
port: 7422
|
|
125
|
+
bearer_token: "" # empty = no auth (trusted LAN)
|
|
126
|
+
storage:
|
|
127
|
+
db_path: ~/.seren-loci/loci.db
|
|
128
|
+
embedding_model: # null = floor (no torch). name one to light the vector finder.
|
|
129
|
+
embedding_device: cpu
|
|
130
|
+
tls:
|
|
131
|
+
trust_system_store: false # true (+ [corp]) for TLS-intercepting corp proxies
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Where it sits in the constellation
|
|
137
|
+
|
|
138
|
+
- **SerenMemory** — the right brain. Fuzzy, consolidated, episodic. General-purpose AI memory protocol.
|
|
139
|
+
- **SerenLoci** — *this*. The left brain. Keyed facts, deterministic, strict-supersede.
|
|
140
|
+
- **SerenMargin** — opinionated private notes, opt-in by deploy.
|
|
141
|
+
- **SerenCorpusCallosum** *(planned)* — fans a query across both hemispheres and merges on the shared score currency.
|
|
142
|
+
|
|
143
|
+
Build for the floor, not the ceiling. The Nano is the floor, not the cap. GPL-3.0-or-later. Rip it and win. 🌭🔧
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
|
2
|
+
<PropertyGroup>
|
|
3
|
+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
4
|
+
<SchemaVersion>2.0</SchemaVersion>
|
|
5
|
+
<ProjectGuid>e5ad2b2f-4465-4a6d-b491-02c5819d2234</ProjectGuid>
|
|
6
|
+
<ProjectHome>.</ProjectHome>
|
|
7
|
+
<StartupFile>
|
|
8
|
+
</StartupFile>
|
|
9
|
+
<SearchPath>
|
|
10
|
+
</SearchPath>
|
|
11
|
+
<WorkingDirectory>.</WorkingDirectory>
|
|
12
|
+
<OutputPath>.</OutputPath>
|
|
13
|
+
<Name>SerenLoci</Name>
|
|
14
|
+
<RootNamespace>SerenLoci</RootNamespace>
|
|
15
|
+
</PropertyGroup>
|
|
16
|
+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
|
17
|
+
<DebugSymbols>true</DebugSymbols>
|
|
18
|
+
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
|
19
|
+
</PropertyGroup>
|
|
20
|
+
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
|
21
|
+
<DebugSymbols>true</DebugSymbols>
|
|
22
|
+
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
|
23
|
+
</PropertyGroup>
|
|
24
|
+
<ItemGroup>
|
|
25
|
+
<Folder Include="seren_loci\" />
|
|
26
|
+
<Folder Include="seren_loci\mcp\" />
|
|
27
|
+
<Folder Include="seren_loci\models\" />
|
|
28
|
+
<Folder Include="seren_loci\routes\" />
|
|
29
|
+
<Folder Include="seren_loci\tests\" />
|
|
30
|
+
<Folder Include="seren_loci\tests\__pycache__\" />
|
|
31
|
+
<Folder Include="seren_loci\viewer\" />
|
|
32
|
+
<Folder Include="tests\" />
|
|
33
|
+
<Folder Include="tests\__pycache__\" />
|
|
34
|
+
</ItemGroup>
|
|
35
|
+
<ItemGroup>
|
|
36
|
+
<Compile Include="seren_loci\app.py" />
|
|
37
|
+
<Compile Include="seren_loci\config.py" />
|
|
38
|
+
<Compile Include="seren_loci\mcp\server.py" />
|
|
39
|
+
<Compile Include="seren_loci\mcp\tools.py" />
|
|
40
|
+
<Compile Include="seren_loci\mcp\__init__.py" />
|
|
41
|
+
<Compile Include="seren_loci\models\schemas.py" />
|
|
42
|
+
<Compile Include="seren_loci\models\__init__.py" />
|
|
43
|
+
<Compile Include="seren_loci\routes\fact.py" />
|
|
44
|
+
<Compile Include="seren_loci\routes\facts.py" />
|
|
45
|
+
<Compile Include="seren_loci\routes\search.py" />
|
|
46
|
+
<Compile Include="seren_loci\routes\__init__.py" />
|
|
47
|
+
<Compile Include="seren_loci\store.py" />
|
|
48
|
+
<Compile Include="seren_loci\tests\conftest.py" />
|
|
49
|
+
<Compile Include="seren_loci\tests\test_embedder_reconcile.py" />
|
|
50
|
+
<Compile Include="seren_loci\tests\test_mcp_mount.py" />
|
|
51
|
+
<Compile Include="seren_loci\tests\test_mcp_tools.py" />
|
|
52
|
+
<Compile Include="seren_loci\tests\test_routes.py" />
|
|
53
|
+
<Compile Include="seren_loci\tests\test_store.py" />
|
|
54
|
+
<Compile Include="seren_loci\tests\test_vector_sql.py" />
|
|
55
|
+
<Compile Include="seren_loci\__init__.py" />
|
|
56
|
+
<Compile Include="seren_loci\__main__.py" />
|
|
57
|
+
<Compile Include="tests\conftest.py" />
|
|
58
|
+
<Compile Include="tests\test_embedder_reconcile.py" />
|
|
59
|
+
<Compile Include="tests\test_mcp_mount.py" />
|
|
60
|
+
<Compile Include="tests\test_mcp_tools.py" />
|
|
61
|
+
<Compile Include="tests\test_routes.py" />
|
|
62
|
+
<Compile Include="tests\test_store.py" />
|
|
63
|
+
<Compile Include="tests\test_vector_sql.py" />
|
|
64
|
+
</ItemGroup>
|
|
65
|
+
<ItemGroup>
|
|
66
|
+
<Content Include="pyproject.toml" />
|
|
67
|
+
<Content Include="seren-loci.service.sample" />
|
|
68
|
+
<Content Include="seren-loci.yaml.sample" />
|
|
69
|
+
<Content Include="seren_loci\tests\__pycache__\conftest.cpython-311-pytest-9.0.3.pyc" />
|
|
70
|
+
<Content Include="seren_loci\viewer\loci.html" />
|
|
71
|
+
<Content Include="tests\__pycache__\conftest.cpython-311-pytest-9.0.3.pyc" />
|
|
72
|
+
</ItemGroup>
|
|
73
|
+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
|
74
|
+
<!-- Uncomment the CoreCompile target to enable the Build command in
|
|
75
|
+
Visual Studio and specify your pre- and post-build commands in
|
|
76
|
+
the BeforeBuild and AfterBuild targets below. -->
|
|
77
|
+
<!--<Target Name="CoreCompile" />-->
|
|
78
|
+
<Target Name="BeforeBuild">
|
|
79
|
+
</Target>
|
|
80
|
+
<Target Name="AfterBuild">
|
|
81
|
+
</Target>
|
|
82
|
+
</Project>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "setuptools-scm>=8", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seren-loci"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Keyed facts/logic memory for Seren. The left brain - addressable, deterministic, one live value per key."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
# Upper bound mirrors SerenMemory, but the REASON differs: Loci's floor (exact
|
|
11
|
+
# + FTS5) is pure sqlite + the web stack and would run fine anywhere. The cap
|
|
12
|
+
# exists for the OPTIONAL [vector] extra - sentence-transformers pulls torch,
|
|
13
|
+
# and torch's 3.13 wheel gap is the wall. Capping here keeps `pip install
|
|
14
|
+
# seren-loci[vector]` resolving cleanly instead of exploding on a torch build.
|
|
15
|
+
# LIFT THE <3.13 the day torch ships 3.13 wheels (bump the CI matrix to match
|
|
16
|
+
# in the same commit).
|
|
17
|
+
requires-python = ">=3.10,<3.13"
|
|
18
|
+
license = { text = "GPL-3.0-or-later" }
|
|
19
|
+
authors = [{ name = "Chad Roesler" }]
|
|
20
|
+
keywords = ["llm", "memory", "facts", "logic", "sqlite", "sqlite-vec", "seren"]
|
|
21
|
+
|
|
22
|
+
# THE FLOOR IS DEP-LIGHT ON PURPOSE. Exact-key + FTS5 lexical search need
|
|
23
|
+
# nothing but sqlite (stdlib) and the web stack. The associative vector finder
|
|
24
|
+
# - sqlite-vec + sentence-transformers - is the [vector] extra below, so the
|
|
25
|
+
# Nano / 4GB-laptop floor never pulls torch unless an operator opts into the
|
|
26
|
+
# "this smells like that CUDA thing" jump. Structural opt-in: capability by
|
|
27
|
+
# install choice, not a runtime flag.
|
|
28
|
+
dependencies = [
|
|
29
|
+
"fastapi>=0.115.0",
|
|
30
|
+
"uvicorn[standard]>=0.32.0",
|
|
31
|
+
"pydantic>=2.9.0",
|
|
32
|
+
"pyyaml>=6.0.2",
|
|
33
|
+
"httpx>=0.27.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
vector = [
|
|
38
|
+
# The associative finder. sqlite-vec is the index (MIT/Apache, pure C,
|
|
39
|
+
# ~60KB, arm64 wheels exist - drops onto Jetson without the build saga);
|
|
40
|
+
# it's brute-force KNN, which is CORRECT for a facts table this size, not a
|
|
41
|
+
# compromise. sentence-transformers turns fact text into the vectors it
|
|
42
|
+
# searches. Pinned >=0.1.6 (sqlite-vec is on the 0.1.x line; a >=1.0 pin
|
|
43
|
+
# would never resolve). Opt-in: `pip install seren-loci[vector]`.
|
|
44
|
+
"sqlite-vec>=0.1.6",
|
|
45
|
+
"sentence-transformers>=3.0",
|
|
46
|
+
]
|
|
47
|
+
mcp = [
|
|
48
|
+
# The Python MCP SDK from Anthropic (FastMCP merged in). Lets a connected
|
|
49
|
+
# Rhys set_fact/get_fact/search the left brain directly. Same loose pin as
|
|
50
|
+
# SerenMemory - the SDK moves fast and our server tolerates transport drift.
|
|
51
|
+
"mcp>=1.0",
|
|
52
|
+
]
|
|
53
|
+
corp = [
|
|
54
|
+
# Corporate / intercepting-proxy support. Routes Python TLS through the OS
|
|
55
|
+
# trust store so a corp root CA (Zscaler/Netskope/etc.) is honored without
|
|
56
|
+
# hand-exporting .pem files. Opt-in: `pip install seren-loci[corp]` +
|
|
57
|
+
# tls.trust_system_store: true (the --corp installer flag sets both).
|
|
58
|
+
"truststore>=0.9",
|
|
59
|
+
]
|
|
60
|
+
dev = [
|
|
61
|
+
"pytest>=8.0",
|
|
62
|
+
"pytest-asyncio>=0.23",
|
|
63
|
+
# silences the starlette->httpx deprecation warning under fastapi.testclient
|
|
64
|
+
# and future-proofs the test stack against the hard cutover (same as memory).
|
|
65
|
+
"httpx2>=2.3",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[project.scripts]
|
|
69
|
+
seren-loci = "seren_loci.__main__:main"
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.packages.find]
|
|
72
|
+
where = ["."]
|
|
73
|
+
include = ["seren_loci*"]
|
|
74
|
+
|
|
75
|
+
# Ship the viewer HTML inside the wheel when it lands. Same gotcha as
|
|
76
|
+
# SerenMemory: pip strips non-.py files, so GET /viewer would 404 on an
|
|
77
|
+
# installed copy without this. Harmless before the viewer exists (the glob just
|
|
78
|
+
# matches nothing).
|
|
79
|
+
[tool.setuptools.package-data]
|
|
80
|
+
seren_loci = ["viewer/*.html"]
|
|
81
|
+
|
|
82
|
+
[tool.setuptools_scm]
|
|
83
|
+
# .git is one level above pyproject.toml (repo root, not the SerenLoci subdir)
|
|
84
|
+
root = ".."
|
|
85
|
+
version_scheme = "release-branch-semver"
|
|
86
|
+
# Tag-derived version written into the package (shipped in the wheel, read by
|
|
87
|
+
# __init__ / importlib.metadata). _version.py is gitignored - build artifact.
|
|
88
|
+
version_file = "seren_loci/_version.py"
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# SerenLoci systemd unit (sample)
|
|
2
|
+
#
|
|
3
|
+
# Install:
|
|
4
|
+
# 1. Edit __TARGET_USER__ and the paths below
|
|
5
|
+
# 2. sudo cp seren-loci.service /etc/systemd/system/
|
|
6
|
+
# 3. sudo systemctl daemon-reload
|
|
7
|
+
# 4. sudo systemctl enable --now seren-loci
|
|
8
|
+
#
|
|
9
|
+
# Logs: journalctl -u seren-loci -f
|
|
10
|
+
#
|
|
11
|
+
# Tip: you usually don't hand-roll this - the setup scripts generate it for you.
|
|
12
|
+
# bash seren-loci-setup.sh --service (Linux, via the generic core)
|
|
13
|
+
# This sample is for understanding what they produce, or rolling your own.
|
|
14
|
+
|
|
15
|
+
[Unit]
|
|
16
|
+
Description=SerenLoci - keyed facts / the left brain
|
|
17
|
+
After=network-online.target
|
|
18
|
+
Wants=network-online.target
|
|
19
|
+
|
|
20
|
+
[Service]
|
|
21
|
+
Type=simple
|
|
22
|
+
User=__TARGET_USER__
|
|
23
|
+
WorkingDirectory=/home/__TARGET_USER__/seren-loci
|
|
24
|
+
|
|
25
|
+
# Use the venv's python. Adjust the venv path to wherever you installed.
|
|
26
|
+
ExecStart=/home/__TARGET_USER__/seren-venvs/loci/bin/python -m seren_loci --config /home/__TARGET_USER__/seren-loci/seren-loci.yaml
|
|
27
|
+
|
|
28
|
+
# Force UTF-8 stdio regardless of locale, so an emoji or smart-quote in a
|
|
29
|
+
# fact's why-text can never crash the logger. The installer sets this too.
|
|
30
|
+
Environment=PYTHONUTF8=1
|
|
31
|
+
|
|
32
|
+
# Config can also come from env - uncomment + set as needed:
|
|
33
|
+
# Environment=SEREN_LOCI_PORT=7422
|
|
34
|
+
# Environment=SEREN_LOCI_BEARER_TOKEN=
|
|
35
|
+
# Environment=SEREN_LOCI_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
|
36
|
+
|
|
37
|
+
Restart=on-failure
|
|
38
|
+
RestartSec=5
|
|
39
|
+
|
|
40
|
+
# The floor (sqlite + FTS5) barely touches memory. Only the OPTIONAL [vector]
|
|
41
|
+
# embedder wants headroom - sentence-transformers plus a small model is a few
|
|
42
|
+
# hundred MB. 1G is comfortable for all-MiniLM-L6-v2; bump it if you point
|
|
43
|
+
# embedding_model at something larger.
|
|
44
|
+
MemoryMax=1G
|
|
45
|
+
|
|
46
|
+
[Install]
|
|
47
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2
|
+
# seren-loci.yaml - config for SerenLoci (the left brain)
|
|
3
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
4
|
+
#
|
|
5
|
+
# Copy to seren-loci.yaml, edit, and run:
|
|
6
|
+
# python -m seren_loci --config seren-loci.yaml
|
|
7
|
+
#
|
|
8
|
+
# Or just run with no config at all - the defaults below are baked in, so
|
|
9
|
+
# `python -m seren_loci` works zero-config for a local dev spin.
|
|
10
|
+
#
|
|
11
|
+
# Env vars override file values (handy for Docker/systemd):
|
|
12
|
+
# SEREN_LOCI_PORT, SEREN_LOCI_HOST, SEREN_LOCI_BEARER_TOKEN,
|
|
13
|
+
# SEREN_LOCI_DB_PATH, SEREN_LOCI_EMBEDDING_MODEL,
|
|
14
|
+
# SEREN_LOCI_TRUST_SYSTEM_STORE
|
|
15
|
+
#
|
|
16
|
+
# What this is: keyed, deterministic facts - exactly one live value per
|
|
17
|
+
# (project, key), old values kept as history, never blended. Where
|
|
18
|
+
# SerenMemory is fuzzy episodic recall (the right brain), Loci is the
|
|
19
|
+
# addressable logic store (the left). Notably ABSENT vs memory: no
|
|
20
|
+
# consolidator, no tiers, no embedder safe-mode. The finder is a rebuildable
|
|
21
|
+
# index over the fact text, so there's nothing to migrate and nothing to gate.
|
|
22
|
+
#
|
|
23
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
server:
|
|
26
|
+
host: 0.0.0.0
|
|
27
|
+
port: 7422 # neighbor convention: memory 7420, margin 7421, loci 7422
|
|
28
|
+
# Leave empty for no auth (trusted LAN / dev). Set a token to require
|
|
29
|
+
# Authorization: Bearer <token>
|
|
30
|
+
# on every endpoint except /, /health, and /viewer.
|
|
31
|
+
bearer_token: ""
|
|
32
|
+
|
|
33
|
+
storage:
|
|
34
|
+
# The WHOLE left brain is one sqlite file. ~ is expanded; the parent dir is
|
|
35
|
+
# created on first run. Back THIS up - it survives package upgrades untouched.
|
|
36
|
+
db_path: ~/.seren-loci/loci.db
|
|
37
|
+
|
|
38
|
+
# OPTIONAL associative finder (the "this smells like that CUDA thing" jump).
|
|
39
|
+
# null / unset -> embedding-free FLOOR: exact-key + FTS5 lexical only.
|
|
40
|
+
# Zero vector deps, no torch. Runs on a 4GB laptop / Nano.
|
|
41
|
+
# a model name -> builds a sqlite-vec index over the facts so search can
|
|
42
|
+
# make the semantic jump. Exact-key hits ALWAYS still lead
|
|
43
|
+
# at score 1.0; the finder only fills in below them.
|
|
44
|
+
#
|
|
45
|
+
# Requires the vector extra: pip install seren-loci[vector]
|
|
46
|
+
# (or run the installer with --vector / -Vector, which sets this for you).
|
|
47
|
+
# all-MiniLM-L6-v2 is ~80MB, CPU-friendly, downloaded on first use.
|
|
48
|
+
embedding_model: null
|
|
49
|
+
# embedding_model: sentence-transformers/all-MiniLM-L6-v2
|
|
50
|
+
embedding_device: cpu # "cpu", or "cuda" if you've got the VRAM to spare
|
|
51
|
+
|
|
52
|
+
# -- Corporate / intercepting-proxy support (opt-in) ------------------------
|
|
53
|
+
# If you're on a corporate network that does TLS interception (Zscaler,
|
|
54
|
+
# Netskope, etc.), the proxy's root CA is in your OS trust store but not in
|
|
55
|
+
# Python's bundled certs, so the embedder's model download fails with
|
|
56
|
+
# CERTIFICATE_VERIFY_FAILED. Turn this on to route TLS through the OS store.
|
|
57
|
+
#
|
|
58
|
+
# Requires the corp extra: pip install seren-loci[corp]
|
|
59
|
+
# Or run the installer with --corp / -Corp (sets this and installs the extra).
|
|
60
|
+
# tls:
|
|
61
|
+
# trust_system_store: true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SerenLoci - the left brain. A keyed facts/logic store for the Seren
|
|
3
|
+
constellation: addressable, deterministic, one live value per key.
|
|
4
|
+
|
|
5
|
+
The deterministic spine (exact + FTS5 lexical) runs embedding-free on the
|
|
6
|
+
floor. An optional sqlite-vec finder adds the associative jump when an
|
|
7
|
+
embedder is configured. Pairs with SerenLoci (the right brain) under a
|
|
8
|
+
future SerenCorpusCallosum.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from ._version import version as __version__
|
|
14
|
+
except Exception: # noqa: BLE001 - source checkout without a build
|
|
15
|
+
__version__ = "0.0.0+unknown"
|
|
16
|
+
|
|
17
|
+
__all__ = ["__version__"]
|