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.
Files changed (43) hide show
  1. seren_loci-0.1.0/PKG-INFO +169 -0
  2. seren_loci-0.1.0/README.md +143 -0
  3. seren_loci-0.1.0/SerenLoci.pyproj +82 -0
  4. seren_loci-0.1.0/pyproject.toml +91 -0
  5. seren_loci-0.1.0/seren-loci.service.sample +47 -0
  6. seren_loci-0.1.0/seren-loci.yaml.sample +61 -0
  7. seren_loci-0.1.0/seren_loci/__init__.py +17 -0
  8. seren_loci-0.1.0/seren_loci/__main__.py +79 -0
  9. seren_loci-0.1.0/seren_loci/_version.py +24 -0
  10. seren_loci-0.1.0/seren_loci/app.py +165 -0
  11. seren_loci-0.1.0/seren_loci/config.py +101 -0
  12. seren_loci-0.1.0/seren_loci/mcp/__init__.py +15 -0
  13. seren_loci-0.1.0/seren_loci/mcp/server.py +161 -0
  14. seren_loci-0.1.0/seren_loci/mcp/tools.py +179 -0
  15. seren_loci-0.1.0/seren_loci/models/__init__.py +20 -0
  16. seren_loci-0.1.0/seren_loci/models/schemas.py +120 -0
  17. seren_loci-0.1.0/seren_loci/routes/__init__.py +1 -0
  18. seren_loci-0.1.0/seren_loci/routes/fact.py +71 -0
  19. seren_loci-0.1.0/seren_loci/routes/facts.py +31 -0
  20. seren_loci-0.1.0/seren_loci/routes/search.py +29 -0
  21. seren_loci-0.1.0/seren_loci/store.py +614 -0
  22. seren_loci-0.1.0/seren_loci/tests/conftest.py +44 -0
  23. seren_loci-0.1.0/seren_loci/tests/test_embedder_reconcile.py +203 -0
  24. seren_loci-0.1.0/seren_loci/tests/test_mcp_mount.py +60 -0
  25. seren_loci-0.1.0/seren_loci/tests/test_mcp_tools.py +114 -0
  26. seren_loci-0.1.0/seren_loci/tests/test_routes.py +104 -0
  27. seren_loci-0.1.0/seren_loci/tests/test_store.py +178 -0
  28. seren_loci-0.1.0/seren_loci/tests/test_vector_sql.py +65 -0
  29. seren_loci-0.1.0/seren_loci/viewer/loci.html +504 -0
  30. seren_loci-0.1.0/seren_loci.egg-info/PKG-INFO +169 -0
  31. seren_loci-0.1.0/seren_loci.egg-info/SOURCES.txt +41 -0
  32. seren_loci-0.1.0/seren_loci.egg-info/dependency_links.txt +1 -0
  33. seren_loci-0.1.0/seren_loci.egg-info/entry_points.txt +2 -0
  34. seren_loci-0.1.0/seren_loci.egg-info/requires.txt +20 -0
  35. seren_loci-0.1.0/seren_loci.egg-info/top_level.txt +1 -0
  36. seren_loci-0.1.0/setup.cfg +4 -0
  37. seren_loci-0.1.0/tests/conftest.py +44 -0
  38. seren_loci-0.1.0/tests/test_embedder_reconcile.py +203 -0
  39. seren_loci-0.1.0/tests/test_mcp_mount.py +60 -0
  40. seren_loci-0.1.0/tests/test_mcp_tools.py +114 -0
  41. seren_loci-0.1.0/tests/test_routes.py +104 -0
  42. seren_loci-0.1.0/tests/test_store.py +178 -0
  43. 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__"]