hotmem 0.1.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.
hotmem-0.1.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 valiantone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hotmem-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: hotmem
3
+ Version: 0.1.3
4
+ Summary: A local-first memory sidecar for agent applications
5
+ Author-email: valiantone <zrjohn@yahoo.com>
6
+ Project-URL: Homepage, https://github.com/valiantone/hotmem
7
+ Project-URL: Repository, https://github.com/valiantone/hotmem
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: click<9,>=8.1
18
+ Requires-Dist: fastapi<1,>=0.115
19
+ Requires-Dist: httpx<1,>=0.28
20
+ Requires-Dist: uvicorn<1,>=0.30
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest<10,>=9; extra == "dev"
23
+ Requires-Dist: ruff<1,>=0.15; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # HotMem
27
+
28
+ A local-first memory sidecar for agent applications. One SQLite DB. One port: 8711.
29
+
30
+ HotMem provides fast, queryable working memory with hybrid vector + keyword search. Store facts, retrieve them ranked, and get back LLM-ready message objects you can stitch directly into prompts.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install hotmem@git+https://github.com/KnowGuard-AI/HotMem.git
36
+ # or
37
+ uv add hotmem@git+https://github.com/KnowGuard-AI/HotMem.git
38
+ ```
39
+
40
+ Or add to `requirements.txt`:
41
+
42
+ ```
43
+ hotmem @ git+https://github.com/KnowGuard-AI/HotMem.git
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```bash
49
+ # Start with a mount directory (portable memory)
50
+ hotmem serve --mount ./hotmem
51
+
52
+ # Or just start (uses temp DB)
53
+ hotmem serve
54
+ ```
55
+
56
+ ## CLI
57
+
58
+ ```bash
59
+ hotmem serve --port 8711 --mount ./data/hotmem
60
+ hotmem serve --db ./my.sqlite
61
+ hotmem hydrate --file swap.jsonl --db ./my.sqlite
62
+ hotmem snapshot --file swap.jsonl --db ./my.sqlite
63
+ hotmem status
64
+ ```
65
+
66
+ ## API
67
+
68
+ All endpoints under `/v1`. Default: `http://127.0.0.1:8711`
69
+
70
+ ### `GET /v1/health`
71
+
72
+ ```json
73
+ {"status": "ok", "memory_count": 42, "db_path": "...", "uptime_s": 120.5}
74
+ ```
75
+
76
+ ### `POST /v1/add`
77
+
78
+ ```json
79
+ {"identifier": "vendor_x", "fact": "Invoice total was $5000", "importance": 0.8}
80
+ ```
81
+
82
+ ### `POST /v1/search`
83
+
84
+ ```json
85
+ {"query": "duplicate invoice risk", "top_k": 5, "max_chars": 1500}
86
+ ```
87
+
88
+ Returns ranked message objects ready for LLM stitching:
89
+
90
+ ```json
91
+ {
92
+ "memories": [
93
+ {"role": "system", "content": "...", "memory_id": "...", "identifier": "...", "score": 0.87}
94
+ ],
95
+ "count": 5,
96
+ "trace_ms": 2.1
97
+ }
98
+ ```
99
+
100
+ ### `POST /v1/hydrate`
101
+
102
+ ```json
103
+ {"file": "swap.jsonl"}
104
+ ```
105
+
106
+ ### `POST /v1/snapshot`
107
+
108
+ ```json
109
+ {"file": "swap.jsonl"}
110
+ ```
111
+
112
+ ## Python Client
113
+
114
+ ```python
115
+ from hotmem.client import HotMemClient
116
+
117
+ with HotMemClient("http://127.0.0.1:8711") as client:
118
+ client.add("vendor_x", "Invoice total $5000", importance=0.8)
119
+
120
+ memories = client.search("duplicate invoice risk", top_k=5, max_chars=1500)
121
+
122
+ # memories are LLM-ready message objects
123
+ messages = memories + [{"role": "user", "content": "Analyze this vendor."}]
124
+ ```
125
+
126
+ ## Mounting
127
+
128
+ Any directory can be a HotMem mount. The mount contains:
129
+
130
+ - `hotmem.sqlite` — the database
131
+ - `swap.jsonl` — portable JSONL backup
132
+ - `manifest.json` — mount metadata
133
+
134
+ ```bash
135
+ hotmem serve --mount /mnt/usb/hotmem # portable memory on USB
136
+ hotmem serve --mount ./data/hotmem # local project memory
137
+ ```
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ uv sync # install deps
143
+ uv run pytest # run tests
144
+ uv run ruff check src/ tests/ # lint
145
+ uv run ruff format src/ tests/ # format
146
+ uv build # build wheel
147
+ ```
148
+
149
+ ## Architecture (for agents)
150
+
151
+ Each source module is independently extensible with self-documenting headers:
152
+
153
+ | Module | Purpose | Extension Point |
154
+ |--------|---------|------------------|
155
+ | `trace.py` | Structured JSON logging | Add sinks, formatters |
156
+ | `embed.py` | Hash-based embedder (dim=64) | Swap for real model |
157
+ | `db.py` | SQLite + cosine UDF | Add FTS5, indexes |
158
+ | `search.py` | Hybrid ranking | Add reranking, MMR |
159
+ | `swap.py` | JSONL hydrate/snapshot | Add compression |
160
+ | `mount.py` | Directory bootstrap | Add remote sync |
161
+ | `server.py` | FastAPI endpoints | Add CORS, new routes |
162
+ | `cli.py` | Click CLI | Add subcommands |
163
+ | `client.py` | httpx SDK | Add async client |
164
+
165
+ Every operation emits structured JSON traces to stderr with component tags:
166
+
167
+ ```bash
168
+ hotmem serve --mount ./data 2>&1 | grep '"component": "search"'
169
+ ```
hotmem-0.1.3/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # HotMem
2
+
3
+ A local-first memory sidecar for agent applications. One SQLite DB. One port: 8711.
4
+
5
+ HotMem provides fast, queryable working memory with hybrid vector + keyword search. Store facts, retrieve them ranked, and get back LLM-ready message objects you can stitch directly into prompts.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install hotmem@git+https://github.com/KnowGuard-AI/HotMem.git
11
+ # or
12
+ uv add hotmem@git+https://github.com/KnowGuard-AI/HotMem.git
13
+ ```
14
+
15
+ Or add to `requirements.txt`:
16
+
17
+ ```
18
+ hotmem @ git+https://github.com/KnowGuard-AI/HotMem.git
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ # Start with a mount directory (portable memory)
25
+ hotmem serve --mount ./hotmem
26
+
27
+ # Or just start (uses temp DB)
28
+ hotmem serve
29
+ ```
30
+
31
+ ## CLI
32
+
33
+ ```bash
34
+ hotmem serve --port 8711 --mount ./data/hotmem
35
+ hotmem serve --db ./my.sqlite
36
+ hotmem hydrate --file swap.jsonl --db ./my.sqlite
37
+ hotmem snapshot --file swap.jsonl --db ./my.sqlite
38
+ hotmem status
39
+ ```
40
+
41
+ ## API
42
+
43
+ All endpoints under `/v1`. Default: `http://127.0.0.1:8711`
44
+
45
+ ### `GET /v1/health`
46
+
47
+ ```json
48
+ {"status": "ok", "memory_count": 42, "db_path": "...", "uptime_s": 120.5}
49
+ ```
50
+
51
+ ### `POST /v1/add`
52
+
53
+ ```json
54
+ {"identifier": "vendor_x", "fact": "Invoice total was $5000", "importance": 0.8}
55
+ ```
56
+
57
+ ### `POST /v1/search`
58
+
59
+ ```json
60
+ {"query": "duplicate invoice risk", "top_k": 5, "max_chars": 1500}
61
+ ```
62
+
63
+ Returns ranked message objects ready for LLM stitching:
64
+
65
+ ```json
66
+ {
67
+ "memories": [
68
+ {"role": "system", "content": "...", "memory_id": "...", "identifier": "...", "score": 0.87}
69
+ ],
70
+ "count": 5,
71
+ "trace_ms": 2.1
72
+ }
73
+ ```
74
+
75
+ ### `POST /v1/hydrate`
76
+
77
+ ```json
78
+ {"file": "swap.jsonl"}
79
+ ```
80
+
81
+ ### `POST /v1/snapshot`
82
+
83
+ ```json
84
+ {"file": "swap.jsonl"}
85
+ ```
86
+
87
+ ## Python Client
88
+
89
+ ```python
90
+ from hotmem.client import HotMemClient
91
+
92
+ with HotMemClient("http://127.0.0.1:8711") as client:
93
+ client.add("vendor_x", "Invoice total $5000", importance=0.8)
94
+
95
+ memories = client.search("duplicate invoice risk", top_k=5, max_chars=1500)
96
+
97
+ # memories are LLM-ready message objects
98
+ messages = memories + [{"role": "user", "content": "Analyze this vendor."}]
99
+ ```
100
+
101
+ ## Mounting
102
+
103
+ Any directory can be a HotMem mount. The mount contains:
104
+
105
+ - `hotmem.sqlite` — the database
106
+ - `swap.jsonl` — portable JSONL backup
107
+ - `manifest.json` — mount metadata
108
+
109
+ ```bash
110
+ hotmem serve --mount /mnt/usb/hotmem # portable memory on USB
111
+ hotmem serve --mount ./data/hotmem # local project memory
112
+ ```
113
+
114
+ ## Development
115
+
116
+ ```bash
117
+ uv sync # install deps
118
+ uv run pytest # run tests
119
+ uv run ruff check src/ tests/ # lint
120
+ uv run ruff format src/ tests/ # format
121
+ uv build # build wheel
122
+ ```
123
+
124
+ ## Architecture (for agents)
125
+
126
+ Each source module is independently extensible with self-documenting headers:
127
+
128
+ | Module | Purpose | Extension Point |
129
+ |--------|---------|------------------|
130
+ | `trace.py` | Structured JSON logging | Add sinks, formatters |
131
+ | `embed.py` | Hash-based embedder (dim=64) | Swap for real model |
132
+ | `db.py` | SQLite + cosine UDF | Add FTS5, indexes |
133
+ | `search.py` | Hybrid ranking | Add reranking, MMR |
134
+ | `swap.py` | JSONL hydrate/snapshot | Add compression |
135
+ | `mount.py` | Directory bootstrap | Add remote sync |
136
+ | `server.py` | FastAPI endpoints | Add CORS, new routes |
137
+ | `cli.py` | Click CLI | Add subcommands |
138
+ | `client.py` | httpx SDK | Add async client |
139
+
140
+ Every operation emits structured JSON traces to stderr with component tags:
141
+
142
+ ```bash
143
+ hotmem serve --mount ./data 2>&1 | grep '"component": "search"'
144
+ ```
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "hotmem"
3
+ version = "0.1.3"
4
+ description = "A local-first memory sidecar for agent applications"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+
8
+ authors = [
9
+ { name = "valiantone", email = "zrjohn@yahoo.com" }
10
+ ]
11
+
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ ]
20
+
21
+ dependencies = [
22
+ "click>=8.1,<9",
23
+ "fastapi>=0.115,<1",
24
+ "httpx>=0.28,<1",
25
+ "uvicorn>=0.30,<1",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/valiantone/hotmem"
30
+ Repository = "https://github.com/valiantone/hotmem"
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=9,<10",
35
+ "ruff>=0.15,<1",
36
+ ]
37
+
38
+ [project.scripts]
39
+ hotmem = "hotmem.cli:main"
40
+
41
+ [build-system]
42
+ requires = [
43
+ "setuptools>=75",
44
+ "wheel"
45
+ ]
46
+ build-backend = "setuptools.build_meta"
47
+
48
+ [tool.ruff]
49
+ target-version = "py311"
50
+ line-length = 100
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E", "F", "I", "UP", "B", "SIM"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+
hotmem-0.1.3/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """HotMem — A local-first memory sidecar for agent applications."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,118 @@
1
+ """HotMem CLI — command-line interface for the memory sidecar.
2
+
3
+ Purpose:
4
+ Provide serve, hydrate, snapshot, and status commands.
5
+ Entry point: `hotmem` (registered in pyproject.toml).
6
+
7
+ Interface:
8
+ main() — Click group with subcommands
9
+
10
+ Deps: click, uvicorn, hotmem.server, hotmem.mount, hotmem.db, hotmem.swap, hotmem.trace
11
+ Extension: add new subcommands (e.g. `hotmem inspect`, `hotmem gc`) here.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import tempfile
17
+
18
+ import click
19
+
20
+ from hotmem.trace import get_tracer
21
+
22
+ _trace = get_tracer("cli")
23
+
24
+
25
+ @click.group()
26
+ @click.version_option(package_name="hotmem")
27
+ def main():
28
+ """HotMem — local-first memory sidecar for agent applications."""
29
+
30
+
31
+ @main.command()
32
+ @click.option("--port", default=8711, type=int, help="Port to listen on.")
33
+ @click.option("--mount", default=None, type=click.Path(), help="Mount directory path.")
34
+ @click.option("--db", "db_path", default=None, type=click.Path(), help="Explicit database path.")
35
+ @click.option("--host", default="127.0.0.1", help="Host to bind to.")
36
+ def serve(port: int, mount: str | None, db_path: str | None, host: str):
37
+ """Start the HotMem sidecar server."""
38
+ import uvicorn
39
+
40
+ from hotmem.mount import bootstrap_mount
41
+ from hotmem.server import create_app
42
+
43
+ swap_path = None
44
+
45
+ if mount:
46
+ config = bootstrap_mount(mount)
47
+ db_path = str(config.db_path)
48
+ swap_path = str(config.swap_path)
49
+ elif not db_path:
50
+ db_path = tempfile.mktemp(suffix=".sqlite", prefix="hotmem_")
51
+ _trace.warn(
52
+ "serve", "no mount or db path specified, using temp db",
53
+ detail={"path": db_path},
54
+ )
55
+
56
+ app = create_app(db_path=db_path, swap_path=swap_path, port=port)
57
+
58
+ _trace.info(
59
+ "serve", f"starting server on {host}:{port}",
60
+ detail={"db": db_path, "mount": mount},
61
+ )
62
+ uvicorn.run(app, host=host, port=port, log_level="warning")
63
+
64
+
65
+ @main.command()
66
+ @click.option(
67
+ "--file", "swap_file", default="swap.jsonl",
68
+ type=click.Path(), help="Swap file path.",
69
+ )
70
+ @click.option("--db", "db_path", required=True, type=click.Path(), help="Database path.")
71
+ def hydrate(swap_file: str, db_path: str):
72
+ """Load a swap file into the database."""
73
+ from hotmem.db import MemoryDB
74
+ from hotmem.swap import hydrate as do_hydrate
75
+
76
+ db = MemoryDB(db_path)
77
+ result = do_hydrate(db, swap_file)
78
+ db.close()
79
+
80
+ click.echo(f"Loaded: {result.loaded}, Skipped dupes: {result.skipped_dupes}")
81
+
82
+
83
+ @main.command()
84
+ @click.option(
85
+ "--file", "swap_file", default="swap.jsonl",
86
+ type=click.Path(), help="Output swap file path.",
87
+ )
88
+ @click.option("--db", "db_path", required=True, type=click.Path(), help="Database path.")
89
+ def snapshot(swap_file: str, db_path: str):
90
+ """Export database memories to a swap file."""
91
+ from hotmem.db import MemoryDB
92
+ from hotmem.swap import snapshot as do_snapshot
93
+
94
+ db = MemoryDB(db_path)
95
+ result = do_snapshot(db, swap_file)
96
+ db.close()
97
+
98
+ click.echo(f"Exported: {result.exported} → {result.path}")
99
+
100
+
101
+ @main.command()
102
+ @click.option("--port", default=8711, type=int, help="Port to check.")
103
+ @click.option("--host", default="127.0.0.1", help="Host to check.")
104
+ def status(port: int, host: str):
105
+ """Check if a HotMem server is running."""
106
+ import httpx
107
+
108
+ url = f"http://{host}:{port}/v1/health"
109
+ try:
110
+ resp = httpx.get(url, timeout=3.0)
111
+ data = resp.json()
112
+ click.echo(f"Status: {data['status']}")
113
+ click.echo(f"Memories: {data['memory_count']}")
114
+ click.echo(f"DB: {data['db_path']}")
115
+ click.echo(f"Uptime: {data['uptime_s']}s")
116
+ except httpx.ConnectError as err:
117
+ click.echo(f"No HotMem server found at {url}", err=True)
118
+ raise SystemExit(1) from err
@@ -0,0 +1,100 @@
1
+ """HotMem client — Python SDK for the memory sidecar.
2
+
3
+ Purpose:
4
+ Provide a simple, typed client for the HotMem HTTP API.
5
+ Designed for direct use in agent applications and SPA backends.
6
+
7
+ Interface:
8
+ HotMemClient(base_url)
9
+ .add(identifier, fact, ...) -> dict
10
+ .search(query, top_k, max_chars?) -> list[MessageObject]
11
+ .health() -> dict
12
+ .hydrate(file?) -> dict
13
+ .snapshot(file?) -> dict
14
+
15
+ Deps: httpx
16
+ Extension: add async client, retry logic, or connection pooling here.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Any
22
+
23
+ import httpx
24
+
25
+
26
+ class HotMemClient:
27
+ """Synchronous client for the HotMem sidecar API."""
28
+
29
+ def __init__(self, base_url: str = "http://127.0.0.1:8711") -> None:
30
+ self.base_url = base_url.rstrip("/")
31
+ self._client = httpx.Client(base_url=self.base_url, timeout=30.0)
32
+
33
+ def health(self) -> dict[str, Any]:
34
+ """Check server health."""
35
+ resp = self._client.get("/v1/health")
36
+ resp.raise_for_status()
37
+ return resp.json()
38
+
39
+ def add(
40
+ self,
41
+ identifier: str,
42
+ fact: str,
43
+ *,
44
+ source: str = "",
45
+ importance: float = 0.5,
46
+ metadata: dict[str, Any] | None = None,
47
+ ) -> dict[str, Any]:
48
+ """Add a fact to memory."""
49
+ payload = {
50
+ "identifier": identifier,
51
+ "fact": fact,
52
+ "source": source,
53
+ "importance": importance,
54
+ "metadata": metadata or {},
55
+ }
56
+ resp = self._client.post("/v1/add", json=payload)
57
+ resp.raise_for_status()
58
+ return resp.json()
59
+
60
+ def search(
61
+ self,
62
+ query: str,
63
+ top_k: int = 5,
64
+ max_chars: int | None = None,
65
+ ) -> list[dict[str, Any]]:
66
+ """Search memories and return LLM-ready message objects."""
67
+ payload: dict[str, Any] = {"query": query, "top_k": top_k}
68
+ if max_chars is not None:
69
+ payload["max_chars"] = max_chars
70
+ resp = self._client.post("/v1/search", json=payload)
71
+ resp.raise_for_status()
72
+ return resp.json()["memories"]
73
+
74
+ def hydrate(self, file: str | None = None) -> dict[str, Any]:
75
+ """Trigger swap file hydration."""
76
+ payload: dict[str, Any] = {}
77
+ if file:
78
+ payload["file"] = file
79
+ resp = self._client.post("/v1/hydrate", json=payload)
80
+ resp.raise_for_status()
81
+ return resp.json()
82
+
83
+ def snapshot(self, file: str | None = None) -> dict[str, Any]:
84
+ """Trigger database snapshot to swap file."""
85
+ payload: dict[str, Any] = {}
86
+ if file:
87
+ payload["file"] = file
88
+ resp = self._client.post("/v1/snapshot", json=payload)
89
+ resp.raise_for_status()
90
+ return resp.json()
91
+
92
+ def close(self) -> None:
93
+ """Close the underlying HTTP client."""
94
+ self._client.close()
95
+
96
+ def __enter__(self) -> HotMemClient:
97
+ return self
98
+
99
+ def __exit__(self, *args: object) -> None:
100
+ self.close()