personal memory agent
1[build-system]
2requires = ["setuptools==83.0.0", "wheel==0.47.0"]
3build-backend = "setuptools.build_meta"
4
5[project]
6name = "solstone"
7version = "1.0.17"
8description = "Navigate Life Intelligently"
9readme = "README.md"
10requires-python = ">=3.12"
11license = {text = "AGPL-3.0-only"}
12authors = [
13 {name = "Jer Miller", email = "jer@solpbc.org"}
14]
15classifiers = [
16 "Development Status :: 3 - Alpha",
17 "Intended Audience :: Developers",
18 "License :: OSI Approved :: GNU Affero General Public License v3",
19 "Operating System :: POSIX :: Linux",
20 "Operating System :: MacOS :: MacOS X",
21 "Programming Language :: Python :: 3",
22 "Programming Language :: Python :: 3.12",
23 "Programming Language :: Python :: 3.13",
24 "Topic :: Multimedia :: Sound/Audio :: Analysis",
25 "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
26 "Topic :: Scientific/Engineering :: Artificial Intelligence",
27]
28keywords = ["audio", "transcription", "screenshot", "multimodal", "ai", "gemini"]
29
30dependencies = [
31 # The thin `sol` access surface only. `pip install solstone` (the default)
32 # is a thin client that talks to a journal — it carries NO provider SDKs,
33 # no AI/media/ML runtime, no Flask. Install a journal host through
34 # `pip install solstone-journal` or `pip install solstone-journal-cuda`.
35 #
36 # This partition is the boundary the access-surface import-clean guard
37 # (scripts/check_access_imports_clean.py) and the extras-consistency lint
38 # (scripts/check_extras_consistency.py) enforce. Adding a dependency here
39 # must keep the `sol` access commands import-clean — do not add anything the
40 # journal host needs; that belongs in [journal-host] below.
41 "setproctitle",
42 "typer",
43 "requests",
44 # Link tunnel client (`sol link serve`/`join`, think/link/): TLS 1.3 in
45 # memory-BIO mode over an opaque WebSocket; pyOpenSSL supplies the
46 # handshake-time verify callback that stdlib ssl can't express.
47 "cryptography>=42,<47",
48 "pyOpenSSL>=24.0",
49 # argon2id for the journal-mark work factor (think/link/mark.py).
50 "argon2-cffi",
51 "websockets>=13.0",
52 "psutil",
53 "userpath>=1.9.2,<2",
54 "solstone-core==1.0.17; sys_platform == 'linux' and platform_machine == 'x86_64'",
55 "solstone-core==1.0.17; sys_platform == 'linux' and platform_machine == 'aarch64'",
56 "solstone-core==1.0.17; sys_platform == 'darwin' and platform_machine == 'arm64'",
57 "solstone-core-unsupported-platform==1.0.17; (sys_platform != 'linux' and sys_platform != 'darwin') or (sys_platform == 'linux' and platform_machine != 'x86_64' and platform_machine != 'aarch64') or (sys_platform == 'darwin' and platform_machine != 'arm64')",
58]
59
60[project.optional-dependencies]
61# ---------------------------------------------------------------------------
62# Install menu. `pip install solstone` is the thin access client above.
63# The journal is now its own package:
64#
65# pip install solstone-journal # the journal (CPU)
66# pip install solstone-journal-cuda # the journal on NVIDIA CUDA
67#
68# [journal] and [journal-cuda] are retired tombstones that fail loudly and point
69# old installs at the new leaf packages. They no longer install the journal.
70#
71# [journal-host], [pdf-import], [pdf-export], and [pdf] remain internal
72# building blocks. The leaf packages compose [journal-host] with either the CPU
73# ONNX runtime or the NVIDIA CUDA runtime; [pdf] is a convenience composite of
74# [pdf-import] and [pdf-export], while the split extras back focused feature hints.
75#
76# Base solstone owns the public POSIX launchers. The platform-native
77# `solstone-core` executable is installed as the launcher sibling. Install exactly
78# one journal leaf; the CPU and GPU ONNX wheels own the same `onnxruntime/`
79# import dir and clobber each other.
80# ---------------------------------------------------------------------------
81pdf-import = [
82 "pypdfium2>=5.11,<6",
83 "Pillow>=11.0.0",
84]
85
86pdf-export = [
87 "weasyprint>=60.0",
88]
89
90pdf = [
91 "solstone[pdf-import]",
92 "solstone[pdf-export]",
93]
94
95# The full journal-host stack EXCEPT the ONNX runtime package (which is the
96# CPU-vs-CUDA choice made by the solstone-journal leaf packages). Not a
97# user-facing extra — always pulled in by a leaf package.
98journal-host = [
99 "solstone-journal-models==1.0.0",
100 "python-dotenv",
101 "pyhpke==0.6.4",
102 "Flask[async]",
103 "Markdown",
104 "mistune",
105 "python-frontmatter",
106 "packaging",
107 "Pillow>=11.0.0",
108 "numpy",
109 "tokenizers>=0.20",
110 "av",
111 "openai>=1.2.0",
112 "openhands-sdk==1.27.*",
113 # OpenHands leaves LiteLLM broad. Keep production on the exact version
114 # exercised by uv.lock: 1.92.0 started importing FastAPI from the core
115 # completion path even though neither package declares that proxy extra.
116 "litellm==1.86.1",
117 # Pinned to >=1.27.1: that release reverts the Gemini prompt-cache markers
118 # (upstream PR #3586 / issue #3585). Emitting cache_control on Gemini froze
119 # its cache at the static prefix and disabled Google's implicit caching
120 # (~6-14x cost); keep implicit caching available for exact Gemini defaults
121 # and any explicit gemini-2.5/3 pin.
122 # openhands-sdk pulls lmnr's OpenTelemetry beta stack. Pin the matching line
123 # so pip does not mix newer 1.42/0.63 packages with lmnr's 0.60b1
124 # semantic-conventions pin.
125 "opentelemetry-api==1.39.1",
126 "opentelemetry-sdk==1.39.1",
127 "opentelemetry-exporter-otlp-proto-grpc==1.39.1",
128 "opentelemetry-exporter-otlp-proto-http==1.39.1",
129 "opentelemetry-instrumentation==0.60b1",
130 "opentelemetry-instrumentation-threading==0.60b1",
131 "opentelemetry-semantic-conventions==0.60b1",
132 # 0.6.2 = validated continuous-batching-fix baseline; <0.7 pins it
133 # so on-device builds don't float past the soak-validated server-wedge fix.
134 "mlx-vlm>=0.6.2,<0.7; sys_platform == 'darwin' and platform_machine == 'arm64'",
135 "mlx>=0.31.2,<1; sys_platform == 'darwin' and platform_machine == 'arm64'",
136 "huggingface-hub>=0.24",
137 "httpx",
138 "h2",
139 "jsonschema>=4.26,<5",
140 "genai-prices",
141 "icalendar",
142 "blessed>=1.20.0",
143 "tzlocal",
144 "python-slugify",
145 "rapidfuzz",
146 # Audio processing
147 "soundfile",
148 "kaldi-native-fbank>=1.22",
149 # solstone/apps/speakers/{owner,discovery}.py use sklearn.cluster.HDBSCAN.
150 # Previously pulled transitively via resemblyzer; now a direct dep.
151 "scikit-learn>=1.3",
152 # Media processing
153 "opencv-python-headless",
154 # PDF folds into the journal host ("choose journal, get it all"). Referenced
155 # via the building-block extra so the per-feature hints stay in sync.
156 "solstone[pdf]",
157]
158
159# TOMBSTONE. The journal moved out of the root distribution into its own
160# packages. `solstone[journal]` and the `solstone-journal-host` shim are retired;
161# this extra now pins a build-fails-by-design tombstone so every old spelling
162# fails loudly at install time instead of silently thin-installing. Install the
163# journal via:
164# pip install solstone-journal # the journal (CPU)
165# pip install solstone-journal-cuda # the journal on NVIDIA CUDA
166# The pin is STATIC (never rewritten per release); render_packaging.py --check
167# and check_extras_consistency.py verify it stays exactly this.
168journal = ["solstone-journal-host==0.7.0"]
169
170# TOMBSTONE (see [journal] above). Retired — same loud-failure tombstone pin.
171# Install `solstone-journal-cuda` for the NVIDIA CUDA runtime.
172journal-cuda = ["solstone-journal-host==0.7.0"]
173
174[project.urls]
175Homepage = "https://github.com/solpbc/solstone-journal"
176Repository = "https://github.com/solpbc/solstone-journal"
177Documentation = "https://github.com/solpbc/solstone-journal/blob/main/README.md"
178"Bug Tracker" = "https://github.com/solpbc/solstone-journal/issues"
179
180[tool.setuptools]
181# Raw root launchers are script-file payloads, not console entry points, so
182# uv-tool exports the root-owned public commands while dependency-owned core
183# scripts stay inside the tool environment.
184script-files = [
185 "scripts/root-launchers/sol",
186 "scripts/root-launchers/solstone",
187]
188
189# Package data is included wholesale via MANIFEST.in (`graft solstone`) plus the
190# include-package-data flag below. We deliberately do NOT hand-maintain per-subdir
191# package-data globs: that curated list repeatedly dropped data files added in new
192# subdirectories — the installed wheel would silently omit e.g.
193# solstone/think/contract/layout.json while source-tree `make ci` stayed green,
194# hard-500ing observer ingest. Including every non-.py file under solstone/ is the
195# correct default here: the long tail of .json/.md/.html/.schema.json data files
196# is intentionally shipped. If a file lives under solstone/ at build time, it
197# ships. New data file → no pyproject edit required.
198include-package-data = true
199
200[tool.setuptools.packages.find]
201include = ["solstone*"]
202exclude = ["*.tests", "*.tests.*"]
203
204[tool.setuptools.cmdclass]
205build_py = "build_hooks.BuildPyWithoutTests"
206
207[tool.uv]
208# Tombstone dependencies are build-fails-by-design distributions that must NOT
209# resolve during workspace development. This override prunes them from workspace
210# resolution: the marker `python_version < '3.12'` contradicts requires-python =
211# ">=3.12", so uv drops the requirement entirely (uv lock succeeds; no tombstone
212# node lands in uv.lock). [tool.uv] is never published, so end-user wheel
213# METADATA still carries the tombstone pins and fails loudly at install time.
214override-dependencies = [
215 "solstone-journal-host==0.7.0; python_version < '3.12'",
216 "solstone-core-unsupported-platform==1.0.17; python_version < '3.12'",
217]
218
219[tool.uv.workspace]
220members = ["packages/solstone-journal", "packages/solstone-journal-cuda", "packages/solstone-journal-models", "packages/solstone-core", "packages/solstone-core-speakers-analyze"]
221
222[tool.uv.sources]
223solstone-journal = {workspace = true}
224solstone-journal-cuda = {workspace = true}
225solstone-journal-models = {workspace = true}
226solstone-core = {workspace = true}
227solstone-core-speakers-analyze = {workspace = true}
228
229# Development tools configuration
230[tool.ruff]
231target-version = "py312"
232exclude = [".venv", "scratch", "logs", "build", "dist"]
233
234[tool.ruff.lint]
235select = ["F", "E", "W", "I"]
236ignore = ["E501", "E203", "E402"]
237
238[tool.pytest.ini_options]
239addopts = "--import-mode=importlib"
240asyncio_mode = "strict"
241testpaths = ["tests", "solstone/apps"]
242python_files = ["test_*.py"]
243python_classes = ["Test*"]
244python_functions = ["test_*"]
245markers = [
246 "integration: exercises real local processes, builds, or persisted index contracts outside unit CI",
247 "performance: asserts a wall-clock performance floor and is opt-in on an idle host",
248 "real_local_backend_probe: opts out of the deterministic bundled local backend probe monkeypatch",
249 "xdist_group: marks tests to run in the same xdist worker group",
250]
251timeout = 15
252tmp_path_retention_policy = "none"
253filterwarnings = [
254 # Third-party deprecation warnings (Python 3.14+)
255 "ignore:codecs.open.* is deprecated:DeprecationWarning",
256 "ignore:.*_UnionGenericAlias.* is deprecated:DeprecationWarning",
257 "ignore:.*DefaultEventLoopPolicy.* is deprecated:DeprecationWarning",
258]
259
260[dependency-groups]
261dev = [
262 "freezegun",
263 "onnx",
264 "pytest",
265 "pytest-asyncio",
266 "pytest-cov",
267 "pytest-timeout",
268 "pytest-xdist",
269 "ruff",
270 "schemathesis",
271 "setuptools",
272 # Test-only: encrypted PDF fixture construction in tests/pdf_worker_fixtures.py.
273 "pypdf>=4.0.0",
274]
275journal-cpu = ["solstone-journal"]
276journal-cuda = ["solstone-journal-cuda"]