orma-ai 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.
- orma_ai-0.1.0/.gitignore +51 -0
- orma_ai-0.1.0/HANDOVER.md +115 -0
- orma_ai-0.1.0/LICENSE +176 -0
- orma_ai-0.1.0/PKG-INFO +234 -0
- orma_ai-0.1.0/README.md +213 -0
- orma_ai-0.1.0/docs/ARCHITECTURE.md +103 -0
- orma_ai-0.1.0/docs/CLIENTS.md +83 -0
- orma_ai-0.1.0/docs/SETUP_GUIDE.md +44 -0
- orma_ai-0.1.0/docs/superpowers/plans/2026-10-12-memory-core.md +2949 -0
- orma_ai-0.1.0/docs/superpowers/plans/2026-10-13-proxy-cli.md +978 -0
- orma_ai-0.1.0/docs/superpowers/specs/2026-10-12-local-neurosymbolic-memory-design.md +152 -0
- orma_ai-0.1.0/eval/__init__.py +1 -0
- orma_ai-0.1.0/eval/cases.json +143 -0
- orma_ai-0.1.0/eval/run.py +207 -0
- orma_ai-0.1.0/eval/score.py +98 -0
- orma_ai-0.1.0/eval/test_cases.json +151 -0
- orma_ai-0.1.0/implementation.md +49 -0
- orma_ai-0.1.0/orma/__init__.py +7 -0
- orma_ai-0.1.0/orma/__main__.py +5 -0
- orma_ai-0.1.0/orma/cli.py +192 -0
- orma_ai-0.1.0/orma/clock.py +110 -0
- orma_ai-0.1.0/orma/config.py +89 -0
- orma_ai-0.1.0/orma/embed.py +53 -0
- orma_ai-0.1.0/orma/encode.py +52 -0
- orma_ai-0.1.0/orma/llm.py +91 -0
- orma_ai-0.1.0/orma/memory.py +148 -0
- orma_ai-0.1.0/orma/messages.py +12 -0
- orma_ai-0.1.0/orma/recall/__init__.py +148 -0
- orma_ai-0.1.0/orma/recall/pack.py +87 -0
- orma_ai-0.1.0/orma/recall/rank.py +46 -0
- orma_ai-0.1.0/orma/serve/__init__.py +1 -0
- orma_ai-0.1.0/orma/serve/capture.py +97 -0
- orma_ai-0.1.0/orma/serve/proxy.py +174 -0
- orma_ai-0.1.0/orma/serve/scheduler.py +57 -0
- orma_ai-0.1.0/orma/sleep/__init__.py +126 -0
- orma_ai-0.1.0/orma/sleep/consolidate.py +240 -0
- orma_ai-0.1.0/orma/sleep/extract.py +169 -0
- orma_ai-0.1.0/orma/store/__init__.py +6 -0
- orma_ai-0.1.0/orma/store/db.py +160 -0
- orma_ai-0.1.0/orma/store/index.py +103 -0
- orma_ai-0.1.0/orma/store/records.py +122 -0
- orma_ai-0.1.0/orma/store/store.py +452 -0
- orma_ai-0.1.0/orma/text.py +86 -0
- orma_ai-0.1.0/pyproject.toml +39 -0
- orma_ai-0.1.0/scripts/demo_ollama.py +93 -0
- orma_ai-0.1.0/scripts/latency_smoke.py +48 -0
- orma_ai-0.1.0/tasks.md +27 -0
- orma_ai-0.1.0/tests/conftest.py +103 -0
- orma_ai-0.1.0/tests/test_capture.py +66 -0
- orma_ai-0.1.0/tests/test_cli.py +73 -0
- orma_ai-0.1.0/tests/test_clock.py +65 -0
- orma_ai-0.1.0/tests/test_config.py +23 -0
- orma_ai-0.1.0/tests/test_consolidate.py +221 -0
- orma_ai-0.1.0/tests/test_embed.py +32 -0
- orma_ai-0.1.0/tests/test_encode.py +56 -0
- orma_ai-0.1.0/tests/test_eval.py +94 -0
- orma_ai-0.1.0/tests/test_extract.py +81 -0
- orma_ai-0.1.0/tests/test_llm.py +114 -0
- orma_ai-0.1.0/tests/test_memory.py +113 -0
- orma_ai-0.1.0/tests/test_proxy.py +284 -0
- orma_ai-0.1.0/tests/test_rank_pack.py +104 -0
- orma_ai-0.1.0/tests/test_recall.py +106 -0
- orma_ai-0.1.0/tests/test_scheduler.py +74 -0
- orma_ai-0.1.0/tests/test_sleep.py +102 -0
- orma_ai-0.1.0/tests/test_stats.py +22 -0
- orma_ai-0.1.0/tests/test_store_db.py +123 -0
- orma_ai-0.1.0/tests/test_store_episodes.py +38 -0
- orma_ai-0.1.0/tests/test_store_events.py +22 -0
- orma_ai-0.1.0/tests/test_text.py +33 -0
orma_ai-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Python bytecode & cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
|
|
10
|
+
# Virtual Environment
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
ENV/
|
|
15
|
+
|
|
16
|
+
# Local databases & storage
|
|
17
|
+
.smruti/
|
|
18
|
+
.smriti/
|
|
19
|
+
.orma/
|
|
20
|
+
*.db
|
|
21
|
+
*.db-wal
|
|
22
|
+
*.db-shm
|
|
23
|
+
*.sqlite
|
|
24
|
+
*.sqlite3
|
|
25
|
+
|
|
26
|
+
# Build & packaging artifacts
|
|
27
|
+
build/
|
|
28
|
+
dist/
|
|
29
|
+
*.egg-info/
|
|
30
|
+
*.whl
|
|
31
|
+
|
|
32
|
+
# Test & coverage
|
|
33
|
+
.coverage
|
|
34
|
+
htmlcov/
|
|
35
|
+
|
|
36
|
+
# Environment & secrets
|
|
37
|
+
.env
|
|
38
|
+
.env.*
|
|
39
|
+
!.env.example
|
|
40
|
+
|
|
41
|
+
# IDE & OS
|
|
42
|
+
.vscode/
|
|
43
|
+
.idea/
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
46
|
+
*.swp
|
|
47
|
+
*~
|
|
48
|
+
|
|
49
|
+
# Benchmark & eval run logs
|
|
50
|
+
eval/runs/
|
|
51
|
+
scratch/
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Smruti handover
|
|
2
|
+
|
|
3
|
+
Everything you need to take over Smruti: what it is, how to run it, how it is built and why, what has been verified, what is broken, and what to do next.
|
|
4
|
+
|
|
5
|
+
## 1. What it is, in one paragraph
|
|
6
|
+
|
|
7
|
+
Smruti is a general long-term memory layer for small local LLMs (reference: Qwen2.5-3B on llama.cpp or `llama3.2:3b` on Ollama, 4k-token budget, an 8 GB laptop). Anything can be added: chat turns, notes, documents, tool output. Every record is kept in full and is searchable at once. In the background, while the machine is idle, the same local model reads new records and builds two kinds of structure that plain search cannot give: **states** (what is true now, one value at a time, superseded when it changes, with history) and **dated events**. Recall needs no LLM: hybrid search over the kept sentences plus the current states, rendered into the prompt with outdated lines marked. It ships as an SDK (`Memory.add / search / sleep / build_context`), a CLI, and an OpenAI-compatible proxy that adds memory to any chat client.
|
|
8
|
+
|
|
9
|
+
## 2. Run it (about 5 minutes)
|
|
10
|
+
|
|
11
|
+
Python 3.10 or newer.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git clone https://github.com/wizardwithcodehazard/smruti.git && cd smruti
|
|
15
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
16
|
+
pip install -e ".[dev]"
|
|
17
|
+
python -m pytest -q # no downloads needed
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python -m smruti add "My dentist is Dr. Rao in Kothrud." # a note
|
|
22
|
+
python -m smruti add notes/handbook.md # a document
|
|
23
|
+
python -m smruti search "who is my dentist?"
|
|
24
|
+
python -m smruti sleep --model MODEL --upstream http://127.0.0.1:PORT/v1
|
|
25
|
+
python -m smruti facts --history
|
|
26
|
+
|
|
27
|
+
# the chat proxy (Ollama default; llama.cpp: --upstream http://127.0.0.1:PORT/v1)
|
|
28
|
+
python -m smruti proxy --model llama3.2:3b
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`python -m smruti` always runs this checkout, even if another `smruti` command is first on PATH. The first run downloads the bge-small embedding model (about 130 MB). Memory lives in `~/.smruti/memory.db` unless `SMRUTI_DIR` is set. A database from version 1 keeps its records; run `smruti rebuild` once.
|
|
32
|
+
|
|
33
|
+
## 3. Repo map
|
|
34
|
+
|
|
35
|
+
| Path | What it is |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `smruti/config.py` | `SmrutiConfig`: every tunable and every ablation switch (`enable_*`) |
|
|
38
|
+
| `smruti/text.py` | Sentence splitting, code-block markers, whole-word matching |
|
|
39
|
+
| `smruti/clock.py` | Timezone-aware days; date words resolved against when they were said |
|
|
40
|
+
| `smruti/store/` | SQLite schema (`db.py`), vector and FTS5 helpers (`index.py`), records, `Store` |
|
|
41
|
+
| `smruti/encode.py` | Adding a record: near-copy gate, sentences, indexing |
|
|
42
|
+
| `smruti/sleep/` | Batching and the lease (`__init__.py`), the two-list extraction (`extract.py`), checks and time maintenance (`consolidate.py`) |
|
|
43
|
+
| `smruti/recall/` | Hybrid search, ranking (`rank.py`), budgeted packing (`pack.py`) |
|
|
44
|
+
| `smruti/serve/` | The proxy, turn capture, the background sleep scheduler |
|
|
45
|
+
| `smruti/memory.py`, `smruti/cli.py` | The SDK and the `smruti` command |
|
|
46
|
+
| `eval/` | Dev cases (`cases.json`), held-out test cases (`test_cases.json`), the harness (`run.py`, `score.py`) |
|
|
47
|
+
| `tests/` | Unit and integration tests with `FakeEmbedder`, `NoisyEmbedder`, `FakeLLM` and `seed_state` |
|
|
48
|
+
| `scripts/` | Real-model chat demo, latency and needle-in-a-haystack smoke test |
|
|
49
|
+
| `docs/ARCHITECTURE.md` | Engineering overview of v2 (current) |
|
|
50
|
+
| `docs/superpowers/` | The v1 design spec and plans (historical) |
|
|
51
|
+
|
|
52
|
+
## 4. How it works
|
|
53
|
+
|
|
54
|
+
Read `docs/ARCHITECTURE.md`. In short:
|
|
55
|
+
|
|
56
|
+
- **add** (no LLM): keep the record, split into sentences, embed and index them. A near-copy (≥ 0.97) is counted, not stored.
|
|
57
|
+
- **sleep** (one LLM call per batch): numbered lines in, `{user_name, states, events}` out, each citing a line number. Code checks the citation, who it is about (pronouns carry over from the last name before them), that the value is in the line, and maintains one value per entity and attribute over time.
|
|
58
|
+
- **recall** (no LLM, no writes): hybrid search with a bounded recency boost; the user's current states always shown; superseded sentences marked `[outdated]`.
|
|
59
|
+
|
|
60
|
+
## 5. Key decisions and the history behind them (read before changing things)
|
|
61
|
+
|
|
62
|
+
| Decision | Why (what went wrong otherwise) |
|
|
63
|
+
|---|---|
|
|
64
|
+
| **v2: episodes are the memory; only states and events are structured** | v1 extracted a general triple graph. On Qwen2.5-3B the triples were often wrong (`user allergic_to Asha`), yet answers were right because the source sentence came along. Every repair (quotes, holder labels, learned cardinality, role aliases) added fields for a 3B model to get wrong and doubled sleep time (~40 s to ~110 s for 3 turns). Facts found swung 6/12 → 0/12 → 5/12 between patches. |
|
|
65
|
+
| Records are never deleted | v1 pruned raw turns after 7 days and kept the lossy extraction forever, so a bad extraction was permanent. Now `rebuild` can re-read everything with a better model. |
|
|
66
|
+
| The model cites line numbers, not quotes | Copying quotes made the answer long (the slow part) and needed fuzzy matching. A line number is a few tokens and exact. |
|
|
67
|
+
| Only one-valued states are structured | One-valued state is what plain search cannot do (what is true *now*). Many-valued facts (likes, allergies, hobbies) are found as sentences; no cardinality guessing is needed. |
|
|
68
|
+
| Who a memory is about is resolved in code; the model's label is not trusted | Qwen2.5-3B labelled "I live in Pune" as about someone else. First-person words in the user's own lines mean the user; "she" carries over the last name before it (Panini's anuvrtti). The two pronoun lists are config, replaceable per language. |
|
|
69
|
+
| No hand-written meaning heuristics | The pre-v1 engine was full of them and broke on ordinary input. Structural rules (sentence ends, code fences, dates, closed pronoun classes) are fine. |
|
|
70
|
+
| Recall never writes and never calls an LLM | Speed, and "retrieval is not evidence": a memory must not get stronger because it was retrieved. |
|
|
71
|
+
| Similarity weighted against a data-adaptive floor | bge-small gives unrelated short texts about 0.4–0.6 cosine, so plain rank fusion pulled in junk. |
|
|
72
|
+
| Others' states need lexical evidence to be shown | In a fresh space (before the floor warms up) vector-only matches put unrelated people's states into "Now true". |
|
|
73
|
+
| Recency is a bounded boost (`use_weight`) | With ACT-R decay 0.5 alone, a 90-day-old memory scores about 0.1 of a fresh one, so fresh trivia would outrank an old relevant fact. The boost moves rank by at most 30%. |
|
|
74
|
+
| Dates: the model copies date words, code resolves them in the user's timezone | Small models get date arithmetic wrong; UTC log dates put "yesterday" on the wrong day between 00:00 and 05:30 IST. |
|
|
75
|
+
| The novelty gate drops only near-copies (0.97) | Embeddings barely see negation: "is allergic to" vs "is not allergic to" is 0.883, so a looser gate dropped corrections. |
|
|
76
|
+
| Today's date in the memory block | Otherwise the model said "a few months ago" for an event four days earlier. |
|
|
77
|
+
| No eval names in the extraction prompt | v1 put "my sister Asha" in the prompt while the eval tested that exact sentence. |
|
|
78
|
+
|
|
79
|
+
## 6. What has been verified
|
|
80
|
+
|
|
81
|
+
- **Tests:** `python -m pytest -q`, all passing, including the proxy against a fake upstream (streaming, disconnects, malformed bodies, CSRF guard, secret masking).
|
|
82
|
+
- **Real model:** Qwen2.5-3B-Instruct Q4_K_M on llama.cpp, eval dev split; see §9 for how to run it. Results of the latest run are in `tasks.md`.
|
|
83
|
+
- **Not verified:** any public benchmark (LongMemEval); the held-out test split beyond a single run; client configs in `docs/CLIENTS.md`; runtimes other than Ollama and llama.cpp.
|
|
84
|
+
|
|
85
|
+
## 7. Known issues and limitations (most important first)
|
|
86
|
+
|
|
87
|
+
1. **Extraction coverage is bounded by the sleep model.** A 3B model misses states. Sentences are still found by search, but "Now true" and `[outdated]` depend on sleep. `rebuild` with a 7B model is the cheapest improvement.
|
|
88
|
+
2. **The eval is small** (8 dev cases, 10 test cases, keyword-scored answers). Treat differences of one or two cases as noise.
|
|
89
|
+
3. **English-centred perception.** The prompt, date parser, FTS stemmer and pronoun lists are English; Hinglish works only as far as the model and embedder manage.
|
|
90
|
+
4. **"user" filed for someone else's state is not caught** when the line has first-person words ("my sister Asha lives in Delhi" filed as the user's). The sentence shown with it lets the chat model correct it.
|
|
91
|
+
5. **Fresh spaces are noisier** until `novelty_warmup` records exist.
|
|
92
|
+
6. **Token counts are an estimate** (`chars_per_token` = 3.5).
|
|
93
|
+
7. **Sleep competes with chat** for the GPU on small machines; `--sleep-upstream` can point sleep at another server.
|
|
94
|
+
8. **No `LICENSE` file yet,** although `pyproject.toml` says Apache-2.0.
|
|
95
|
+
|
|
96
|
+
## 8. What to do next
|
|
97
|
+
|
|
98
|
+
1. Run the held-out test split once and report it next to dev, with the episodic-only baseline (`--set enable_semantic=false`).
|
|
99
|
+
2. LongMemEval (oracle split) with last-k, full-context and plain-RAG baselines.
|
|
100
|
+
3. A stronger sleep model through `rebuild` (7B on a second server) and measure the gain.
|
|
101
|
+
4. Grow the eval to 30–50 cases, judged by a larger model instead of keywords.
|
|
102
|
+
5. Coding sessions: tool-output memory (what was tried, what worked), now as a separate, tested module.
|
|
103
|
+
|
|
104
|
+
## 9. Working on the code
|
|
105
|
+
|
|
106
|
+
- **Tests first.** The fakes make tests deterministic and fast. `seed_state` creates a state backed by a real sentence.
|
|
107
|
+
- **Every mechanism gets an `enable_*` switch** and stays only if turning it off hurts the eval.
|
|
108
|
+
- **Run the eval after every change to sleep or recall:** `python -m eval.run --upstream URL --model MODEL --label NAME` (dev). Ablate with `--set enable_x=false`. Compare with `--compare eval/runs/a.json eval/runs/b.json`. Run `--split test` only to report, never to tune.
|
|
109
|
+
- **Debug extraction by replaying:** `smruti.sleep.extract.build_messages(lines, context)` with `LLMClient.chat_json(..., schema=JSON_SCHEMA)`. Import the module with `importlib.import_module("smruti.sleep.extract")`, because `smruti.sleep` re-exports a function named `extract`.
|
|
110
|
+
|
|
111
|
+
## 10. History
|
|
112
|
+
|
|
113
|
+
- Pre-v1: a coding-agent memory with anti-memories, MCP tools and hooks; replaced.
|
|
114
|
+
- v1 (Plans 1–2, Batches A and C): proxy, triple graph with spreading activation, priming, summaries, procedures, quotes, kartā labels, learned cardinality. Superseded by v2 for the reasons in §5; it is in git history.
|
|
115
|
+
- v2: the general, episode-first layer described here.
|
orma_ai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
orma_ai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: orma-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local episode-first long-term memory for small LLMs
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: dateparser>=1.2.0
|
|
9
|
+
Requires-Dist: fastembed>=0.2.0
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: numpy>=1.20.0
|
|
12
|
+
Requires-Dist: sqlite-vec>=0.1.6
|
|
13
|
+
Requires-Dist: starlette>=0.37.0
|
|
14
|
+
Requires-Dist: typer>=0.9.0
|
|
15
|
+
Requires-Dist: tzdata; platform_system == 'Windows'
|
|
16
|
+
Requires-Dist: uvicorn>=0.29.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Orma (ഓർമ്മ)
|
|
23
|
+
|
|
24
|
+
> **Orma** *(Dravidian / Malayalam for "memory" or "recollection")* is an episode-first, zero-latency long-term memory layer built specifically for local, open-weight LLMs (3B–8B models on consumer 8–16 GB laptops).
|
|
25
|
+
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](https://www.python.org/downloads/)
|
|
28
|
+
[](tests/)
|
|
29
|
+
|
|
30
|
+
Orma runs as an **OpenAI-compatible proxy** in front of Ollama, llama.cpp, LM Studio, or vLLM. It also ships a fast CLI and an embedded Python SDK. Everything runs locally inside SQLite with `sqlite-vec` and fast local embeddings—**no external servers, no cloud API keys, and zero data loss.**
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why Orma?
|
|
35
|
+
|
|
36
|
+
Most agent memory frameworks (like Mem0 or Cognee) were designed for massive cloud models (GPT-4o, Claude 3.5 Sonnet) and enterprise graph databases:
|
|
37
|
+
1. **They freeze chat turns:** Calling an LLM synchronously on every `add()` causes 5–15 second freezes on local laptops.
|
|
38
|
+
2. **They hallucinate graphs:** 3B models struggle with complex entity-relation extraction and invert relationships or emit malformed JSON.
|
|
39
|
+
3. **They permanently corrupt facts:** Prompting a 3B model with `DELETE / UPDATE` leads to destructive, irreversible overwrites.
|
|
40
|
+
|
|
41
|
+
### The Orma Approach
|
|
42
|
+
* **Episodes are the memory:** Every conversation turn, document, or tool log is kept in full in an append-only log and indexed into sentences immediately.
|
|
43
|
+
* **Zero LLM calls on recall:** Recall runs in `< 25ms` using hybrid vector search (sqlite-vec) + BM25 full-text search (FTS5) with bounded recency boosting (ACT-R).
|
|
44
|
+
* **Sleep consolidates state asynchronously:** While the machine is idle, a background worker uses the local model to extract single-valued current states (`now true`) and dated events citing exact line numbers.
|
|
45
|
+
* **Deterministic code guards:** Time resolution, pronoun carry-over (*anuvṛtti*), and contradiction resolution are handled in verified Python code, not by trusting small model arithmetic.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Architecture & Data Flow
|
|
50
|
+
|
|
51
|
+
### 1. The Fast Request & Recall Path (Zero LLM Calls)
|
|
52
|
+
|
|
53
|
+
```mermaid
|
|
54
|
+
flowchart LR
|
|
55
|
+
User["User / Chat Client"] -->|"1. Chat Request"| Proxy["Orma Proxy (Port 8080)"]
|
|
56
|
+
Proxy -->|"2. Hybrid Query"| Store[("SQLite + sqlite-vec + FTS5")]
|
|
57
|
+
Store -->|"3. Match Sentences & Current States"| Ranker["Ranker & Budget Packer"]
|
|
58
|
+
Ranker -->|"4. Injects Memory Block"| Upstream["Local LLM (Ollama / llama.cpp)"]
|
|
59
|
+
Upstream -->|"5. Stream Response"| Proxy
|
|
60
|
+
Proxy -->|"6. Return to User"| User
|
|
61
|
+
Proxy -.->|"7. Async Append Episode"| Store
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. The Background Sleep Path (Idempotent Consolidation)
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
flowchart TD
|
|
68
|
+
Pending["Pending Episodes in SQLite"] --> Batcher["Batcher (Lines [s1, s2, ...])"]
|
|
69
|
+
Batcher --> LocalLLM["Local Sleep Model (Qwen2.5-3B / Llama-3.2-3B)"]
|
|
70
|
+
LocalLLM -->|"Schema Output: States & Events citing Line #"| Validator["Deterministic Grounding Engine"]
|
|
71
|
+
|
|
72
|
+
subgraph Verification ["Deterministic Python Code"]
|
|
73
|
+
Validator --> V1["Verify Line Citations Exist"]
|
|
74
|
+
Validator --> V2["Pronoun & Anuvrtti Resolution"]
|
|
75
|
+
Validator --> V3["Timezone Date Parsing (clock.py)"]
|
|
76
|
+
Validator --> V4["Grounding Check (No Hallucinations)"]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
Verification --> Integrator["State Store"]
|
|
80
|
+
Integrator -->|"Closes Contradicted Old States"| ActiveStates[("Current States & History")]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quickstart
|
|
86
|
+
|
|
87
|
+
### Prerequisites
|
|
88
|
+
* Python 3.10+
|
|
89
|
+
* [Ollama](https://ollama.ai) or [llama.cpp](https://github.com/ggerganov/llama.cpp)
|
|
90
|
+
|
|
91
|
+
### 1. Installation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/wizardwithcodehazard/orma.git
|
|
95
|
+
cd orma
|
|
96
|
+
python -m venv .venv
|
|
97
|
+
|
|
98
|
+
# On Linux/macOS:
|
|
99
|
+
source .venv/bin/activate
|
|
100
|
+
# On Windows PowerShell:
|
|
101
|
+
# .venv\Scripts\Activate.ps1
|
|
102
|
+
|
|
103
|
+
pip install -e ".[dev]"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Run tests to verify the setup (182 tests run deterministically with zero downloads in ~17s):
|
|
107
|
+
```bash
|
|
108
|
+
python -m pytest -q
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Run the Proxy
|
|
112
|
+
|
|
113
|
+
Pull a lightweight model in Ollama:
|
|
114
|
+
```bash
|
|
115
|
+
ollama pull llama3.2:3b
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Start the Orma proxy:
|
|
119
|
+
```bash
|
|
120
|
+
python -m smruti proxy --model llama3.2:3b
|
|
121
|
+
# Listening on http://127.0.0.1:8080 (forwarding upstream to http://localhost:11434/v1)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Point any OpenAI-compatible client (Open WebUI, Continue.dev, Cursor, or Curl) to `http://localhost:8080/v1`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
curl http://localhost:8080/v1/chat/completions \
|
|
128
|
+
-H "Content-Type: application/json" \
|
|
129
|
+
-d '{
|
|
130
|
+
"model": "llama3.2:3b",
|
|
131
|
+
"messages": [
|
|
132
|
+
{"role": "user", "content": "I live in Pune and work on compilers."}
|
|
133
|
+
]
|
|
134
|
+
}'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Inspect Memory
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Add a quick note or document
|
|
141
|
+
python -m smruti add "My dentist is Dr. Rao in Kothrud."
|
|
142
|
+
python -m smruti add docs/notes.md
|
|
143
|
+
|
|
144
|
+
# Search memory
|
|
145
|
+
python -m smruti search "Where is my dentist?"
|
|
146
|
+
|
|
147
|
+
# View active facts & history
|
|
148
|
+
python -m smruti facts --history
|
|
149
|
+
|
|
150
|
+
# Trigger background sleep manually
|
|
151
|
+
python -m smruti sleep --model llama3.2:3b
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Python SDK
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from smruti import Memory
|
|
160
|
+
|
|
161
|
+
# Initialize memory (default SQLite db: ~/.smruti/memory.db or SMRUTI_DIR)
|
|
162
|
+
mem = Memory(upstream="http://localhost:11434/v1", model="llama3.2:3b")
|
|
163
|
+
|
|
164
|
+
# Add memories directly
|
|
165
|
+
mem.add("I moved from Pune to Bangalore last week.", role="user")
|
|
166
|
+
mem.add_file("project_notes.txt")
|
|
167
|
+
|
|
168
|
+
# Build context for an outgoing prompt (hybrid search + current states)
|
|
169
|
+
messages = mem.build_context([
|
|
170
|
+
{"role": "user", "content": "Where do I currently live?"}
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
# Stream or call your model
|
|
174
|
+
reply = mem.chat([{"role": "user", "content": "Where do I live?"}])
|
|
175
|
+
print(reply)
|
|
176
|
+
|
|
177
|
+
# Background consolidation
|
|
178
|
+
mem.sleep()
|
|
179
|
+
|
|
180
|
+
# Rebuild all memory from scratch using a stronger model if needed
|
|
181
|
+
# mem.rebuild()
|
|
182
|
+
|
|
183
|
+
mem.close()
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Memory Prompt Output Format
|
|
189
|
+
|
|
190
|
+
When Orma recalls memories, it packs them into a tight token budget injected into the system prompt:
|
|
191
|
+
|
|
192
|
+
```markdown
|
|
193
|
+
## Memory
|
|
194
|
+
What you remember about the person you are talking to. Today is 2026-10-24.
|
|
195
|
+
|
|
196
|
+
Now true:
|
|
197
|
+
- user · home city: Bangalore (since 2026-10-17)
|
|
198
|
+
|
|
199
|
+
Things said before:
|
|
200
|
+
- 2026-10-10 user: I'm Sahil and I live in Pune. [outdated: now user · home city: Bangalore]
|
|
201
|
+
- 2026-10-18 user: Last Saturday I went trekking to Sinhagad with my sister Asha.
|
|
202
|
+
|
|
203
|
+
Events:
|
|
204
|
+
- 2026-10-12: user went trekking at Sinhagad with Asha
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Comparison: Orma vs. Mem0 & Cognee
|
|
210
|
+
|
|
211
|
+
| Feature | Mem0 | Cognee | **Orma** |
|
|
212
|
+
| :--- | :--- | :--- | :--- |
|
|
213
|
+
| **Target LLMs** | Cloud (GPT-4o) | Cloud (GPT-4o) | **Local 3B–8B (Ollama / llama.cpp)** |
|
|
214
|
+
| **Write Overhead** | Synchronous LLM calls (5–15s delay) | Multi-stage graph pipelines | **Instant (< 5ms SQLite append)** |
|
|
215
|
+
| **Recall Overhead** | Vector + LLM synthesis | Graph traversal + LLM | **Instant (< 25ms hybrid search, 0 LLM calls)** |
|
|
216
|
+
| **Data Safety** | Destructive `DELETE / UPDATE` | Lossy graph extraction | **Lossless append-only logs (`rebuild` anytime)** |
|
|
217
|
+
| **Hallucination Protection** | Relies on LLM accuracy | Relies on Pydantic output | **Line citations + deterministic Python guards** |
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Configuration & Ablation Switches
|
|
222
|
+
|
|
223
|
+
Orma comes with built-in ablation switches in `smruti/config.py` to evaluate every mechanism against raw episodic baselines:
|
|
224
|
+
|
|
225
|
+
* `enable_semantic`: Toggle state and event extraction (off = pure episodic sentence search baseline).
|
|
226
|
+
* `enable_novelty_gate`: Drops near-duplicate records ($\ge 0.97$ cosine similarity).
|
|
227
|
+
* `enable_similarity_weighting`: Calibrates vector similarity against adaptive space noise floor.
|
|
228
|
+
* `enable_base_level`: Enables ACT-R recency and repetition boost.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
Distributed under the **Apache-2.0 License**. See [LICENSE](LICENSE) for more information.
|