dentate 0.1.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.
Files changed (267) hide show
  1. dentate/__init__.py +10 -0
  2. dentate/_deps.py +38 -0
  3. dentate/adapters/__init__.py +7 -0
  4. dentate/adapters/oracle/__init__.py +5 -0
  5. dentate/adapters/oracle/fake.py +62 -0
  6. dentate/adapters/teacher/__init__.py +5 -0
  7. dentate/adapters/teacher/fake.py +27 -0
  8. dentate/adapters/tracking/__init__.py +6 -0
  9. dentate/adapters/tracking/null.py +19 -0
  10. dentate/adapters/tracking/wandb.py +53 -0
  11. dentate/agent.py +76 -0
  12. dentate/analysis.py +421 -0
  13. dentate/analysis_llm.py +247 -0
  14. dentate/api.py +1255 -0
  15. dentate/arch/__init__.py +115 -0
  16. dentate/arch/_baseline_torch.py +110 -0
  17. dentate/arch/_xlstm_torch.py +122 -0
  18. dentate/arch/baseline.py +84 -0
  19. dentate/arch/knobs.py +159 -0
  20. dentate/arch/registry.py +72 -0
  21. dentate/arch/xlstm.py +86 -0
  22. dentate/backends/__init__.py +11 -0
  23. dentate/backends/_stub.py +55 -0
  24. dentate/backends/cuda.py +16 -0
  25. dentate/backends/factory.py +31 -0
  26. dentate/backends/fake.py +165 -0
  27. dentate/backends/hf.py +317 -0
  28. dentate/backends/mlx.py +171 -0
  29. dentate/bootcamp/__init__.py +8 -0
  30. dentate/bootcamp/__main__.py +89 -0
  31. dentate/bootcamp/app.py +438 -0
  32. dentate/bootcamp/auth.py +231 -0
  33. dentate/bootcamp/config.py +121 -0
  34. dentate/bootcamp/experiment.py +277 -0
  35. dentate/bootcamp/jobs.py +434 -0
  36. dentate/bootcamp/papers.py +60 -0
  37. dentate/bootcamp/project.py +111 -0
  38. dentate/bootcamp/repository.py +240 -0
  39. dentate/bootcamp/resources.py +97 -0
  40. dentate/bootcamp/worker.py +247 -0
  41. dentate/cli.py +1885 -0
  42. dentate/compute.py +141 -0
  43. dentate/config.py +166 -0
  44. dentate/control.py +63 -0
  45. dentate/data/__init__.py +25 -0
  46. dentate/data/dataset.py +44 -0
  47. dentate/data/replay.py +53 -0
  48. dentate/data/schemas.py +63 -0
  49. dentate/demo/NOTICE +20 -0
  50. dentate/demo/TOKENIZER.json +12 -0
  51. dentate/demo/__init__.py +159 -0
  52. dentate/demo/starter.dentate +10 -0
  53. dentate/demo/tokenizer/merges.txt +48901 -0
  54. dentate/demo/tokenizer/special_tokens_map.json +28 -0
  55. dentate/demo/tokenizer/tokenizer.json +98249 -0
  56. dentate/demo/tokenizer/tokenizer_config.json +154 -0
  57. dentate/demo/tokenizer/vocab.json +1 -0
  58. dentate/distill/__init__.py +6 -0
  59. dentate/distill/judge_distill.py +24 -0
  60. dentate/domain/__init__.py +57 -0
  61. dentate/domain/ports.py +128 -0
  62. dentate/domain/types.py +145 -0
  63. dentate/errors.py +57 -0
  64. dentate/eval/__init__.py +20 -0
  65. dentate/eval/baselines.py +75 -0
  66. dentate/eval/evaluate.py +63 -0
  67. dentate/eval/report.py +25 -0
  68. dentate/eval/stats.py +48 -0
  69. dentate/export_smi.py +196 -0
  70. dentate/harness.py +284 -0
  71. dentate/ingest.py +21 -0
  72. dentate/integrations/__init__.py +37 -0
  73. dentate/integrations/compute.py +336 -0
  74. dentate/integrations/huggingface.py +233 -0
  75. dentate/integrations/secrets.py +112 -0
  76. dentate/integrations/wandb_link.py +29 -0
  77. dentate/manifest.py +81 -0
  78. dentate/model_docs.py +152 -0
  79. dentate/models/__init__.py +22 -0
  80. dentate/models/registry.py +141 -0
  81. dentate/models/userstore.py +53 -0
  82. dentate/obs.py +190 -0
  83. dentate/online/__init__.py +5 -0
  84. dentate/online/loop.py +78 -0
  85. dentate/os_sim/__init__.py +17 -0
  86. dentate/os_sim/cache.py +54 -0
  87. dentate/os_sim/checker.py +71 -0
  88. dentate/os_sim/dataset.py +180 -0
  89. dentate/os_sim/engine.py +462 -0
  90. dentate/os_sim/enrich.py +99 -0
  91. dentate/os_sim/filesystem.py +160 -0
  92. dentate/os_sim/grounding.py +64 -0
  93. dentate/os_sim/import_traces.py +341 -0
  94. dentate/os_sim/manage.py +93 -0
  95. dentate/os_sim/memory.py +150 -0
  96. dentate/os_sim/persona.py +51 -0
  97. dentate/os_sim/reward_bridge.py +123 -0
  98. dentate/os_sim/risk.py +50 -0
  99. dentate/os_sim/scenarios.py +172 -0
  100. dentate/os_sim/session.py +221 -0
  101. dentate/os_sim/shell_llm.py +127 -0
  102. dentate/os_sim/state.py +88 -0
  103. dentate/os_sim/templates.py +130 -0
  104. dentate/os_sim/trajectory.py +77 -0
  105. dentate/os_sim/vfs.py +260 -0
  106. dentate/package.py +113 -0
  107. dentate/playground.py +934 -0
  108. dentate/presets.py +96 -0
  109. dentate/pretrain/__init__.py +15 -0
  110. dentate/pretrain/agentic_data.py +153 -0
  111. dentate/pretrain/agentic_eval.py +102 -0
  112. dentate/pretrain/agentic_run.py +300 -0
  113. dentate/pretrain/assoc.py +261 -0
  114. dentate/pretrain/behaviors.py +83 -0
  115. dentate/pretrain/benchmark.py +102 -0
  116. dentate/pretrain/best_ckpt.py +60 -0
  117. dentate/pretrain/capacity.py +145 -0
  118. dentate/pretrain/certificate.py +116 -0
  119. dentate/pretrain/chatml.py +144 -0
  120. dentate/pretrain/code_env.py +310 -0
  121. dentate/pretrain/coherence.py +62 -0
  122. dentate/pretrain/corpusstats.py +166 -0
  123. dentate/pretrain/curator.py +177 -0
  124. dentate/pretrain/deeploop.py +104 -0
  125. dentate/pretrain/derivation.py +264 -0
  126. dentate/pretrain/gates.py +111 -0
  127. dentate/pretrain/grpo.py +308 -0
  128. dentate/pretrain/grpo_run.py +217 -0
  129. dentate/pretrain/hf_reason_data.py +169 -0
  130. dentate/pretrain/llc.py +98 -0
  131. dentate/pretrain/lm_replay.py +47 -0
  132. dentate/pretrain/longctx.py +61 -0
  133. dentate/pretrain/longctx_data.py +80 -0
  134. dentate/pretrain/longctx_train.py +66 -0
  135. dentate/pretrain/managed.py +493 -0
  136. dentate/pretrain/meaningful_eval.py +164 -0
  137. dentate/pretrain/metrics.py +391 -0
  138. dentate/pretrain/ntgrid.py +95 -0
  139. dentate/pretrain/oracle_traces.py +274 -0
  140. dentate/pretrain/passk.py +110 -0
  141. dentate/pretrain/pool.py +139 -0
  142. dentate/pretrain/presets.py +84 -0
  143. dentate/pretrain/preview.py +101 -0
  144. dentate/pretrain/probes.py +147 -0
  145. dentate/pretrain/reason_data.py +150 -0
  146. dentate/pretrain/reason_run.py +454 -0
  147. dentate/pretrain/reason_trainer.py +276 -0
  148. dentate/pretrain/recall_data.py +153 -0
  149. dentate/pretrain/recall_run.py +292 -0
  150. dentate/pretrain/smoltalk.py +44 -0
  151. dentate/pretrain/teacher.py +229 -0
  152. dentate/pretrain/tools.py +103 -0
  153. dentate/pretrain/trainer.py +764 -0
  154. dentate/pretrain/vision.py +181 -0
  155. dentate/pretrain/vl_run.py +110 -0
  156. dentate/pretrain/wmprobe.py +235 -0
  157. dentate/pretrain/world_model.py +150 -0
  158. dentate/primitives/__init__.py +19 -0
  159. dentate/primitives/losses.py +156 -0
  160. dentate/primitives/optimizer.py +28 -0
  161. dentate/primitives/sampling.py +55 -0
  162. dentate/primitives/schedules.py +20 -0
  163. dentate/reward/__init__.py +11 -0
  164. dentate/reward/combine.py +64 -0
  165. dentate/reward/reward_model.py +47 -0
  166. dentate/reward/service.py +57 -0
  167. dentate/runconfig.py +119 -0
  168. dentate/runkind/__init__.py +18 -0
  169. dentate/runkind/base.py +55 -0
  170. dentate/runkind/capabilities.py +46 -0
  171. dentate/runkind/distill.py +52 -0
  172. dentate/runkind/reason.py +63 -0
  173. dentate/runkind/rlvr.py +61 -0
  174. dentate/sandbox.py +146 -0
  175. dentate/scripts/__init__.py +1 -0
  176. dentate/scripts/campaign_100m.py +173 -0
  177. dentate/serve_openai.py +281 -0
  178. dentate/site.py +290 -0
  179. dentate/store.py +420 -0
  180. dentate/tasks/__init__.py +11 -0
  181. dentate/tasks/hf.py +151 -0
  182. dentate/tasks/masking.py +161 -0
  183. dentate/tasks/multi.py +59 -0
  184. dentate/tasks/reason_source.py +59 -0
  185. dentate/tasks/synthetic.py +89 -0
  186. dentate/train/__init__.py +6 -0
  187. dentate/train/curriculum.py +277 -0
  188. dentate/train/pipeline.py +383 -0
  189. dentate/train/stages.py +65 -0
  190. dentate/ui.py +189 -0
  191. dentate/util.py +39 -0
  192. dentate/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  193. dentate/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  194. dentate/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  195. dentate/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  196. dentate/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  197. dentate/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  198. dentate/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  199. dentate/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  200. dentate/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  201. dentate/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  202. dentate/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  203. dentate/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  204. dentate/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  205. dentate/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  206. dentate/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  207. dentate/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  208. dentate/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  209. dentate/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  210. dentate/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  211. dentate/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  212. dentate/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  213. dentate/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  214. dentate/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  215. dentate/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  216. dentate/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  217. dentate/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  218. dentate/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  219. dentate/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  220. dentate/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  221. dentate/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  222. dentate/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  223. dentate/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  224. dentate/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  225. dentate/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  226. dentate/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  227. dentate/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  228. dentate/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  229. dentate/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  230. dentate/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  231. dentate/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  232. dentate/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  233. dentate/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  234. dentate/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  235. dentate/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  236. dentate/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  237. dentate/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  238. dentate/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  239. dentate/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  240. dentate/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  241. dentate/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  242. dentate/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  243. dentate/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  244. dentate/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  245. dentate/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  246. dentate/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  247. dentate/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  248. dentate/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  249. dentate/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  250. dentate/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  251. dentate/web/assets/index-DsBOi9cQ.js +1058 -0
  252. dentate/web/assets/index-fQgsX_rq.css +41 -0
  253. dentate/web/favicon.svg +19 -0
  254. dentate/web/icon-192.png +0 -0
  255. dentate/web/icon-512.png +0 -0
  256. dentate/web/index.html +18 -0
  257. dentate/web/manifest.webmanifest +15 -0
  258. dentate/web/media/dentate.jpg +0 -0
  259. dentate/web/media/dentate.mp4 +0 -0
  260. dentate/web/sw.js +46 -0
  261. dentate-0.1.0.dist-info/METADATA +423 -0
  262. dentate-0.1.0.dist-info/RECORD +267 -0
  263. dentate-0.1.0.dist-info/WHEEL +5 -0
  264. dentate-0.1.0.dist-info/entry_points.txt +2 -0
  265. dentate-0.1.0.dist-info/licenses/LICENSE +202 -0
  266. dentate-0.1.0.dist-info/licenses/dentate/demo/NOTICE +20 -0
  267. dentate-0.1.0.dist-info/top_level.txt +1 -0
dentate/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Cortex Dentate — RLVR + distillation training over Gauntlet verifiable rewards.
2
+
3
+ P0 scaffold: typed config + model registry + CLI surface. Training loops (SFT/GKD/DPO/GRPO/online) land in
4
+ P1+ (see PLAN.md and docs/). Three tiers (docs/ARCHITECTURE.md): a 1–5B trainable *student* (default
5
+ Qwen3-4B-2507), a local *teacher* (default Gemma 4 ⇄ Qwen3-MoE) for bulk sampling + on-policy distillation,
6
+ and an API *oracle* that authors/judges. Reward = R1 verifiable (the reused Gauntlet composite — one source
7
+ of truth, no metric drift) ⊕ R2 reference (masking) ⊕ R3 AI-feedback (oracle RLAIF/PRM, gated inside R1).
8
+ """
9
+
10
+ __version__ = "0.0.0"
dentate/_deps.py ADDED
@@ -0,0 +1,38 @@
1
+ """Thread-safe access to the heavy third-party deps dentate imports lazily.
2
+
3
+ `transformers` builds its public namespace lazily: its package `__init__` runs a few hundred lines and
4
+ only as its LAST statement swaps `sys.modules["transformers"]` for the `_LazyModule` that resolves
5
+ `AutoTokenizer` & friends. A second thread running `from transformers import AutoTokenizer` inside that
6
+ window finds the half-built module already in `sys.modules`, misses the attribute, and dies with
7
+
8
+ ImportError: cannot import name 'AutoTokenizer' from 'transformers'
9
+
10
+ Dentate hits that window routinely, because it imports transformers off the main thread from more than
11
+ one place at once: the dashboard resolves examples in FastAPI's threadpool (the Analysis tab requests the
12
+ `train` and `eval` splits concurrently) and `RunManager` trains in background threads. Measured on
13
+ transformers 4.57.3, 8 threads racing a cold `from transformers import AutoTokenizer` fail 5 times.
14
+
15
+ So every dentate-side transformers import goes through `hf()`, which serializes the package build. The
16
+ lock is only ever contended on the first call; afterwards it is an uncontended acquire around attribute
17
+ lookups on an already-built module.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import threading
23
+ from typing import Any
24
+
25
+ _LOCK = threading.Lock()
26
+
27
+
28
+ def hf(*names: str) -> tuple[Any, ...]:
29
+ """`from transformers import <names>`, serialized against the lazy-init race described above.
30
+
31
+ Import-site replacement, so it unpacks exactly like the statement it replaces::
32
+
33
+ (AutoTokenizer,) = hf("AutoTokenizer")
34
+ AutoModelForCausalLM, AutoTokenizer = hf("AutoModelForCausalLM", "AutoTokenizer")
35
+ """
36
+ with _LOCK:
37
+ import transformers # noqa: PLC0415 — heavy dep, deliberately deferred to this single site
38
+ return tuple(getattr(transformers, name) for name in names)
@@ -0,0 +1,7 @@
1
+ """Concrete non-backend adapters: fake oracle/teacher plus experiment tracking."""
2
+
3
+ from .oracle import FakeOracle
4
+ from .teacher import FakeTeacher
5
+ from .tracking import NullTracker, TrackerError, WandbTracker, make_tracker
6
+
7
+ __all__ = ["FakeOracle", "FakeTeacher", "NullTracker", "TrackerError", "WandbTracker", "make_tracker"]
@@ -0,0 +1,5 @@
1
+ """Oracle adapters — the API judge/author tier. `FakeOracle` for dev/test; Anthropic adapter lands in P3."""
2
+
3
+ from .fake import FakeOracle
4
+
5
+ __all__ = ["FakeOracle"]
@@ -0,0 +1,62 @@
1
+ """FakeOracle — a deterministic stand-in for the API oracle (author / solve / judge / decompose).
2
+
3
+ It is a *strong* judge: it knows the task target (the tasks were generated here), so its R3 scores and rankings
4
+ are meaningful learning signal — exactly the RLAIF setup, minus the API cost. Optional `noise` simulates an
5
+ imperfect judge so the reward-model-distillation drift checks have something to detect.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import random
11
+ from collections.abc import Sequence
12
+
13
+ from ...domain.types import Rollout, Task, Verdict
14
+ from ...util import parse_wrapped, similarity, stable_seed
15
+
16
+
17
+ class FakeOracle:
18
+ """Implements the `Oracle` port."""
19
+
20
+ def __init__(self, noise: float = 0.0, seed: int = 0) -> None:
21
+ self.noise = noise
22
+ self.seed = seed
23
+
24
+ def author(self, seed: int, domain: str) -> Task:
25
+ # Lazy import to avoid an adapters→tasks import at module load.
26
+ if domain == "synthetic":
27
+ from ...tasks.synthetic import SyntheticTasks
28
+
29
+ return SyntheticTasks().sample(1, seed)[0]
30
+ from ...tasks.masking import MaskingTasks
31
+
32
+ return MaskingTasks(domain=domain).sample(1, seed)[0]
33
+
34
+ def solve(self, task: Task) -> Rollout:
35
+ """An expert trace (for SeqKD/RAFT warm-start) — the oracle solves it correctly."""
36
+ completion = f"<out>{task.target}</out>" if task.target is not None else ""
37
+ return Rollout(task_id=task.id, prompt=task.prompt, completion=completion, policy_version=-1,
38
+ meta={"role": "oracle"})
39
+
40
+ def judge(self, task: Task, rollouts: Sequence[Rollout]) -> list[Verdict]:
41
+ scores = [self._quality(task, r, i) for i, r in enumerate(rollouts)]
42
+ order = sorted(range(len(rollouts)), key=lambda i: scores[i], reverse=True)
43
+ rank_of = {i: rank for rank, i in enumerate(order)}
44
+ return [Verdict(score=round(scores[i], 4), rank=rank_of[i]) for i in range(len(rollouts))]
45
+
46
+ def decompose(self, task: Task) -> list[str]:
47
+ if task.steps:
48
+ return list(task.steps)
49
+ if task.target:
50
+ return [s for s in task.target.split("\n") if s] or [task.target]
51
+ return [task.prompt[:24]]
52
+
53
+ def _quality(self, task: Task, rollout: Rollout, idx: int) -> float:
54
+ inner = parse_wrapped(rollout.completion, "out")
55
+ if inner is None:
56
+ return 0.0
57
+ r1 = 1.0 if inner == task.target else 0.0
58
+ quality = 0.5 * r1 + 0.5 * similarity(inner, task.target or "")
59
+ if self.noise:
60
+ rng = random.Random(stable_seed("judge", self.seed, task.id, idx))
61
+ quality += (rng.random() * 2 - 1) * self.noise
62
+ return min(1.0, max(0.0, quality))
@@ -0,0 +1,5 @@
1
+ """Teacher adapters — the local bulk-sampler / GKD source. `FakeTeacher` for dev/test; MLX teacher in P3.5."""
2
+
3
+ from .fake import FakeTeacher
4
+
5
+ __all__ = ["FakeTeacher"]
@@ -0,0 +1,27 @@
1
+ """FakeTeacher — a competent *local* model: bulk-samples candidates and exposes logprobs (enabling GKD).
2
+
3
+ Implemented by reusing `FakeBackend` at a fixed high skill, so the teacher is just a strong, logprob-exposing
4
+ policy — which is exactly what a local teacher is. The API oracle, by contrast, has no logprobs (FakeOracle).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from ...backends.fake import FakeBackend, Solver
12
+ from ...domain.types import Policy, Rollout, Task
13
+
14
+
15
+ class FakeTeacher:
16
+ """Implements the `Teacher` port."""
17
+
18
+ def __init__(self, solver: Solver, model_key: str = "gemma-4", skill: float = 0.85, group_size: int = 4) -> None:
19
+ self.model_key = model_key
20
+ self._backend = FakeBackend(solver, group_size=group_size)
21
+ self._policy = Policy(model_key=model_key, version=0, params={"skill": skill})
22
+
23
+ def sample(self, tasks: Sequence[Task], n: int = 1, seed: int = 0) -> list[list[Rollout]]:
24
+ return self._backend.generate(self._policy, [t.prompt for t in tasks], n=n, seed=seed)
25
+
26
+ def logprobs(self, prompt: str, completion: str) -> list[float]:
27
+ return self._backend.logprobs(self._policy, prompt, completion)
@@ -0,0 +1,6 @@
1
+ """Experiment trackers — `NullTracker` (tests) and a fail-loud W&B tracker via `make_tracker`."""
2
+
3
+ from .null import NullTracker
4
+ from .wandb import TrackerError, WandbTracker, make_tracker
5
+
6
+ __all__ = ["NullTracker", "TrackerError", "WandbTracker", "make_tracker"]
@@ -0,0 +1,19 @@
1
+ """A no-op tracker — the default in tests and when W&B is disabled/absent."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class NullTracker:
7
+ """Implements the `Tracker` port; drops everything. Usable as a context manager."""
8
+
9
+ def log(self, metrics: dict[str, float], step: int | None = None) -> None:
10
+ return None
11
+
12
+ def summary(self, data: dict[str, float]) -> None:
13
+ return None
14
+
15
+ def __enter__(self) -> NullTracker:
16
+ return self
17
+
18
+ def __exit__(self, *exc: object) -> bool:
19
+ return False
@@ -0,0 +1,53 @@
1
+ """Weights & Biases tracker. Disabled tracking uses `NullTracker`; requested W&B failures are explicit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ...config import WandbSpec
6
+ from ...domain.ports import Tracker
7
+ from ...errors import TrackerError
8
+ from .null import NullTracker
9
+
10
+ __all__ = ["TrackerError", "WandbTracker", "make_tracker"]
11
+
12
+
13
+ class WandbTracker:
14
+ """Implements the `Tracker` port over a live W&B run. `wandb` is imported lazily (only on use)."""
15
+
16
+ def __init__(self, spec: WandbSpec) -> None:
17
+ try:
18
+ import wandb # noqa: PLC0415 (lazy by design — keep wandb out of the import graph)
19
+ except ImportError as exc:
20
+ raise TrackerError("W&B tracking was requested, but `wandb` is not importable") from exc
21
+
22
+ self._run = wandb.init(
23
+ project=spec.project, entity=spec.entity, group=spec.run_group or None,
24
+ tags=list(spec.tags) or None,
25
+ )
26
+
27
+ @property
28
+ def url(self) -> str | None:
29
+ """The live W&B run URL (stored in the manifest's links for cross-dashboard navigation)."""
30
+ return getattr(self._run, "url", None)
31
+
32
+ def log(self, metrics: dict[str, float], step: int | None = None) -> None:
33
+ self._run.log(metrics, step=step)
34
+
35
+ def summary(self, data: dict[str, float]) -> None:
36
+ for key, value in data.items():
37
+ self._run.summary[key] = value
38
+
39
+ def __enter__(self) -> WandbTracker:
40
+ return self
41
+
42
+ def __exit__(self, *exc: object) -> bool:
43
+ self._run.finish()
44
+ return False
45
+
46
+
47
+ def make_tracker(spec: WandbSpec) -> Tracker:
48
+ """Return a live W&B tracker, or a `NullTracker` when tracking is disabled."""
49
+ if not spec.enabled:
50
+ return NullTracker()
51
+ if spec.report_to != "wandb":
52
+ raise TrackerError(f"unsupported tracker {spec.report_to!r}; known: wandb")
53
+ return WandbTracker(spec)
dentate/agent.py ADDED
@@ -0,0 +1,76 @@
1
+ """Agentic tool-calling loop — lets a capable checkpoint use the sandbox tools, then answer.
2
+
3
+ Protocol (model-agnostic text form, works without native tool-calling support): to call a tool the model emits
4
+ <tool_call>{"name": "python", "arguments": {"code": "..."}}</tool_call>
5
+ and stops; it receives a `<tool_result>…</tool_result>` turn and continues; a turn with no tool_call is the final
6
+ answer. `run_agent` drives this against a `generate(messages) -> str` callable and a `Sandbox`, yielding events
7
+ the API streams to the UI (assistant text, tool_call, tool_result + workspace files, done / step-limit).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import re
14
+ from collections.abc import Callable, Iterator
15
+
16
+ from .sandbox import TOOLS, Sandbox, dispatch
17
+
18
+ _CALL_RE = re.compile(r"<tool_call>\s*(\{.*?\})\s*</tool_call>", re.DOTALL)
19
+
20
+
21
+ def system_prompt() -> str:
22
+ lines = [
23
+ "You are an agent that can use tools running in a sandboxed workspace. To call a tool, output EXACTLY:",
24
+ '<tool_call>{"name": "<tool>", "arguments": { ... }}</tool_call>',
25
+ "then stop and wait. You will receive <tool_result>…</tool_result>; then continue. When you have the",
26
+ "answer, reply normally WITHOUT a tool_call. Prefer writing files for anything the user should keep.",
27
+ "",
28
+ "Available tools:",
29
+ ]
30
+ lines += [f"- {t['name']}({', '.join(t['arguments'])}): {t['description']}" for t in TOOLS]
31
+ return "\n".join(lines)
32
+
33
+
34
+ def strip_tool_calls(text: str) -> str:
35
+ """Remove the raw <tool_call> markers from a turn for display (the UI shows a tool card instead)."""
36
+ return _CALL_RE.sub("", text or "").strip()
37
+
38
+
39
+ def parse_tool_call(text: str) -> dict | None:
40
+ """Extract the first well-formed <tool_call> object, or None (a normal final answer)."""
41
+ m = _CALL_RE.search(text or "")
42
+ if not m:
43
+ return None
44
+ try:
45
+ obj = json.loads(m.group(1))
46
+ except json.JSONDecodeError:
47
+ return None
48
+ if isinstance(obj, dict) and isinstance(obj.get("name"), str):
49
+ args = obj.get("arguments")
50
+ return {"name": obj["name"], "arguments": args if isinstance(args, dict) else {}}
51
+ return None
52
+
53
+
54
+ def run_agent(generate: Callable[[list[dict]], str], sandbox: Sandbox, messages: list[dict],
55
+ *, max_steps: int = 6) -> Iterator[dict]:
56
+ """Drive the tool-calling loop. Yields event dicts; terminates on a tool-free turn or `max_steps`."""
57
+ base_system = next((m.get("content", "") for m in messages if m.get("role") == "system"), "")
58
+ sys_msg = {"role": "system", "content": (base_system + "\n\n" + system_prompt()).strip()}
59
+ convo: list[dict] = [sys_msg, *[m for m in messages if m.get("role") != "system"]]
60
+
61
+ for step in range(max_steps):
62
+ text = generate(convo)
63
+ call = parse_tool_call(text)
64
+ yield {"type": "assistant", "text": text, "step": step}
65
+ convo.append({"role": "assistant", "content": text})
66
+ if not call:
67
+ yield {"type": "done"}
68
+ return
69
+ yield {"type": "tool_call", "name": call["name"], "arguments": call["arguments"], "step": step}
70
+ result = dispatch(sandbox, call["name"], call["arguments"])
71
+ yield {"type": "tool_result", "name": call["name"], "ok": result.ok,
72
+ "output": result.as_text(), "files": result.files, "step": step}
73
+ convo.append({"role": "user", "content": f"<tool_result>\n{result.as_text()}\n</tool_result>"})
74
+
75
+ yield {"type": "tool_limit", "max_steps": max_steps}
76
+ yield {"type": "done"}