minima-cli 0.4.9__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.
Files changed (161) hide show
  1. minima/__init__.py +5 -0
  2. minima/api/__init__.py +1 -0
  3. minima/api/auth.py +39 -0
  4. minima/api/errors.py +40 -0
  5. minima/api/routers/__init__.py +1 -0
  6. minima/api/routers/calibration.py +50 -0
  7. minima/api/routers/feedback.py +279 -0
  8. minima/api/routers/health.py +50 -0
  9. minima/api/routers/models.py +42 -0
  10. minima/api/routers/recommend.py +66 -0
  11. minima/api/routers/savings.py +55 -0
  12. minima/api/routers/strategies.py +33 -0
  13. minima/catalog/__init__.py +1 -0
  14. minima/catalog/data/capability_priors.json +210 -0
  15. minima/catalog/data/model_aliases.json +12 -0
  16. minima/catalog/merge.py +69 -0
  17. minima/catalog/refresh.py +54 -0
  18. minima/catalog/sources/__init__.py +1 -0
  19. minima/catalog/sources/litellm.py +19 -0
  20. minima/catalog/sources/openrouter.py +25 -0
  21. minima/catalog/store.py +86 -0
  22. minima/config.py +288 -0
  23. minima/deps.py +35 -0
  24. minima/llm/__init__.py +1 -0
  25. minima/llm/anthropic.py +106 -0
  26. minima/llm/base.py +196 -0
  27. minima/llm/gemini.py +124 -0
  28. minima/llm/registry.py +54 -0
  29. minima/logging.py +28 -0
  30. minima/main.py +109 -0
  31. minima/memory/__init__.py +1 -0
  32. minima/memory/adapter.py +572 -0
  33. minima/memory/keys.py +83 -0
  34. minima/memory/records.py +190 -0
  35. minima/memory/threadpool.py +41 -0
  36. minima/metrics/__init__.py +1 -0
  37. minima/metrics/calibration.py +415 -0
  38. minima/metrics/report.py +116 -0
  39. minima/metrics/savings.py +98 -0
  40. minima/recommender/__init__.py +1 -0
  41. minima/recommender/_pg_pool.py +38 -0
  42. minima/recommender/_redis_client.py +32 -0
  43. minima/recommender/aggregate.py +157 -0
  44. minima/recommender/classify.py +165 -0
  45. minima/recommender/decisionlog.py +505 -0
  46. minima/recommender/durablerefs.py +312 -0
  47. minima/recommender/engine.py +997 -0
  48. minima/recommender/escalation.py +83 -0
  49. minima/recommender/propensity.py +189 -0
  50. minima/recommender/recstore.py +368 -0
  51. minima/recommender/score.py +318 -0
  52. minima/recommender/types.py +166 -0
  53. minima/schemas/__init__.py +1 -0
  54. minima/schemas/common.py +73 -0
  55. minima/schemas/feedback.py +34 -0
  56. minima/schemas/models_catalog.py +36 -0
  57. minima/schemas/recommend.py +104 -0
  58. minima/schemas/savings.py +39 -0
  59. minima/schemas/strategies.py +57 -0
  60. minima/schemas/workflow.py +43 -0
  61. minima/seeding/__init__.py +1 -0
  62. minima/seeding/items.py +42 -0
  63. minima/seeding/llmrouterbench.py +232 -0
  64. minima/seeding/routerbench.py +141 -0
  65. minima/seeding/run_seed.py +56 -0
  66. minima/seeding/synthetic.py +70 -0
  67. minima/tenancy/__init__.py +8 -0
  68. minima/tenancy/context.py +37 -0
  69. minima/tenancy/passthrough.py +110 -0
  70. minima/version.py +3 -0
  71. minima_cli-0.4.9.dist-info/METADATA +275 -0
  72. minima_cli-0.4.9.dist-info/RECORD +161 -0
  73. minima_cli-0.4.9.dist-info/WHEEL +4 -0
  74. minima_cli-0.4.9.dist-info/entry_points.txt +5 -0
  75. minima_cli-0.4.9.dist-info/licenses/LICENSE +295 -0
  76. minima_client/__init__.py +19 -0
  77. minima_client/autocapture.py +101 -0
  78. minima_client/client.py +301 -0
  79. minima_client/errors.py +23 -0
  80. minima_harness/LICENSE_PI +32 -0
  81. minima_harness/__init__.py +16 -0
  82. minima_harness/agent/__init__.py +72 -0
  83. minima_harness/agent/agent.py +276 -0
  84. minima_harness/agent/events.py +124 -0
  85. minima_harness/agent/loop.py +311 -0
  86. minima_harness/agent/state.py +79 -0
  87. minima_harness/agent/tools.py +97 -0
  88. minima_harness/ai/__init__.py +66 -0
  89. minima_harness/ai/compat.py +71 -0
  90. minima_harness/ai/errors.py +96 -0
  91. minima_harness/ai/events.py +117 -0
  92. minima_harness/ai/openrouter_catalog.py +153 -0
  93. minima_harness/ai/provider_catalog.py +299 -0
  94. minima_harness/ai/provider_quirks.py +37 -0
  95. minima_harness/ai/providers/__init__.py +75 -0
  96. minima_harness/ai/providers/_common.py +48 -0
  97. minima_harness/ai/providers/anthropic.py +290 -0
  98. minima_harness/ai/providers/base.py +65 -0
  99. minima_harness/ai/providers/faux.py +173 -0
  100. minima_harness/ai/providers/google.py +221 -0
  101. minima_harness/ai/providers/openai_compat.py +278 -0
  102. minima_harness/ai/registry.py +184 -0
  103. minima_harness/ai/stream.py +82 -0
  104. minima_harness/ai/tools.py +51 -0
  105. minima_harness/ai/types.py +204 -0
  106. minima_harness/ai/usage.py +41 -0
  107. minima_harness/minima/__init__.py +40 -0
  108. minima_harness/minima/cache.py +102 -0
  109. minima_harness/minima/config.py +85 -0
  110. minima_harness/minima/goals.py +226 -0
  111. minima_harness/minima/judge.py +144 -0
  112. minima_harness/minima/mapping.py +147 -0
  113. minima_harness/minima/meter.py +143 -0
  114. minima_harness/minima/router.py +220 -0
  115. minima_harness/minima/runtime.py +544 -0
  116. minima_harness/minima/signals.py +195 -0
  117. minima_harness/session/__init__.py +14 -0
  118. minima_harness/session/format.py +35 -0
  119. minima_harness/session/store.py +236 -0
  120. minima_harness/tasks/__init__.py +17 -0
  121. minima_harness/tasks/task_set.py +78 -0
  122. minima_harness/tools/__init__.py +7 -0
  123. minima_harness/tools/_io.py +34 -0
  124. minima_harness/tools/bash.py +70 -0
  125. minima_harness/tools/builtin.py +23 -0
  126. minima_harness/tools/edit.py +50 -0
  127. minima_harness/tools/find.py +38 -0
  128. minima_harness/tools/grep.py +73 -0
  129. minima_harness/tools/ls.py +35 -0
  130. minima_harness/tools/read.py +38 -0
  131. minima_harness/tools/tasks.py +75 -0
  132. minima_harness/tools/write.py +36 -0
  133. minima_harness/tui/__init__.py +3 -0
  134. minima_harness/tui/analytics.py +111 -0
  135. minima_harness/tui/app.py +1927 -0
  136. minima_harness/tui/bridge.py +103 -0
  137. minima_harness/tui/cli.py +227 -0
  138. minima_harness/tui/clipboard.py +60 -0
  139. minima_harness/tui/commands.py +49 -0
  140. minima_harness/tui/compaction.py +17 -0
  141. minima_harness/tui/config_cli.py +141 -0
  142. minima_harness/tui/config_store.py +237 -0
  143. minima_harness/tui/context.py +93 -0
  144. minima_harness/tui/customize.py +95 -0
  145. minima_harness/tui/diff.py +53 -0
  146. minima_harness/tui/editor.py +43 -0
  147. minima_harness/tui/extensions.py +84 -0
  148. minima_harness/tui/extra_models.py +52 -0
  149. minima_harness/tui/history.py +71 -0
  150. minima_harness/tui/mubit.py +295 -0
  151. minima_harness/tui/overlays.py +593 -0
  152. minima_harness/tui/packages.py +59 -0
  153. minima_harness/tui/run_modes.py +66 -0
  154. minima_harness/tui/theme.py +77 -0
  155. minima_harness/tui/welcome.py +83 -0
  156. minima_harness/tui/widgets/__init__.py +3 -0
  157. minima_harness/tui/widgets/banner.py +38 -0
  158. minima_harness/tui/widgets/editor.py +83 -0
  159. minima_harness/tui/widgets/footer.py +73 -0
  160. minima_harness/tui/widgets/messages.py +151 -0
  161. minima_harness/tui/widgets/status.py +57 -0
@@ -0,0 +1,312 @@
1
+ """Bookkeeping for the durable (cluster, model) outcome records' Dereference ids.
2
+
3
+ Minima upserts exactly one durable outcome record per (lane, cluster, model) in Mubit
4
+ (``minima:om:{cluster}:{model_id}``). ANN recall usually surfaces it — but embedding
5
+ noise can push the single most-informative record out of the top-k. This store remembers
6
+ each durable record's stable entry id so the engine can Dereference it directly (exact
7
+ re-read) alongside recall, guaranteeing the highest-signal evidence is present.
8
+
9
+ Rows are written from two sources: feedback (the remember() record_id — upserts keep it
10
+ stable) and recall hits whose record matches the current cluster. Seeds are excluded
11
+ (they are per-row batch inserts, not the durable upsert).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sqlite3
17
+ import time
18
+ from dataclasses import dataclass
19
+ from threading import Lock
20
+ from typing import Protocol, runtime_checkable
21
+
22
+ from minima.config import Settings
23
+
24
+
25
+ @dataclass(slots=True)
26
+ class DurableRef:
27
+ model_id: str
28
+ entry_id: str
29
+ reference_id: str
30
+
31
+
32
+ @runtime_checkable
33
+ class DurableRefs(Protocol):
34
+ def upsert(
35
+ self, lane: str, cluster: str, model_id: str, entry_id: str, reference_id: str
36
+ ) -> None: ...
37
+
38
+ def refs(self, lane: str, cluster: str, limit: int = 8) -> list[DurableRef]: ...
39
+
40
+
41
+ class MemoryDurableRefs:
42
+ """In-process store (lost on restart — recall hits repopulate it organically)."""
43
+
44
+ def __init__(self) -> None:
45
+ self._data: dict[tuple[str, str, str], dict[str, DurableRef]] = {}
46
+ self._lock = Lock()
47
+
48
+ def upsert(
49
+ self,
50
+ lane: str,
51
+ cluster: str,
52
+ model_id: str,
53
+ entry_id: str,
54
+ reference_id: str,
55
+ org_id: str = "default",
56
+ ) -> None:
57
+ if not entry_id and not reference_id:
58
+ return
59
+ with self._lock:
60
+ bucket = self._data.setdefault((org_id, lane, cluster), {})
61
+ bucket[model_id] = DurableRef(
62
+ model_id=model_id, entry_id=entry_id, reference_id=reference_id or entry_id
63
+ )
64
+
65
+ def refs(
66
+ self, lane: str, cluster: str, limit: int = 8, org_id: str = "default"
67
+ ) -> list[DurableRef]:
68
+ with self._lock:
69
+ bucket = self._data.get((org_id, lane, cluster), {})
70
+ return list(bucket.values())[: max(0, limit)]
71
+
72
+
73
+ class SqliteDurableRefs:
74
+ """Durable store backed by SQLite (stdlib; shares the state DB file)."""
75
+
76
+ def __init__(self, path: str):
77
+ self._conn = sqlite3.connect(path, check_same_thread=False)
78
+ self._lock = Lock()
79
+ with self._conn:
80
+ self._conn.execute(
81
+ """
82
+ CREATE TABLE IF NOT EXISTS durable_refs (
83
+ org_id TEXT NOT NULL DEFAULT 'default',
84
+ lane TEXT NOT NULL,
85
+ cluster TEXT NOT NULL,
86
+ model_id TEXT NOT NULL,
87
+ entry_id TEXT NOT NULL,
88
+ reference_id TEXT NOT NULL,
89
+ updated_at REAL NOT NULL
90
+ )
91
+ """
92
+ )
93
+ self._conn.execute(
94
+ "CREATE UNIQUE INDEX IF NOT EXISTS ux_durable_refs "
95
+ "ON durable_refs(org_id, lane, cluster, model_id)"
96
+ )
97
+
98
+ def upsert(
99
+ self,
100
+ lane: str,
101
+ cluster: str,
102
+ model_id: str,
103
+ entry_id: str,
104
+ reference_id: str,
105
+ org_id: str = "default",
106
+ ) -> None:
107
+ if not entry_id and not reference_id:
108
+ return
109
+ with self._lock, self._conn:
110
+ self._conn.execute(
111
+ """
112
+ INSERT INTO durable_refs
113
+ (org_id, lane, cluster, model_id, entry_id, reference_id, updated_at)
114
+ VALUES (?, ?, ?, ?, ?, ?, ?)
115
+ ON CONFLICT(org_id, lane, cluster, model_id) DO UPDATE SET
116
+ entry_id = excluded.entry_id,
117
+ reference_id = excluded.reference_id,
118
+ updated_at = excluded.updated_at
119
+ """,
120
+ (
121
+ org_id,
122
+ lane,
123
+ cluster,
124
+ model_id,
125
+ entry_id,
126
+ reference_id or entry_id,
127
+ time.time(),
128
+ ),
129
+ )
130
+
131
+ def refs(
132
+ self, lane: str, cluster: str, limit: int = 8, org_id: str = "default"
133
+ ) -> list[DurableRef]:
134
+ with self._lock:
135
+ rows = self._conn.execute(
136
+ "SELECT model_id, entry_id, reference_id FROM durable_refs "
137
+ "WHERE org_id = ? AND lane = ? AND cluster = ? "
138
+ "ORDER BY updated_at DESC LIMIT ?",
139
+ (org_id, lane, cluster, max(0, limit)),
140
+ ).fetchall()
141
+ return [
142
+ DurableRef(model_id=str(m), entry_id=str(e), reference_id=str(r)) for m, e, r in rows
143
+ ]
144
+
145
+ def close(self) -> None:
146
+ with self._lock:
147
+ self._conn.close()
148
+
149
+
150
+ class OrgScopedDurableRefs:
151
+ """Binds a shared backend to one org, presenting the ``DurableRefs`` Protocol."""
152
+
153
+ def __init__(self, backend: DurableRefs, org_id: str):
154
+ self._backend = backend
155
+ self._org_id = org_id
156
+
157
+ def upsert(
158
+ self, lane: str, cluster: str, model_id: str, entry_id: str, reference_id: str
159
+ ) -> None:
160
+ self._backend.upsert(lane, cluster, model_id, entry_id, reference_id, self._org_id) # type: ignore[call-arg]
161
+
162
+ def refs(self, lane: str, cluster: str, limit: int = 8) -> list[DurableRef]:
163
+ return self._backend.refs(lane, cluster, limit, self._org_id) # type: ignore[call-arg]
164
+
165
+
166
+ class PostgresDurableRefs:
167
+ """Durable refs store backed by PostgreSQL."""
168
+
169
+ def __init__(self, database_url: str):
170
+ from minima.recommender._pg_pool import cursor as _cursor
171
+
172
+ self._url = database_url
173
+ self._cursor = _cursor
174
+ with self._cursor(self._url) as cur:
175
+ cur.execute(
176
+ """
177
+ CREATE TABLE IF NOT EXISTS durable_refs (
178
+ org_id TEXT NOT NULL DEFAULT 'default',
179
+ lane TEXT NOT NULL,
180
+ cluster TEXT NOT NULL,
181
+ model_id TEXT NOT NULL,
182
+ entry_id TEXT NOT NULL,
183
+ reference_id TEXT NOT NULL,
184
+ updated_at DOUBLE PRECISION NOT NULL,
185
+ UNIQUE(org_id, lane, cluster, model_id)
186
+ )
187
+ """
188
+ )
189
+
190
+ def upsert(
191
+ self,
192
+ lane: str,
193
+ cluster: str,
194
+ model_id: str,
195
+ entry_id: str,
196
+ reference_id: str,
197
+ org_id: str = "default",
198
+ ) -> None:
199
+ if not entry_id and not reference_id:
200
+ return
201
+ with self._cursor(self._url) as cur:
202
+ cur.execute(
203
+ """
204
+ INSERT INTO durable_refs
205
+ (org_id, lane, cluster, model_id, entry_id, reference_id, updated_at)
206
+ VALUES (%s, %s, %s, %s, %s, %s, %s)
207
+ ON CONFLICT (org_id, lane, cluster, model_id) DO UPDATE SET
208
+ entry_id = EXCLUDED.entry_id,
209
+ reference_id = EXCLUDED.reference_id,
210
+ updated_at = EXCLUDED.updated_at
211
+ """,
212
+ (
213
+ org_id,
214
+ lane,
215
+ cluster,
216
+ model_id,
217
+ entry_id,
218
+ reference_id or entry_id,
219
+ time.time(),
220
+ ),
221
+ )
222
+
223
+ def refs(
224
+ self, lane: str, cluster: str, limit: int = 8, org_id: str = "default"
225
+ ) -> list[DurableRef]:
226
+ with self._cursor(self._url) as cur:
227
+ cur.execute(
228
+ "SELECT model_id, entry_id, reference_id FROM durable_refs"
229
+ " WHERE org_id = %s AND lane = %s AND cluster = %s"
230
+ " ORDER BY updated_at DESC LIMIT %s",
231
+ (org_id, lane, cluster, max(0, limit)),
232
+ )
233
+ rows = cur.fetchall()
234
+ return [
235
+ DurableRef(model_id=str(m), entry_id=str(e), reference_id=str(r)) for m, e, r in rows
236
+ ]
237
+
238
+
239
+ class RedisDurableRefs:
240
+ """Durable refs store backed by Redis (Cloud Memorystore).
241
+
242
+ Each (org_id, lane, cluster) bucket is a Redis hash keyed by model_id, with
243
+ the value being a JSON-encoded {entry_id, reference_id}. No TTL — refs are
244
+ long-lived and updated in-place as new outcomes arrive.
245
+ """
246
+
247
+ def __init__(self, redis_url: str):
248
+ from minima.recommender._redis_client import get_client
249
+
250
+ self._r = get_client(redis_url)
251
+
252
+ def _key(self, org_id: str, lane: str, cluster: str) -> str:
253
+ return f"drefs:{org_id}:{lane}:{cluster}"
254
+
255
+ def upsert(
256
+ self,
257
+ lane: str,
258
+ cluster: str,
259
+ model_id: str,
260
+ entry_id: str,
261
+ reference_id: str,
262
+ org_id: str = "default",
263
+ ) -> None:
264
+ import json
265
+
266
+ if not entry_id and not reference_id:
267
+ return
268
+ self._r.hset(
269
+ self._key(org_id, lane, cluster),
270
+ model_id,
271
+ json.dumps({"entry_id": entry_id, "reference_id": reference_id or entry_id}),
272
+ )
273
+
274
+ def refs(
275
+ self, lane: str, cluster: str, limit: int = 8, org_id: str = "default"
276
+ ) -> list[DurableRef]:
277
+ import json
278
+
279
+ raw = self._r.hgetall(self._key(org_id, lane, cluster))
280
+ from minima.recommender._redis_client import decode
281
+
282
+ out: list[DurableRef] = []
283
+ for model_id, value in raw.items():
284
+ d = json.loads(value)
285
+ out.append(
286
+ DurableRef(
287
+ model_id=decode(model_id),
288
+ entry_id=d["entry_id"],
289
+ reference_id=d["reference_id"],
290
+ )
291
+ )
292
+ return out[: max(0, limit)]
293
+
294
+
295
+ def build_durable_refs(settings: Settings) -> DurableRefs:
296
+ backend = (
297
+ settings.minima_recstore_backend.strip().lower()
298
+ or settings.minima_recommendation_store.strip().lower()
299
+ )
300
+ if backend == "redis":
301
+ if not settings.minima_redis_url:
302
+ raise RuntimeError("MINIMA_REDIS_URL is required when MINIMA_RECSTORE_BACKEND=redis")
303
+ return RedisDurableRefs(settings.minima_redis_url)
304
+ if backend in ("cloudsql", "postgres", "postgresql"):
305
+ if not settings.minima_database_url:
306
+ raise RuntimeError(
307
+ "MINIMA_DATABASE_URL is required when MINIMA_RECOMMENDATION_STORE=cloudsql"
308
+ )
309
+ return PostgresDurableRefs(settings.minima_database_url)
310
+ if backend == "sqlite":
311
+ return SqliteDurableRefs(settings.minima_sqlite_path)
312
+ return MemoryDurableRefs()