dsh-biocore 0.5.1 → 0.5.4

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 (69) hide show
  1. package/lib/index.js +10 -0
  2. package/lib/py/bridge.py +98 -0
  3. package/package.json +1 -1
  4. package/python/biocore_memory/adapters/dsh_cli.py +56 -2
  5. package/python/biocore_memory/core/config.py +274 -142
  6. package/python/biocore_memory/engine.py +3346 -2738
  7. package/python/biocore_memory/engine_helpers.py +15 -1
  8. package/python/biocore_memory/event/activation.py +118 -0
  9. package/python/biocore_memory/event/chains.py +112 -0
  10. package/python/biocore_memory/event/claims.py +486 -0
  11. package/python/biocore_memory/event/compaction.py +240 -0
  12. package/python/biocore_memory/event/consistency.py +10 -99
  13. package/python/biocore_memory/event/event_graph.py +100 -0
  14. package/python/biocore_memory/event/events.py +926 -0
  15. package/python/biocore_memory/event/facts.py +150 -0
  16. package/python/biocore_memory/event/json_tolerant.py +73 -0
  17. package/python/biocore_memory/event/keynorm.py +380 -0
  18. package/python/biocore_memory/event/rolling.py +211 -0
  19. package/python/biocore_memory/event/segment.py +164 -0
  20. package/python/biocore_memory/event/segment_adaptive.py +114 -0
  21. package/python/biocore_memory/event/timerender.py +68 -0
  22. package/python/biocore_memory/evict/consequence.py +10 -14
  23. package/python/biocore_memory/index/entities.py +11 -51
  24. package/python/biocore_memory/recall/activation.py +144 -0
  25. package/python/biocore_memory/recall/api.py +5 -2
  26. package/python/biocore_memory/recall/cover.py +134 -0
  27. package/python/biocore_memory/recall/embedding.py +555 -50
  28. package/python/biocore_memory/recall/point_expand.py +169 -0
  29. package/python/biocore_memory/recall/providers.py +442 -0
  30. package/python/biocore_memory/recall/segments.py +111 -0
  31. package/python/biocore_memory/space.py +967 -24
  32. package/python/biocore_memory/space_learning.py +347 -0
  33. package/python/biocore_memory/store/dedupe.py +5 -42
  34. package/python/biocore_memory/store/json_store.py +81 -2
  35. package/python/biocore_memory/store/near_dup.py +250 -0
  36. package/python/biocore_memory/store/sql_store.py +665 -0
  37. package/python/biocore_memory/store/versioning.py +291 -0
  38. package/python/biocore_model/entity_graph.py +13 -7
  39. package/python/biocore_model/tests/conftest.py +58 -0
  40. package/python/biocore_model/tests/test_appraisal.py +23 -9
  41. package/python/biocore_model/tests/test_appraisal_v2.py +140 -0
  42. package/python/biocore_model/tests/test_batch_b.py +310 -0
  43. package/python/biocore_model/tests/test_batch_d.py +244 -0
  44. package/python/biocore_model/tests/test_batch_e.py +210 -0
  45. package/python/biocore_model/tests/test_batch_registry.py +272 -0
  46. package/python/biocore_model/tests/test_bio_tick.py +50 -5
  47. package/python/biocore_model/tests/test_damping_needs_split.py +144 -0
  48. package/python/biocore_model/tests/test_dimensions.py +249 -0
  49. package/python/biocore_model/tests/test_dimensions_conflict.py +192 -0
  50. package/python/biocore_model/tests/test_expression.py +254 -0
  51. package/python/biocore_model/tests/test_governance.py +180 -181
  52. package/python/biocore_model/tests/test_learning_eval.py +142 -0
  53. package/python/biocore_model/tests/test_module_monitor.py +172 -0
  54. package/python/biocore_model/tests/test_paper_alignment.py +138 -0
  55. package/python/biocore_model/tests/test_perception_batch.py +371 -0
  56. package/python/biocore_model/tests/test_perception_batch_a2.py +304 -0
  57. package/python/biocore_model/tests/test_proposal_split.py +214 -0
  58. package/python/biocore_model/tests/test_runtime_pipeline.py +3 -6
  59. package/python/biocore_model/tests/test_source_split.py +170 -0
  60. package/python/biocore_model/tests/test_space.py +240 -0
  61. package/python/biocore_model/tests/test_spatial_recall.py +288 -0
  62. package/python/biocore_model/tests/test_surprise_priority.py +143 -0
  63. package/python/biocore_model/tests/test_timescales.py +181 -0
  64. package/python/biocore_runtime/extensions.py +2 -2
  65. package/python/biocore_runtime/pipeline.py +3996 -183
  66. package/python/biocore_runtime/rerank.py +51 -84
  67. package/python/biocore_model/tests/test_autonomy_self.py +0 -153
  68. package/python/biocore_model/tests/test_memory_inject.py +0 -211
  69. package/python/data/signals.json +0 -15307
package/lib/index.js CHANGED
@@ -385,7 +385,17 @@ function coreConfigPayload(dataDir) {
385
385
  try { switches = readSwitches(dataDir).switches } catch { switches = {} }
386
386
  let persona = ''
387
387
  try { persona = readPersonaText(dataDir) } catch { persona = '' }
388
+ // Keys this host does not own (summarizer, recall, anything the core or a
389
+ // human added) must survive a re-configure. Dropping them made capture raise
390
+ // "no summarizer block" and memory vanished silently behind an adapter
391
+ // fallback, so carry the previous file forward and overwrite only our keys.
392
+ let carried = {}
393
+ try {
394
+ const previous = JSON.parse(readFileSync(join(dataDir, 'core_config.json'), 'utf8'))
395
+ if (previous && typeof previous === 'object' && !Array.isArray(previous)) carried = previous
396
+ } catch { carried = {} }
388
397
  return {
398
+ ...carried,
389
399
  data_dir: dataDir,
390
400
  persona_text: persona,
391
401
  embedding: config.embedding,
package/lib/py/bridge.py CHANGED
@@ -256,12 +256,90 @@ def _json_arg(value):
256
256
  return {}
257
257
 
258
258
 
259
+ _SUMMARIZER_STORE = [""]
260
+
261
+
262
+ def _deepseek_key():
263
+ """API key for the summariser: env first, then the DSH credential store."""
264
+ key = str(os.environ.get("DEEPSEEK_API_KEY") or "").strip()
265
+ if key:
266
+ return key
267
+ try:
268
+ import re
269
+ text = open(str(_DSH_HOME / ".credentials.yaml"), encoding="utf-8").read()
270
+ hit = re.search(r"DEEPSEEK_API_KEY:\s*(\S+)", text)
271
+ if hit:
272
+ return hit.group(1).strip().strip('"').strip("'")
273
+ except Exception:
274
+ pass
275
+ return ""
276
+
277
+
278
+ def _summarizer_config(store):
279
+ """The summariser block of the store's core_config.json (no silent default)."""
280
+ for name in ("core_config.json", "config.json"):
281
+ path = os.path.join(str(store or ""), name)
282
+ if not os.path.isfile(path):
283
+ continue
284
+ try:
285
+ data = json.loads(open(path, encoding="utf-8").read())
286
+ except Exception:
287
+ continue
288
+ block = data.get("summarizer") if isinstance(data, dict) else None
289
+ if isinstance(block, dict) and str(block.get("model") or "").strip():
290
+ return {"base_url": str(block.get("base_url") or block.get("baseUrl")
291
+ or "https://api.deepseek.com/v1"),
292
+ "model": str(block["model"]).strip(),
293
+ "key": str(block.get("key") or block.get("api_key") or "").strip()}
294
+ raise RuntimeError("summarizer config missing: core_config.json has no summarizer block "
295
+ "(the event layer cannot close events without it)")
296
+
297
+
298
+ def _llm_summarizer(prompt):
299
+ """Event-close summariser for the real plugin path.
300
+
301
+ capture() only closes events when it is handed a summariser; without one the
302
+ event layer buffers forever and no event ever exists (the production path had
303
+ exactly that hole). This uses the host's own model over HTTPS.
304
+ """
305
+ cfg = _summarizer_config(_SUMMARIZER_STORE[0])
306
+ key = cfg["key"] or _deepseek_key()
307
+ if not key:
308
+ raise RuntimeError("summarizer config has no key and DEEPSEEK_API_KEY is unset")
309
+ import urllib.request
310
+ model = cfg["model"]
311
+ base = cfg["base_url"]
312
+ body = json.dumps({
313
+ "model": model,
314
+ "temperature": 0,
315
+ "messages": [{"role": "system", "content": "只输出 JSON 对象,不要解释。"},
316
+ {"role": "user", "content": str(prompt)}],
317
+ }).encode("utf-8")
318
+ req = urllib.request.Request(base.rstrip("/") + "/chat/completions", data=body,
319
+ headers={"Content-Type": "application/json",
320
+ "Authorization": "Bearer " + key})
321
+ try:
322
+ with urllib.request.urlopen(req, timeout=120) as resp:
323
+ data = json.loads(resp.read().decode("utf-8", "replace"))
324
+ return str((data.get("choices") or [{}])[0].get("message", {}).get("content") or "")
325
+ except Exception as exc:
326
+ sys.stderr.write("__bioSummarizer failed: %s\n" % str(exc)[:160])
327
+ return None
328
+
329
+
259
330
  def _memory_capture(argv):
260
331
  store = argv[1]
261
332
  session_id = argv[2]
262
333
  role = argv[3] if len(argv) > 3 else "user"
263
334
  text = argv[4] if len(argv) > 4 else ""
264
335
  config = _json_arg(argv[5] if len(argv) > 5 else "")
336
+ config = dict(config) if isinstance(config, dict) else {}
337
+ _SUMMARIZER_STORE[0] = str(store or "")
338
+ if not config.get("event_summarizer"):
339
+ # the host never supplied one; without it no event is ever generated.
340
+ # _llm_summarizer raises when core_config.json has no summarizer block.
341
+ _summarizer_config(store)
342
+ config["event_summarizer"] = _llm_summarizer
265
343
  result = _pipeline().capture(session_id, role, text, store, config)
266
344
  decision = result.get("decision") if isinstance(result, dict) else {}
267
345
  decision = decision if isinstance(decision, dict) else {}
@@ -276,6 +354,10 @@ def _memory_capture(argv):
276
354
 
277
355
 
278
356
  def _memory_inject(argv):
357
+ return _memory_inject_impl(argv)
358
+
359
+
360
+ def _memory_inject_impl(argv):
279
361
  store = argv[1]
280
362
  session_id = argv[2]
281
363
  kwargs = {}
@@ -313,9 +395,25 @@ def _runtime_gate(argv):
313
395
  return _emit(_pipeline().gate_reply(reply, payload))
314
396
 
315
397
 
398
+ _EVENT_ACTIONS = ("memory_capture", "memory_inject")
399
+
400
+
316
401
  def _runtime_action(argv):
317
402
  action = argv[1] if len(argv) > 1 else ""
318
403
  payload = _json_arg(argv[2] if len(argv) > 2 else "")
404
+ if action in _EVENT_ACTIONS and isinstance(payload, dict):
405
+ # the real node path calls dispatch(); it must carry the summariser too,
406
+ # or the event layer stays silently off (capture) / raises (render)
407
+ payload = dict(payload)
408
+ store = str(payload.get("store") or payload.get("root") or "")
409
+ _SUMMARIZER_STORE[0] = store
410
+ if str(os.environ.get("BIOCORE_STRICT_TS") or "") == "1" \
411
+ and action == "memory_capture" and not payload.get("now"):
412
+ raise RuntimeError("capture payload has no logical timestamp (now); "
413
+ "BIOCORE_STRICT_TS forbids falling back to the wall clock")
414
+ if not payload.get("event_summarizer"):
415
+ _summarizer_config(store)
416
+ payload["event_summarizer"] = _llm_summarizer
319
417
  return _emit(_pipeline().dispatch(action, payload))
320
418
 
321
419
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-biocore",
3
- "version": "0.5.1",
3
+ "version": "0.5.4",
4
4
  "type": "module",
5
5
  "description": "BioCore DSH UI: per-session state tab, standalone instrument panel, /api/biocore/state, session/event one_turn updates, /bio sync.",
6
6
  "main": "lib/index.js",
@@ -50,7 +50,15 @@ def dispatch(engine, action, **kwargs):
50
50
  if action == "recall":
51
51
  return engine.recall(kwargs.get("query", ""),
52
52
  budget=kwargs.get("budget", 3),
53
- vector=kwargs.get("vector"))
53
+ vector=kwargs.get("vector"),
54
+ mode=kwargs.get("mode", "now"))
55
+ if action in ("spatial_rank", "rank"):
56
+ from ..core.config import SPATIAL_SELECT_DEPTH
57
+ return engine.spatial_rank(
58
+ kwargs.get("query", ""),
59
+ query_ts=kwargs.get("query_ts"),
60
+ limit=kwargs.get("limit", SPATIAL_SELECT_DEPTH),
61
+ scope=kwargs.get("scope", "default"))
54
62
  if action == "forget":
55
63
  return engine.forget(kwargs.get("entry_id", ""))
56
64
  if action == "pin":
@@ -130,10 +138,27 @@ def cmd_recall(args):
130
138
  "tier": "expand" if score >= 0.55 else "hint",
131
139
  "role": entry.get("role"), "ts": entry.get("ts")})
132
140
  return _emit({"ok": True, "command": "recall", "query": args.query,
141
+ "mode": getattr(args, "mode", "now") or "now",
142
+ "spatial_select": getattr(engine, "_last_spatial_select", None),
133
143
  "count": len(out), "results": out,
134
144
  "hint": "no_cue" if not out else ""})
135
145
 
136
146
 
147
+ def cmd_rank(args):
148
+ """Whole-store spatial selection (engine.spatial_rank), JSON out."""
149
+ engine = _engine_class()(data_dir=args.store)
150
+ try:
151
+ limit = int(args.limit) if args.limit is not None else None
152
+ except Exception:
153
+ limit = None
154
+ scope = str(getattr(args, "scope", "") or "").strip() or None
155
+ payload = engine.spatial_rank(args.query,
156
+ query_ts=getattr(args, "ts", None),
157
+ limit=limit, scope=scope)
158
+ return _emit({"ok": bool(payload.get("ok")), "command": "rank",
159
+ "result": payload})
160
+
161
+
137
162
  def cmd_forget(args):
138
163
  engine = _engine_class()(data_dir=args.store)
139
164
  result = engine.forget(args.entry_id)
@@ -233,6 +258,20 @@ def cmd_export(args):
233
258
  return _emit({"ok": True, "command": "export", "payload": payload})
234
259
 
235
260
 
261
+ def cmd_reembed(args):
262
+ """Rebuild every vector in the store with the configured provider.
263
+
264
+ The supported way out of an embedding-space mismatch: the store is read
265
+ through the runtime pipeline (sqlite primary), every entry is re-embedded,
266
+ event centroids are re-derived and the meta is re-stamped.
267
+ """
268
+ from biocore_runtime import pipeline as P
269
+ session = str(getattr(args, "session", "") or "default")
270
+ result = P.reembed_store(args.store, session,
271
+ batch_size=int(getattr(args, "batch_size", 0) or 32))
272
+ return _emit(dict(result, command="reembed"))
273
+
274
+
236
275
  def cmd_stats(args):
237
276
  engine = _engine_class()(data_dir=args.store)
238
277
  return _emit({"ok": True, "command": "stats", "monitor": engine.monitor()})
@@ -285,8 +324,17 @@ def build_parser():
285
324
  r = sp.add_parser("recall", help="query memories")
286
325
  r.add_argument("query")
287
326
  r.add_argument("--limit", type=int, default=3)
288
- r.add_argument("--mode", default="now", choices=("now", "then"))
327
+ r.add_argument("--mode", default="now", choices=("now", "then", "spatial"))
289
328
  r.set_defaults(func=cmd_recall)
329
+ r = sp.add_parser("rank",
330
+ help="top-N over the whole store by five-axis distance")
331
+ r.add_argument("query")
332
+ r.add_argument("--limit", type=int, default=None)
333
+ r.add_argument("--ts", type=float, default=None,
334
+ help="anchor the time axis at this epoch (default now)")
335
+ r.add_argument("--scope", default="",
336
+ help="scope filter (default: every scope in the store)")
337
+ r.set_defaults(func=cmd_rank)
290
338
  r = sp.add_parser("forget", help="delete by id")
291
339
  r.add_argument("entry_id")
292
340
  r.set_defaults(func=cmd_forget)
@@ -319,6 +367,12 @@ def build_parser():
319
367
  r.set_defaults(func=cmd_snapshot)
320
368
  r = sp.add_parser("consolidate-run", help="run gated consolidation")
321
369
  r.set_defaults(func=cmd_consolidate_run)
370
+ r = sp.add_parser("reembed",
371
+ help="rebuild vectors after changing the embedding model/dim")
372
+ r.add_argument("--store", required=True)
373
+ r.add_argument("--session", default="default")
374
+ r.add_argument("--batch-size", dest="batch_size", type=int, default=32)
375
+ r.set_defaults(func=cmd_reembed)
322
376
  return p
323
377
 
324
378