model-d 0.4.0__py3-none-any.whl

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.
ocm/build.py ADDED
@@ -0,0 +1,77 @@
1
+ """Build a workspace over one or more repositories (and their docs).
2
+
3
+ python -m ocm build <folder> [<folder> ...] --out <workspace folder>
4
+
5
+ Creates <workspace>/memory (the fingerprints: 1,024 bits per unit, from a small embedding model that runs on this
6
+ machine), <workspace>/keyword.db (a keyword index for the router's keyword lane), <workspace>/spans.json (the first and
7
+ last line of every unit, so a taught question's vector can be put on the unit holding a given line) and
8
+ <workspace>/workspace.json (which folders, how many units, the router's rare-word threshold). Rebuild after large code changes; teaching later only adds."""
9
+ import argparse, json, os, shutil, sqlite3, time
10
+
11
+ from .cmlm_vector import Memory
12
+ from .units import files, units
13
+
14
+ THETA_DEFAULT = 9.349 # the router law's 44% rule on code questions (measured on transformers)
15
+
16
+
17
+ def build(roots, out, model=None, device=None, log=print, carry=None, prune_after=None, cache=None):
18
+ if os.path.exists(os.path.join(out, "memory", "manifest.json")):
19
+ raise SystemExit(f"{out} already holds a workspace; remove it or choose another --out")
20
+ os.makedirs(out, exist_ok=True)
21
+ roots = {os.path.basename(os.path.abspath(r).rstrip("/")) or "root": os.path.abspath(r) for r in roots}
22
+ m = Memory.create(os.path.join(out, "memory"), bits=1024, seed=0, model=model, device=device)
23
+ cache = cache or os.environ.get("MODELD_CACHE")
24
+ if cache: # rebuilds embed only the units whose text changed
25
+ from .embed_cache import CachedEmbedder
26
+ m._embedder = CachedEmbedder(m.embedder, os.path.expanduser(cache), m.man.get("model", ""))
27
+ F = files(roots); U = []
28
+ for name, rel, path in F:
29
+ try:
30
+ text = open(path, encoding="utf-8").read()
31
+ except (UnicodeDecodeError, OSError):
32
+ continue
33
+ us = units(rel, text)
34
+ m.sources.append({"id": len(m.sources), "path": rel, "sha256": "", "units": len(us)})
35
+ U.extend((t, len(m.sources) - 1, a, b) for t, a, b in us)
36
+ if not U:
37
+ raise SystemExit("no code or documentation found in those folders")
38
+ step = max(1, len(U) // 20000)
39
+ m.freeze_mean([u[0] for u in U[::step][:20000]])
40
+ log(f"{len(m.sources):,} files, {len(U):,} units; fingerprinting (first run downloads the embedding model)")
41
+ t0, seen, spans = time.time(), set(), []
42
+ for s in range(0, len(U), 2048):
43
+ chunk = [u for u in U[s:s + 2048] if u[0] not in seen]; seen.update(u[0] for u in chunk)
44
+ for (t, sid, a, last), b in zip(chunk, m.measure([u[0] for u in chunk])):
45
+ m._add_fact(t, sid, a, 0, True, b); spans.append([a, last])
46
+ if len(m.pending_f) >= 200_000:
47
+ m.save()
48
+ log(f" {min(s + 2048, len(U)):,} / {len(U):,} units, {time.time() - t0:.0f}s")
49
+ m.save()
50
+ db = os.path.join(out, "keyword.db")
51
+ con = sqlite3.connect(db); con.execute("create virtual table s using fts5(t)")
52
+ con.executemany("insert into s(rowid, t) values (?, ?)", ((j + 1, t) for j, t in enumerate(m.texts))); con.commit(); con.close()
53
+ json.dump(spans, open(os.path.join(out, "spans.json"), "w"))
54
+ json.dump({"roots": roots, "files": len(m.sources), "units": len(m.texts), "theta": THETA_DEFAULT,
55
+ "built": time.strftime("%Y-%m-%dT%H:%M:%S")}, open(os.path.join(out, "workspace.json"), "w"), indent=1)
56
+ shutil.copytree(os.path.join(out, "memory"), os.path.join(out, "memory_at_build"))
57
+ reuse = f"; {m._embedder.embedded:,} newly embedded, the rest from the cache" if cache else ""
58
+ log(f"workspace ready: {out} ({len(m.texts):,} units from {len(m.sources):,} files{reuse})")
59
+ if carry:
60
+ from .server import Workspace
61
+ r = Workspace(out).carry(carry, prune_after)
62
+ log(f"carried from {carry}: {r['carried']} of {r['entries']} taught entries ({r['moved']} moved, "
63
+ f"{r['reformatted']} found after reformatting), {r['abstained']} abstained (their line is gone), "
64
+ f"{r['pruned']} pruned; Life sha {r['life sha']}")
65
+
66
+
67
+ def main(argv=None):
68
+ ap = argparse.ArgumentParser(prog="ocm build"); ap.add_argument("folders", nargs="+"); ap.add_argument("--out", required=True)
69
+ ap.add_argument("--model"); ap.add_argument("--device")
70
+ ap.add_argument("--cache", help="an embedding cache file ($MODELD_CACHE): a rebuild embeds only changed code")
71
+ ap.add_argument("--carry", help="an earlier workspace whose taught knowledge (its Life) is carried into this one")
72
+ ap.add_argument("--prune-after", type=int, metavar="N",
73
+ help="with --carry: remove a taught entry whose line has not been found in N carries in a row")
74
+ a = ap.parse_args(argv)
75
+ if a.prune_after is not None and (a.prune_after < 1 or not a.carry):
76
+ ap.error("--prune-after needs --carry and a whole number of at least 1")
77
+ build(a.folders, a.out, a.model, a.device, carry=a.carry, prune_after=a.prune_after, cache=a.cache)
ocm/calc.py ADDED
@@ -0,0 +1,51 @@
1
+ """The savings calculator: expected model requests with and without the workspace, from per-question costs measured in
2
+ live tests (ocm/data/pools.json), with a 95% bootstrap interval.
3
+
4
+ python -m ocm calc --repeat 0.5 --deep 0.3 [--questions-per-month 16800]
5
+
6
+ --repeat share of questions that repeat something a teammate already asked (in any words)
7
+ --deep share of questions that are multi-file deep dives (the rest are lookups and short scans)
8
+ Requests per question = search-and-read calls + a fixed session overhead per question (both arms) + batched teaching
9
+ (one call per session of about six questions, workspace arm). This arithmetic predicted a held-out live run before it
10
+ ran: 83 requests predicted, 84 measured; 1.66x predicted, 1.66x measured (results/RESULTS.md)."""
11
+ import argparse, json, os, random
12
+
13
+ POOLS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "pools.json") # shipped inside the package
14
+
15
+
16
+ def expect(P, p, d, over, teach, rng=None):
17
+ pick = (lambda xs: [rng.choice(xs) for _ in xs]) if rng else (lambda xs: xs)
18
+ m = {k: sum(x) / len(x) for k, x in ((k, pick(v)) for k, v in P.items())}
19
+ def arm(a):
20
+ look = (1 - p) * m[f"lookup first {a}"] + p * m[f"lookup repeat {a}"]
21
+ deep = (1 - p) * m[f"deep first {a}"] + p * m[f"deep repeat {a}"]
22
+ return (1 - d) * look + d * deep + over + (teach if a == "memory" else 0)
23
+ return arm("tools"), arm("memory")
24
+
25
+
26
+ def main(argv=None):
27
+ ap = argparse.ArgumentParser(prog="ocm calc")
28
+ ap.add_argument("--repeat", type=float); ap.add_argument("--deep", type=float, default=0.0)
29
+ ap.add_argument("--questions-per-month", type=int, default=0); ap.add_argument("--seed", type=int, default=20261010)
30
+ ap.add_argument("--table", action="store_true", help="the published table: repeats 0 to 90%%, with and without 30%% deep dives")
31
+ a = ap.parse_args(argv)
32
+ D = json.load(open(POOLS)); P = D["pools"]; over, teach = D["overhead per question"], D["teach per question, batched"]
33
+ if a.table: # one generator over every row, in this order, as the published table
34
+ rng = random.Random(a.seed); q = a.questions_per_month or 16800
35
+ print(f"{'deep dives':>10} {'repeats':>8} {'without':>8} {'with':>6} {'saving':>7} 95% interval fewer per month at {q:,} questions")
36
+ for d in (0.0, 0.3):
37
+ for p in (0.0, 0.25, 0.5, 0.75, 0.9):
38
+ t, m = expect(P, p, d, over, teach)
39
+ bs = sorted(x / y for x, y in (expect(P, p, d, over, teach, rng) for _ in range(5000)))
40
+ print(f"{d:>10.0%} {p:>8.0%} {t:>8.2f} {m:>6.2f} {t / m:>6.2f}x [{bs[125]:.2f}, {bs[4875]:.2f}] {q * (t - m):>9,.0f}")
41
+ return
42
+ if a.repeat is None:
43
+ ap.error("--repeat is required (or use --table)")
44
+ t, m = expect(P, a.repeat, a.deep, over, teach)
45
+ rng = random.Random(a.seed); boots = sorted(x / y for x, y in (expect(P, a.repeat, a.deep, over, teach, rng) for _ in range(5000)))
46
+ print(f"requests per question: without the workspace {t:.2f}, with it {m:.2f}")
47
+ print(f"saving: {t / m:.2f}x fewer requests (95% interval {boots[125]:.2f} to {boots[4875]:.2f})")
48
+ if a.questions_per_month:
49
+ q = a.questions_per_month
50
+ print(f"per month at {q:,} questions: {q * t:,.0f} requests without, {q * m:,.0f} with, {q * (t - m):,.0f} fewer")
51
+ print("billed input falls less than requests (measured: 1.40x when requests fell 1.66x), because readouts add reading.")
ocm/cli.py ADDED
@@ -0,0 +1,182 @@
1
+ """modeld: map a repository once, ask it where the answer is, teach it what was found.
2
+
3
+ modeld build [path] [--out DIR] map the repository into <path>/modeld-out (alias: modeld .)
4
+ modeld ask "question" [--scope code] the ranked places to look; --scope code leaves documentation out
5
+ modeld next "question" the next readout for the same question
6
+ modeld teach "question" path:line teach the line the answer is on
7
+ modeld stats units, files, questions taught
8
+ modeld install [--team URL] register Model D with the coding assistants found on this machine
9
+ modeld init <claude|kiro|codex|gemini|all> [repo] [--url URL]
10
+ wire one repository's agent to a team server
11
+ modeld serve [workspace] [--port 7700] [--host 127.0.0.1] [--build REPO ...]
12
+ the team's shared workspace server (--build maps the repos first if empty)
13
+ modeld mcp the MCP server over stdio (a local workspace, or $OCM_URL)
14
+ modeld fetch-model [--to DIR] download the embedding model once, for offline and air-gapped machines
15
+ modeld hybrid | measure | calc | hook ...
16
+ as in `python -m ocm` (see README)
17
+
18
+ ask, next, teach and stats use the nearest modeld-out folder (this folder or a parent), $MODELD_WORKSPACE, or
19
+ --workspace; with --url (or $OCM_URL) they ask a team server instead."""
20
+ import argparse, json, os, sys, urllib.request
21
+
22
+ OUT = "modeld-out"
23
+
24
+
25
+ def find_workspace(start=None):
26
+ """--workspace, else $MODELD_WORKSPACE, else the nearest modeld-out above the current folder, else None."""
27
+ env = os.environ.get("MODELD_WORKSPACE")
28
+ if env:
29
+ return env
30
+ d = os.path.abspath(start or os.getcwd())
31
+ while True:
32
+ cand = os.path.join(d, OUT)
33
+ if os.path.exists(os.path.join(cand, "workspace.json")):
34
+ return cand
35
+ parent = os.path.dirname(d)
36
+ if parent == d:
37
+ return None
38
+ d = parent
39
+
40
+
41
+ def _post(url, path, body):
42
+ req = urllib.request.Request(f"{url.rstrip('/')}/{path}", data=json.dumps(body).encode(),
43
+ headers={"Content-Type": "application/json"})
44
+ return urllib.request.urlopen(req, timeout=300).read().decode()
45
+
46
+
47
+ def _call(args, path, body, then=None):
48
+ """Run one workspace call (or two, for `next`) locally or on the team server; print the result."""
49
+ url = args.url or os.environ.get("OCM_URL")
50
+ if url:
51
+ print(_post(url, then or path, body)); return
52
+ ws = args.workspace or find_workspace()
53
+ if not ws:
54
+ sys.exit(f"no {OUT} folder here or above: run `modeld build .` first, or pass --workspace or --url")
55
+ os.environ.setdefault("OCM_OFFLINE", "1")
56
+ from .server import Workspace
57
+ w = Workspace(ws)
58
+ out = w.handle(path, body)
59
+ if then: # a local process has no earlier readout to advance from: ask, then next
60
+ out = w.handle(then, body)
61
+ print(out)
62
+
63
+
64
+ def _common(ap):
65
+ ap.add_argument("--workspace"); ap.add_argument("--url")
66
+
67
+
68
+ def build_cmd(argv):
69
+ ap = argparse.ArgumentParser(prog="modeld build")
70
+ ap.add_argument("path", nargs="*", default=["."]); ap.add_argument("--out")
71
+ ap.add_argument("--model"); ap.add_argument("--device"); ap.add_argument("--carry")
72
+ ap.add_argument("--cache", help="an embedding cache file ($MODELD_CACHE): a rebuild embeds only changed code")
73
+ a = ap.parse_args(argv)
74
+ roots = [os.path.abspath(p) for p in a.path]
75
+ out = os.path.abspath(a.out or os.path.join(roots[0], OUT))
76
+ carry = a.carry or (out if os.path.exists(os.path.join(out, "workspace.json")) else None)
77
+ from .build import build
78
+ if carry and os.path.abspath(carry) == out: # a rebuild in place carries what was taught
79
+ import shutil, tempfile
80
+ tmp = tempfile.mkdtemp(prefix="modeld-out.new-")
81
+ build(roots, tmp, a.model, a.device, carry=out, cache=a.cache)
82
+ for name in os.listdir(out): # replace the contents, not the folder: it may be a mount
83
+ p = os.path.join(out, name)
84
+ shutil.rmtree(p) if os.path.isdir(p) and not os.path.islink(p) else os.remove(p)
85
+ for name in os.listdir(tmp):
86
+ shutil.move(os.path.join(tmp, name), os.path.join(out, name))
87
+ os.rmdir(tmp)
88
+ else:
89
+ build(roots, out, a.model, a.device, carry=carry, cache=a.cache)
90
+ open(os.path.join(out, ".gitignore"), "w").write("*\n") # the workspace never lands in git by accident
91
+ print(f"\nmapped into {out}\nnext: modeld ask \"your question\" --scope code")
92
+
93
+
94
+ def ask_cmd(argv, path="ask"):
95
+ ap = argparse.ArgumentParser(prog=f"modeld {path}"); ap.add_argument("question")
96
+ ap.add_argument("--scope", choices=["all", "code"]); _common(ap)
97
+ a = ap.parse_args(argv)
98
+ body = {"question": a.question, **({"scope": a.scope} if a.scope else {})}
99
+ if path == "next":
100
+ _call(a, "ask", body, then="next")
101
+ else:
102
+ _call(a, "ask", body)
103
+
104
+
105
+ def teach_cmd(argv):
106
+ ap = argparse.ArgumentParser(prog="modeld teach"); ap.add_argument("question"); ap.add_argument("at")
107
+ _common(ap)
108
+ a = ap.parse_args(argv)
109
+ _call(a, "teach", {"pairs": [{"question": a.question, "at": a.at}]})
110
+
111
+
112
+ def stats_cmd(argv):
113
+ ap = argparse.ArgumentParser(prog="modeld stats"); _common(ap)
114
+ _call(ap.parse_args(argv), "stats", {})
115
+
116
+
117
+ def serve_cmd(argv):
118
+ ap = argparse.ArgumentParser(prog="modeld serve"); ap.add_argument("workspace", nargs="?")
119
+ ap.add_argument("--port", type=int, default=7700); ap.add_argument("--host", default="127.0.0.1")
120
+ ap.add_argument("--build", nargs="+", metavar="REPO", help="map these repositories into the workspace first, if it is empty")
121
+ a = ap.parse_args(argv)
122
+ ws = a.workspace or find_workspace()
123
+ if a.build and ws and not os.path.exists(os.path.join(ws, "workspace.json")):
124
+ build_cmd([*a.build, "--out", ws])
125
+ if not ws or not os.path.exists(os.path.join(ws, "workspace.json")):
126
+ sys.exit(f"no workspace at {ws or 'a ' + OUT + ' folder here or above'}: run `modeld build <repo>` first, or pass --build")
127
+ os.environ.setdefault("OCM_OFFLINE", "1")
128
+ from .server import serve
129
+ print(f"serving {ws} at http://{a.host}:{a.port}"); sys.stdout.flush()
130
+ serve(ws, a.port, a.host)
131
+
132
+
133
+ def fetch_model_cmd(argv):
134
+ ap = argparse.ArgumentParser(prog="modeld fetch-model")
135
+ ap.add_argument("--to", help="a folder to hold the model (used as HF_HOME); default: the normal Hugging Face cache")
136
+ a = ap.parse_args(argv)
137
+ if a.to:
138
+ os.environ["HF_HOME"] = os.path.abspath(a.to)
139
+ os.environ.pop("HF_HUB_OFFLINE", None); os.environ.pop("TRANSFORMERS_OFFLINE", None)
140
+ from .cmlm_vector.embed import DEFAULT_MODEL
141
+ from huggingface_hub import snapshot_download
142
+ path = snapshot_download(DEFAULT_MODEL, allow_patterns=["*.json", "*.txt", "model.safetensors"]) # weights + tokenizer only
143
+ print(f"{DEFAULT_MODEL} is at {path}")
144
+ if a.to:
145
+ print(f"on the offline machine: export HF_HOME={os.path.abspath(a.to)} OCM_OFFLINE=1")
146
+
147
+
148
+ def main(argv=None):
149
+ argv = list(sys.argv[1:] if argv is None else argv)
150
+ if not argv or argv[0] in ("-h", "--help", "help"):
151
+ print(__doc__); return
152
+ cmd, rest = argv[0], argv[1:]
153
+ if cmd == "--version":
154
+ from . import __version__
155
+ print(f"modeld {__version__}"); return
156
+ if cmd not in COMMANDS and os.path.isdir(cmd): # modeld . == modeld build .
157
+ cmd, rest = "build", argv
158
+ if cmd not in COMMANDS:
159
+ print(__doc__); sys.exit(2)
160
+ COMMANDS[cmd](rest)
161
+
162
+
163
+ def _module(name):
164
+ def run(rest):
165
+ import importlib
166
+ importlib.import_module(f"ocm.{name}").main(rest)
167
+ return run
168
+
169
+
170
+ def install_cmd(argv):
171
+ from .install import main as install_main
172
+ install_main(argv)
173
+
174
+
175
+ COMMANDS = {"build": build_cmd, "ask": ask_cmd, "next": lambda r: ask_cmd(r, "next"), "teach": teach_cmd,
176
+ "stats": stats_cmd, "serve": serve_cmd, "fetch-model": fetch_model_cmd, "install": install_cmd,
177
+ "init": _module("init"), "mcp": _module("mcp"), "hybrid": _module("hybrid"), "measure": _module("measure"),
178
+ "calc": _module("calc"), "hook": _module("loop")}
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -0,0 +1,6 @@
1
+ """cmlm-vector: the CMLM memory with vector generalization and a learned law, as an installable package."""
2
+ __version__ = "0.2.0"
3
+
4
+ from .memory import Memory # noqa: E402
5
+
6
+ __all__ = ["Memory", "__version__"]
@@ -0,0 +1,38 @@
1
+ """Text into units: the sentences a memory stores and returns.
2
+
3
+ Paragraphs are split on blank lines, then into sentences; a line break inside a paragraph is kept as a boundary
4
+ only when the next line starts a new thought (a capital, a digit or a bullet), so wrapped prose is rejoined. Units
5
+ under `min_words` words are dropped; a unit over `max_chars` is cut at word boundaries. Deterministic: the same
6
+ text gives the same units in the same order."""
7
+ import re
8
+
9
+ _SENT = re.compile(r"(?<=[.!?])\s+(?=[A-Z0-9\"'(\[])")
10
+ # mail and news metadata: near-identical lines in every message, which crowd a readout and rarely answer anything
11
+ HEADER = re.compile(r"^(From|Subject|Organi[sz]ation|Lines|Reply-To|NNTP-Posting-Host|Distribution|Keywords|Summary|"
12
+ r"Article-I\.D\.|Expires|X-[\w-]+|Sender|Path|Newsgroups|Message-ID|References|Date|To|Cc):|^In article <|"
13
+ r"(writes|wrote):$", re.I)
14
+
15
+
16
+ def units(text, min_words=4, max_chars=400, drop_headers=False):
17
+ if drop_headers:
18
+ return [u for u in units(text, min_words, max_chars) if not HEADER.search(u)]
19
+ out = []
20
+ for para in re.split(r"\n\s*\n", text.replace("\r", "")):
21
+ lines = [l.strip() for l in para.split("\n") if l.strip()]
22
+ if not lines:
23
+ continue
24
+ joined = lines[0]
25
+ for l in lines[1:]:
26
+ joined += ("\n" if re.match(r"[A-Z0-9\-*>•]", l) and re.search(r"[.!?:]$", joined) else " ") + l
27
+ for piece in joined.split("\n"):
28
+ for s in _SENT.split(piece):
29
+ s = " ".join(s.split())
30
+ while len(s) > max_chars:
31
+ cut = s.rfind(" ", 0, max_chars)
32
+ cut = cut if cut > max_chars // 2 else max_chars
33
+ head, s = s[:cut], s[cut:].strip()
34
+ if len(head.split()) >= min_words:
35
+ out.append(head)
36
+ if len(s.split()) >= min_words:
37
+ out.append(s)
38
+ return out
ocm/cmlm_vector/cli.py ADDED
@@ -0,0 +1,75 @@
1
+ """cmlm-vector: a memory a frontier model asks and teaches, instead of a transcript it rereads.
2
+
3
+ cmlm-vector init MEM [--bits 1024] [--seed 0] [--model BAAI/bge-base-en-v1.5] [--device cpu|mps|cuda]
4
+ cmlm-vector ingest MEM PATH... [--glob '*.txt'] [--drop-headers] files or folders; already-held files are skipped
5
+ cmlm-vector ask MEM "question" [-k 5] [--context 1] [--json] single shot: the top k facts with their sources
6
+ cmlm-vector teach MEM --cue "how a person might ask" --fact "the one sentence that answers it"
7
+ cmlm-vector info MEM
8
+ cmlm-vector verify MEM every file against its sha256, planes against seed
9
+
10
+ A memory is a folder; copy it and it answers the same way on another machine (use the same device for bit-exact
11
+ replication). Nothing is sent anywhere: embeddings are computed locally."""
12
+ import argparse, glob as globmod, json, os, sys
13
+
14
+ from .memory import Memory
15
+
16
+
17
+ def _files(paths, pattern):
18
+ out = []
19
+ for p in paths:
20
+ if os.path.isdir(p):
21
+ out += [f for f in globmod.glob(os.path.join(p, "**", pattern), recursive=True) if os.path.isfile(f)]
22
+ elif os.path.isfile(p):
23
+ out.append(p)
24
+ return sorted(set(out))
25
+
26
+
27
+ def main(argv=None):
28
+ p = argparse.ArgumentParser(prog="cmlm-vector", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
29
+ sub = p.add_subparsers(dest="cmd", required=True)
30
+ a = sub.add_parser("init"); a.add_argument("mem"); a.add_argument("--bits", type=int, default=1024)
31
+ a.add_argument("--seed", type=int, default=0); a.add_argument("--model"); a.add_argument("--device")
32
+ a = sub.add_parser("ingest"); a.add_argument("mem"); a.add_argument("paths", nargs="+")
33
+ a.add_argument("--glob", default="*.txt"); a.add_argument("--keep-float", action="store_true")
34
+ a.add_argument("--drop-headers", action="store_true", help="skip mail/news metadata lines (From:, Subject:, ...)")
35
+ a.add_argument("--no-center", action="store_true", help="do not centre the bits on the corpus mean")
36
+ a = sub.add_parser("ask"); a.add_argument("mem"); a.add_argument("question"); a.add_argument("-k", type=int, default=5)
37
+ a.add_argument("--context", type=int, default=1); a.add_argument("--json", action="store_true")
38
+ a = sub.add_parser("teach"); a.add_argument("mem"); a.add_argument("--cue", required=True); a.add_argument("--fact", required=True)
39
+ a = sub.add_parser("info"); a.add_argument("mem")
40
+ a = sub.add_parser("verify"); a.add_argument("mem")
41
+ x = p.parse_args(argv)
42
+
43
+ if x.cmd == "init":
44
+ m = Memory.create(x.mem, bits=x.bits, seed=x.seed, model=x.model, device=x.device)
45
+ print(json.dumps(m.info(), indent=1)); return 0
46
+ if x.cmd == "verify": # before opening: a damaged memory may not open at all
47
+ bad = Memory.verify_path(x.mem)
48
+ print("verified: every file matches its sha256 and the planes match the seed" if not bad else f"MISMATCH: {bad}")
49
+ return 0 if not bad else 1
50
+ m = Memory(x.mem)
51
+ if x.cmd == "ingest":
52
+ fs = _files(x.paths, x.glob)
53
+ print(f"ingesting {len(fs):,} files into {x.mem}", flush=True)
54
+ r = m.ingest(fs, keep_float=x.keep_float, log=lambda s: print(s, flush=True), drop_headers=x.drop_headers, center=not x.no_center)
55
+ print(json.dumps({**r, **m.info()}, indent=1)); return 0
56
+ if x.cmd == "ask":
57
+ r = m.recall(x.question, k=x.k, context=x.context)
58
+ if x.json:
59
+ print(json.dumps(r, indent=1)); return 0
60
+ if r["status"] == "abstain":
61
+ print("NOTHING CLOSE: no stored fact stands out for this question; these are the nearest. Ask again in other words if none answers it.")
62
+ for i, row in enumerate(r["rows"], 1):
63
+ flag = " [ambiguous: check against the question]" if row["flag"] else ""
64
+ print(f"{i}. {row['fact']}{flag}\n source: {row['source']} (unit {row['unit']})")
65
+ if row["context"]:
66
+ print(" context: " + " | ".join(row["context"]))
67
+ return 0
68
+ if x.cmd == "teach":
69
+ r = m.teach([(x.cue, x.fact)]); m.save(); print(json.dumps(r)); return 0
70
+ if x.cmd == "info":
71
+ print(json.dumps(m.info(), indent=1)); return 0
72
+
73
+
74
+ if __name__ == "__main__":
75
+ sys.exit(main())
@@ -0,0 +1,66 @@
1
+ """Sentence embeddings from a small local model, at no token cost: nothing leaves the machine.
2
+
3
+ BAAI/bge-base-en-v1.5 by default (768 dimensions, CLS pooled, normalized), read from the local Hugging Face cache.
4
+ Batched in order of length so padding stays small, on Apple's GPU or CUDA when present. The device is recorded in
5
+ the memory's manifest: CPU and GPU arithmetic differ in the last bits, so an exact replication uses the same device
6
+ (or --device cpu everywhere)."""
7
+ import os
8
+
9
+ DEFAULT_MODEL = os.environ.get("CMLM_EMBED_MODEL", "BAAI/bge-base-en-v1.5")
10
+ # The instruction a model's card says to put before a search query matched against passages (not before passages,
11
+ # and not before a query matched against other questions).
12
+ QUERY_PREFIX = {"BAAI/bge-small-en-v1.5": "Represent this sentence for searching relevant passages: ",
13
+ "BAAI/bge-base-en-v1.5": "Represent this sentence for searching relevant passages: ",
14
+ "BAAI/bge-large-en-v1.5": "Represent this sentence for searching relevant passages: "}
15
+
16
+
17
+ def pick_device(device=None):
18
+ import torch
19
+ if device:
20
+ return device
21
+ if torch.cuda.is_available():
22
+ return "cuda"
23
+ if getattr(torch.backends, "mps", None) and torch.backends.mps.is_available():
24
+ return "mps"
25
+ return "cpu"
26
+
27
+
28
+ class Embedder:
29
+ def __init__(self, name=DEFAULT_MODEL, device=None, offline=None, max_length=128):
30
+ # The first run downloads the embedding model (about 440 MB) from Hugging Face; set OCM_OFFLINE=1 afterwards to
31
+ # guarantee nothing is fetched. Your code and questions are never sent anywhere: embedding runs on this machine.
32
+ offline = os.environ.get("OCM_OFFLINE") == "1" if offline is None else offline
33
+ if offline:
34
+ os.environ.setdefault("HF_HUB_OFFLINE", "1"); os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
35
+ import torch
36
+ from transformers import AutoModel, AutoTokenizer
37
+ self.torch, self.name, self.max_length = torch, name, max_length
38
+ self.device = pick_device(device)
39
+ self.tok = AutoTokenizer.from_pretrained(name)
40
+ self.model = AutoModel.from_pretrained(name).train(False).to(self.device) # inference only: nothing learns
41
+ self.dim = self.model.config.hidden_size
42
+
43
+ def __call__(self, texts, batch=256):
44
+ import numpy as np
45
+ texts = list(texts)
46
+ out = np.zeros((len(texts), self.dim), dtype=np.float32)
47
+ order = sorted(range(len(texts)), key=lambda i: len(texts[i]))
48
+ with self.torch.no_grad():
49
+ for s in range(0, len(order), batch):
50
+ idx = order[s:s + batch]
51
+ b = self.tok([texts[i] for i in idx], padding=True, truncation=True, max_length=self.max_length, return_tensors="pt")
52
+ b = {k: v.to(self.device) for k, v in b.items()}
53
+ v = self.model(**b).last_hidden_state[:, 0]
54
+ v = self.torch.nn.functional.normalize(v.float(), dim=-1).cpu().numpy()
55
+ out[idx] = v
56
+ return out
57
+
58
+
59
+ _SHARED = {}
60
+
61
+
62
+ def shared(name=DEFAULT_MODEL, device=None):
63
+ key = (name, device)
64
+ if key not in _SHARED:
65
+ _SHARED[key] = Embedder(name, device)
66
+ return _SHARED[key]
@@ -0,0 +1,146 @@
1
+ """The learned law: fluidnet's searched integer law over the vector law's top candidates, learned from Claude's teaching.
2
+
3
+ The vector law ranks by closeness alone. The learned law decides which of its top N candidates comes first, from an
4
+ 8-bit word measured per candidate, and it is SEARCHED, not trained: each lane is STRONG, WEAK or OFF (3^8 = 6,561
5
+ laws), the rank is the lexicographic (strong count, weak count, the vector law's order), one strong beats any number
6
+ of weak because the rank says so, and every law is monotone (a lane turning on never lowers a candidate), which
7
+ `monotone` checks on all 256 words as fluidnet checks its laws.
8
+
9
+ b0 CUE1 best of all facts by cue bits b1 FACT1 best by fact-text bits
10
+ b2 CUE~ within 1% of the bits of the best cue b3 FACT~ the same by fact text
11
+ b4 CUE3 top 3 by cue b5 FACT3 top 3 by fact text
12
+ b6 TYPE the question asks for a date / amount / count / person / place / percent and the fact holds one;
13
+ silent when it asks for none (fluidnet's revision 2: unmeasured is silent, not weak)
14
+ b7 BOTH3 top 3 by cue and by fact text
15
+
16
+ The examples are the pairs Claude teaches for a fact the memory already holds: a new wording of a known fact, which
17
+ is what learning from a miss produces. Each is measured before its cue is stored, so it cannot match itself. The
18
+ gate is prequential and honest: every example is first scored by the law in use and by the plain vector order, and
19
+ the law takes over only when it has put the right fact first more often, on examples it had not yet learned from,
20
+ over the last 50 examples (and it is searched on those 50), so a law that stops winning loses its place.
21
+ sandbox/general_check/law_search.py is the same law judged on held-out facts and topics."""
22
+ import itertools
23
+ import re
24
+
25
+ import numpy as np
26
+
27
+ LANES = ("CUE1", "FACT1", "CUE~", "FACT~", "CUE3", "FACT3", "TYPE", "BOTH3")
28
+ NEAR = 0.01
29
+
30
+ ASK = {
31
+ "date": r"\bwhen\b|\bdates?\b|\bdays?\b|\bdeadline\b|\bwhat time\b|\bmoment\b|\bwhich year\b|\bwhat year\b",
32
+ "money": r"\bhow much\b|\bmoney\b|\bprice\b|\bcosts?\b|\bfees?\b|\bcharges?\b|\bspend|\bbudget|\bbill\b|\bpay\b|\bpaid\b|\bowe\b"
33
+ r"|\bsalary\b|\bcompensation\b|\bdollar|\bfunding\b|\ballowance\b|\breimburse|\bbonus\b|\bstipend\b|\bearn\b|\bpayout\b",
34
+ "count": r"\bhow many\b|\bnumber of\b|\bcapacity\b|\bheadcount\b|\bhold\b|\bfit\b|\bcount\b|\bsample size\b|\battendance\b",
35
+ "person": r"\bwho\b|\bwhom\b|\bwhose\b|\bwhich (colleague|individual|person|people|employee|staff|hire|specialist)\b",
36
+ "place": r"\bwhere\b|\baddress\b|\blocation\b|\bvenue\b|\bwhich city\b|\bregion\b|\bzone\b",
37
+ "percent": r"\bpercent|\bproportion\b|\bshare of\b|\bfraction\b|\bportion\b",
38
+ }
39
+ MONTHS = "January|February|March|April|May|June|July|August|September|October|November|December"
40
+ HOLDS = {
41
+ "date": rf"\b({MONTHS}|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\b|\b(19|20)\d\d\b|\bQ[1-4]\b",
42
+ "money": r"\$\s?\d",
43
+ "count": r"(?<![$\d.,])\b\d[\d,]*\b(?!\s*(percent|%))",
44
+ "person": r"\b[A-Z][a-z]+ [A-Z][a-z]+\b",
45
+ "place": r"\b(Street|Avenue|Road|Boulevard|floor|Room|Suite|region)\b",
46
+ "percent": r"\bpercent\b|%",
47
+ }
48
+
49
+
50
+ def asks(q):
51
+ return {t for t, rx in ASK.items() if re.search(rx, q.lower())}
52
+
53
+
54
+ def holds(f):
55
+ return {t for t, rx in HOLDS.items() if re.search(rx, f)}
56
+
57
+
58
+ LAWS = np.array(list(itertools.product((0, 1, 2), repeat=8)), dtype=np.int16) # 0 off, 1 weak, 2 strong
59
+ BASE = int(np.where(np.all(LAWS == 0, axis=1))[0][0]) # every lane off: the vector order
60
+ _BITS = ((np.arange(256)[:, None] >> np.arange(8)[None, :]) & 1).astype(np.int16)
61
+ VALUE = ((_BITS[None] * (LAWS[:, None, :] == 2)).sum(-1) * 16
62
+ + (_BITS[None] * (LAWS[:, None, :] == 1)).sum(-1)).astype(np.int16) # [law, word]
63
+
64
+
65
+ def words(order, strong, weak, bits, qtypes, ftypes, n=8):
66
+ """The 8-bit word of each of the top-n candidates in `order` (the vector law's order)."""
67
+ sr = np.empty(len(strong), dtype=np.int64); sr[np.argsort(-strong, kind="stable")] = np.arange(len(strong))
68
+ wr = np.empty(len(weak), dtype=np.int64); wr[np.argsort(-weak, kind="stable")] = np.arange(len(weak))
69
+ bs, bw = int(strong.max()), int(weak.max())
70
+ out = []
71
+ for j in order[:n]:
72
+ b = (sr[j] == 0, wr[j] == 0, strong[j] >= bs - NEAR * bits, weak[j] >= bw - NEAR * bits,
73
+ sr[j] < 3, wr[j] < 3, bool(qtypes & ftypes[j]), sr[j] < 3 and wr[j] < 3)
74
+ out.append(sum(int(v) << i for i, v in enumerate(b)))
75
+ return out
76
+
77
+
78
+ def rank_of(law, ws, right):
79
+ """Where `law` puts the candidate at position `right` (0 = first); ties keep the vector law's order."""
80
+ if right < 0:
81
+ return 99
82
+ v = VALUE[law][ws].astype(np.int32) * 64 - np.arange(len(ws))
83
+ return int((v > v[right]).sum())
84
+
85
+
86
+ def apply(law, ws):
87
+ """The permutation of the candidates that `law` ranks."""
88
+ v = VALUE[law][ws].astype(np.int32) * 64 - np.arange(len(ws))
89
+ return list(np.argsort(-v, kind="stable"))
90
+
91
+
92
+ def search(examples, n=8):
93
+ """fluidnet: the law that puts the right fact first most often; ties -> better top-3, then fewer lanes on."""
94
+ ex = [(w + [0] * (n - len(w)), r) for w, r in examples if r >= 0]
95
+ if not ex:
96
+ return BASE
97
+ Wd = np.array([w for w, _ in ex]); R = np.array([r for _, r in ex])
98
+ v = VALUE[:, Wd].astype(np.int32) * 64 - np.arange(n, dtype=np.int32)[None, None, :]
99
+ vr = np.take_along_axis(v, np.broadcast_to(R[None, :, None], v.shape[:2] + (1,)), 2)
100
+ got = (v > vr).sum(-1)
101
+ key = (got == 0).mean(1) * 1e6 + (got < 3).mean(1) * 1e3 - (LAWS > 0).sum(1)
102
+ return int(np.argmax(key))
103
+
104
+
105
+ def monotone(law):
106
+ return all(VALUE[law][w | (1 << b)] >= VALUE[law][w] for w in range(256) for b in range(8))
107
+
108
+
109
+ def describe(law):
110
+ s = [x for x, t in zip(LANES, LAWS[law]) if t == 2]; w = [x for x, t in zip(LANES, LAWS[law]) if t == 1]
111
+ return f"strong {{{', '.join(s)}}} weak {{{', '.join(w)}}}" if (s or w) else "the vector order"
112
+
113
+
114
+ class LearnedLaw:
115
+ def __init__(self, n=8, min_examples=12, window=50):
116
+ """window: the law is searched on, and must be winning over, the last `window` examples (None: all of
117
+ them). The memory changes as Claude teaches (cues pile up and the vector order gets better), so a law
118
+ that won early must keep winning to keep its place."""
119
+ self.n, self.min_examples, self.window = n, min_examples, window
120
+ self.examples = [] # (words, position of the right fact among the candidates, or -1)
121
+ self.scored = [] # prequential, per example: (law in use first, vector order first)
122
+ self.law = BASE
123
+
124
+ @property
125
+ def hits(self):
126
+ recent = self.scored[-self.window:] if self.window else self.scored
127
+ return {"law": sum(a for a, _ in recent), "vector": sum(b for _, b in recent)}
128
+
129
+ @property
130
+ def active(self):
131
+ h = self.hits
132
+ return len(self.examples) >= self.min_examples and self.law != BASE and h["law"] > h["vector"]
133
+
134
+ def observe(self, ws, right):
135
+ self.scored.append((int(rank_of(self.law, ws, right) == 0), int(right == 0)))
136
+ self.examples.append((list(ws), int(right)))
137
+ self.law = search(self.examples[-self.window:] if self.window else self.examples, self.n)
138
+ assert monotone(self.law)
139
+
140
+ def order(self, cand, ws):
141
+ """The candidates reordered by the law when it has earned it, else as they came."""
142
+ return [cand[i] for i in apply(self.law, ws)] if self.active else list(cand)
143
+
144
+ def state(self):
145
+ return {"law": describe(self.law), "active": self.active, "examples": len(self.examples),
146
+ "prequential first-right": dict(self.hits)}