fabric-rlm 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. fabric_rlm-0.2.8/LICENSE +21 -0
  2. fabric_rlm-0.2.8/PKG-INFO +392 -0
  3. fabric_rlm-0.2.8/README.md +350 -0
  4. fabric_rlm-0.2.8/fabric_rlm/__init__.py +92 -0
  5. fabric_rlm-0.2.8/fabric_rlm/_worker.py +993 -0
  6. fabric_rlm-0.2.8/fabric_rlm/artifacts.py +129 -0
  7. fabric_rlm-0.2.8/fabric_rlm/cli.py +184 -0
  8. fabric_rlm-0.2.8/fabric_rlm/excel_artifacts.py +434 -0
  9. fabric_rlm-0.2.8/fabric_rlm/experimental/__init__.py +38 -0
  10. fabric_rlm-0.2.8/fabric_rlm/experimental/adaptive.py +255 -0
  11. fabric_rlm-0.2.8/fabric_rlm/experimental/adaptive_eval.py +116 -0
  12. fabric_rlm-0.2.8/fabric_rlm/experimental/adaptive_policy.py +824 -0
  13. fabric_rlm-0.2.8/fabric_rlm/experimental/adaptive_runner.py +447 -0
  14. fabric_rlm-0.2.8/fabric_rlm/experimental/bandit_policy.py +258 -0
  15. fabric_rlm-0.2.8/fabric_rlm/experimental/decompose_engine.py +155 -0
  16. fabric_rlm-0.2.8/fabric_rlm/experimental/decompose_rung.py +329 -0
  17. fabric_rlm-0.2.8/fabric_rlm/experimental/effort_ladder_policy.py +248 -0
  18. fabric_rlm-0.2.8/fabric_rlm/experimental/task_classifier.py +292 -0
  19. fabric_rlm-0.2.8/fabric_rlm/interpreter.py +759 -0
  20. fabric_rlm-0.2.8/fabric_rlm/lm.py +360 -0
  21. fabric_rlm-0.2.8/fabric_rlm/metrics.py +124 -0
  22. fabric_rlm-0.2.8/fabric_rlm/prompts.py +194 -0
  23. fabric_rlm-0.2.8/fabric_rlm/py.typed +1 -0
  24. fabric_rlm-0.2.8/fabric_rlm/replay.py +318 -0
  25. fabric_rlm-0.2.8/fabric_rlm/replay_lm.py +360 -0
  26. fabric_rlm-0.2.8/fabric_rlm/runtime.py +2696 -0
  27. fabric_rlm-0.2.8/fabric_rlm/security.py +687 -0
  28. fabric_rlm-0.2.8/fabric_rlm/serializers.py +199 -0
  29. fabric_rlm-0.2.8/fabric_rlm/skill_loader.py +328 -0
  30. fabric_rlm-0.2.8/fabric_rlm/skill_router.py +256 -0
  31. fabric_rlm-0.2.8/fabric_rlm/skills/PLAYBOOK_CONTRACT.md +141 -0
  32. fabric_rlm-0.2.8/fabric_rlm/skills/SKILL_TEMPLATE.md +99 -0
  33. fabric_rlm-0.2.8/fabric_rlm/skills/__init__.py +2 -0
  34. fabric_rlm-0.2.8/fabric_rlm/skills/core.md +105 -0
  35. fabric_rlm-0.2.8/fabric_rlm/skills/data_exploration.md +274 -0
  36. fabric_rlm-0.2.8/fabric_rlm/skills/error_handling.md +36 -0
  37. fabric_rlm-0.2.8/fabric_rlm/skills/excel_extract.md +583 -0
  38. fabric_rlm-0.2.8/fabric_rlm/skills/excel_modify.md +251 -0
  39. fabric_rlm-0.2.8/fabric_rlm/skills/pdf_document_analysis.md +113 -0
  40. fabric_rlm-0.2.8/fabric_rlm/skills/validation.md +38 -0
  41. fabric_rlm-0.2.8/fabric_rlm/trajectory.py +614 -0
  42. fabric_rlm-0.2.8/fabric_rlm/validators.py +420 -0
  43. fabric_rlm-0.2.8/fabric_rlm.egg-info/PKG-INFO +392 -0
  44. fabric_rlm-0.2.8/fabric_rlm.egg-info/SOURCES.txt +115 -0
  45. fabric_rlm-0.2.8/fabric_rlm.egg-info/dependency_links.txt +1 -0
  46. fabric_rlm-0.2.8/fabric_rlm.egg-info/entry_points.txt +2 -0
  47. fabric_rlm-0.2.8/fabric_rlm.egg-info/requires.txt +19 -0
  48. fabric_rlm-0.2.8/fabric_rlm.egg-info/top_level.txt +1 -0
  49. fabric_rlm-0.2.8/pyproject.toml +94 -0
  50. fabric_rlm-0.2.8/setup.cfg +4 -0
  51. fabric_rlm-0.2.8/tests/test_adaptive.py +52 -0
  52. fabric_rlm-0.2.8/tests/test_adaptive_eval.py +70 -0
  53. fabric_rlm-0.2.8/tests/test_adaptive_policy.py +473 -0
  54. fabric_rlm-0.2.8/tests/test_adaptive_runner.py +433 -0
  55. fabric_rlm-0.2.8/tests/test_adaptive_runtime.py +448 -0
  56. fabric_rlm-0.2.8/tests/test_artifacts.py +34 -0
  57. fabric_rlm-0.2.8/tests/test_bandit_policy.py +307 -0
  58. fabric_rlm-0.2.8/tests/test_cached_reasoning_tokens.py +158 -0
  59. fabric_rlm-0.2.8/tests/test_cli_run_flags.py +88 -0
  60. fabric_rlm-0.2.8/tests/test_decompose_rung.py +314 -0
  61. fabric_rlm-0.2.8/tests/test_effort_ladder_policy.py +320 -0
  62. fabric_rlm-0.2.8/tests/test_engine_consolidation_parity.py +924 -0
  63. fabric_rlm-0.2.8/tests/test_env_limits.py +35 -0
  64. fabric_rlm-0.2.8/tests/test_excel_artifacts.py +322 -0
  65. fabric_rlm-0.2.8/tests/test_golden_trajectories.py +47 -0
  66. fabric_rlm-0.2.8/tests/test_halve_max_iter_param.py +124 -0
  67. fabric_rlm-0.2.8/tests/test_interpreter.py +133 -0
  68. fabric_rlm-0.2.8/tests/test_lm.py +82 -0
  69. fabric_rlm-0.2.8/tests/test_lm_smart_defaults.py +161 -0
  70. fabric_rlm-0.2.8/tests/test_metrics.py +61 -0
  71. fabric_rlm-0.2.8/tests/test_ons_cpi_benchmark.py +95 -0
  72. fabric_rlm-0.2.8/tests/test_output_format_guard.py +189 -0
  73. fabric_rlm-0.2.8/tests/test_prompt_mapreduce.py +131 -0
  74. fabric_rlm-0.2.8/tests/test_prompts.py +26 -0
  75. fabric_rlm-0.2.8/tests/test_refreshing_lm.py +310 -0
  76. fabric_rlm-0.2.8/tests/test_replay_cli.py +176 -0
  77. fabric_rlm-0.2.8/tests/test_replay_lm.py +368 -0
  78. fabric_rlm-0.2.8/tests/test_rlm_facade.py +299 -0
  79. fabric_rlm-0.2.8/tests/test_rlm_from_task.py +413 -0
  80. fabric_rlm-0.2.8/tests/test_rlm_result_aliases.py +158 -0
  81. fabric_rlm-0.2.8/tests/test_router_task_text_fallback.py +160 -0
  82. fabric_rlm-0.2.8/tests/test_routing_uses_bound_inputs.py +135 -0
  83. fabric_rlm-0.2.8/tests/test_runtime_budget.py +126 -0
  84. fabric_rlm-0.2.8/tests/test_runtime_code_extraction.py +141 -0
  85. fabric_rlm-0.2.8/tests/test_runtime_final_turn_nudge.py +250 -0
  86. fabric_rlm-0.2.8/tests/test_runtime_mock_lm.py +138 -0
  87. fabric_rlm-0.2.8/tests/test_runtime_repair_nudge.py +109 -0
  88. fabric_rlm-0.2.8/tests/test_runtime_router_integration.py +187 -0
  89. fabric_rlm-0.2.8/tests/test_runtime_stdout_truncation.py +259 -0
  90. fabric_rlm-0.2.8/tests/test_runtime_stuck_loop.py +374 -0
  91. fabric_rlm-0.2.8/tests/test_runtime_timeout.py +117 -0
  92. fabric_rlm-0.2.8/tests/test_runtime_token_details.py +509 -0
  93. fabric_rlm-0.2.8/tests/test_runtime_token_tracking.py +211 -0
  94. fabric_rlm-0.2.8/tests/test_runtime_truncation_hint.py +92 -0
  95. fabric_rlm-0.2.8/tests/test_runtime_verifier.py +408 -0
  96. fabric_rlm-0.2.8/tests/test_security_baseline.py +298 -0
  97. fabric_rlm-0.2.8/tests/test_security_corpus_regression.py +126 -0
  98. fabric_rlm-0.2.8/tests/test_security_violation_state_preserved.py +98 -0
  99. fabric_rlm-0.2.8/tests/test_serializers.py +95 -0
  100. fabric_rlm-0.2.8/tests/test_serializers_stable_repr.py +150 -0
  101. fabric_rlm-0.2.8/tests/test_skill_frontmatter.py +220 -0
  102. fabric_rlm-0.2.8/tests/test_skill_router.py +243 -0
  103. fabric_rlm-0.2.8/tests/test_smoke_fabric.py +26 -0
  104. fabric_rlm-0.2.8/tests/test_subprocess_interpreter.py +399 -0
  105. fabric_rlm-0.2.8/tests/test_task_classifier.py +284 -0
  106. fabric_rlm-0.2.8/tests/test_token_usage_extraction.py +119 -0
  107. fabric_rlm-0.2.8/tests/test_trajectory_analysis.py +778 -0
  108. fabric_rlm-0.2.8/tests/test_trajectory_cleanup.py +159 -0
  109. fabric_rlm-0.2.8/tests/test_v7_token_totals.py +82 -0
  110. fabric_rlm-0.2.8/tests/test_validators.py +224 -0
  111. fabric_rlm-0.2.8/tests/test_verifier_wrapper.py +187 -0
  112. fabric_rlm-0.2.8/tests/test_wire_decompose_rung.py +212 -0
  113. fabric_rlm-0.2.8/tests/test_wire_task_classifier.py +320 -0
  114. fabric_rlm-0.2.8/tests/test_worker_predict.py +124 -0
  115. fabric_rlm-0.2.8/tests/test_worker_sandbox_shim.py +257 -0
  116. fabric_rlm-0.2.8/tests/test_worker_subprocess_traceback.py +100 -0
  117. fabric_rlm-0.2.8/tests/test_worker_traceback_filter.py +232 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fabric-rlm contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,392 @@
1
+ Metadata-Version: 2.4
2
+ Name: fabric-rlm
3
+ Version: 0.2.8
4
+ Summary: A portable Python subprocess runtime for Recursive Language Models in Fabric notebooks and managed Python environments.
5
+ Author: Sandeep Pawar
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pawarbi/fabric-rlm-core
8
+ Project-URL: Repository, https://github.com/pawarbi/fabric-rlm-core
9
+ Project-URL: Issues, https://github.com/pawarbi/fabric-rlm-core/issues
10
+ Project-URL: Changelog, https://github.com/pawarbi/fabric-rlm-core/blob/main/CHANGELOG.md
11
+ Keywords: rlm,recursive-language-model,llm,agents,code-interpreter,dspy,fabric,microsoft-fabric
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: aiohttp>=3.12
27
+ Requires-Dist: dspy<3.4,>=3.2.1
28
+ Requires-Dist: nest_asyncio>=1.6
29
+ Requires-Dist: pydantic>=2
30
+ Requires-Dist: typing_extensions>=4.15
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
34
+ Provides-Extra: fabric
35
+ Requires-Dist: synapseml; extra == "fabric"
36
+ Provides-Extra: pdf
37
+ Requires-Dist: pymupdf>=1.24; extra == "pdf"
38
+ Provides-Extra: analytics
39
+ Requires-Dist: duckdb>=1.1; extra == "analytics"
40
+ Requires-Dist: polars>=1.0; extra == "analytics"
41
+ Dynamic: license-file
42
+
43
+ # fabric-rlm
44
+
45
+ [![CI](https://github.com/pawarbi/fabric-rlm-core/actions/workflows/test.yml/badge.svg)](https://github.com/pawarbi/fabric-rlm-core/actions/workflows/test.yml)
46
+ [![PyPI](https://img.shields.io/pypi/v/fabric-rlm.svg)](https://pypi.org/project/fabric-rlm/)
47
+ [![Python](https://img.shields.io/pypi/pyversions/fabric-rlm.svg)](https://pypi.org/project/fabric-rlm/)
48
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
49
+
50
+ Recursive Language Models (RLMs) for Microsoft Fabric notebooks. The model writes
51
+ Python, the code runs in a real CPython subprocess, and the model keeps iterating
52
+ (reading outputs, fixing its own errors) until it calls `SUBMIT(...)` with the
53
+ answer. It also runs anywhere else CPython runs: your laptop, CI, or an Azure
54
+ Function.
55
+
56
+ ## Why not dspy's RLM directly in a notebook
57
+
58
+ dspy ships its own `RLM`, but it executes generated code in a Deno + Pyodide
59
+ (WASM) sandbox. That runtime is impractical to stand up in a Fabric notebook, and
60
+ because it is WASM-isolated it cannot import the notebook's installed Python
61
+ packages or reach the mounted Lakehouse. fabric-rlm keeps dspy's RLM loop and
62
+ replaces the interpreter with a CPython subprocess that runs inside the notebook
63
+ process. The model's code gets the full Python ecosystem (pandas, DuckDB, Polars,
64
+ openpyxl, PyMuPDF) and can read and write Lakehouse `Files` paths directly. The
65
+ raw bytes stay in the subprocess; only the model's summaries and computed results
66
+ go back through the LM, so it works on files far larger than any context window.
67
+
68
+ ## What you get
69
+
70
+ - Built for Fabric notebooks: runs where dspy's Deno/Pyodide RLM can't, and reads
71
+ and writes the mounted Lakehouse filesystem directly.
72
+ - No key and no resource to provision on Fabric: `FabricLM` uses the capacity's
73
+ built-in Azure OpenAI endpoint (details below).
74
+ - Real subprocess execution: full CPython, native libraries, and real files.
75
+ - Reusable skills: Markdown playbooks for PDFs, tabular/log EDA, and Excel, with a
76
+ keyword router that loads only what a task needs.
77
+ - A self-correcting loop: a PLAN / VERIFY / REFLECT contract and an output
78
+ verifier feed failures back to the model so it fixes its own code.
79
+ - Three engines plus an experimental adaptive one, including native
80
+ `dspy.predict.RLM` interop.
81
+ - A default security policy that scrubs secret-bearing environment variables and
82
+ blocks destructive Lakehouse operations (`notebookutils.fs`, `mssparkutils`).
83
+ - Structured trajectories you can inspect, save to the Lakehouse, and replay
84
+ offline.
85
+ - Runs locally too, and ships `py.typed`.
86
+
87
+ ## When to use an RLM (and when not to)
88
+
89
+ An RLM earns its overhead when the task needs computation, iteration, or data
90
+ that will not fit in a context window. For a quick question or a conversational
91
+ task, a plain LLM call is cheaper and faster; the loop and the subprocess add
92
+ nothing.
93
+
94
+ Use an RLM when:
95
+
96
+ - The data is larger than any context window. Multi-gigabyte Spark logs, wide
97
+ Excel workbooks, long JSONL streams: the model queries them with DuckDB,
98
+ Polars, or openpyxl inside the subprocess, and only its aggregates enter the
99
+ LM context. This is what the `data_exploration` skill was built for.
100
+ - The answer must be computed exactly or written to a file. An LLM cannot
101
+ reliably do arithmetic over thousands of rows or edit an `.xlsx` in place; code
102
+ can. On the full SpreadsheetBench Verified-400, MiniMax M3 through this RLM
103
+ passes 82.5 percent of questions for about $2.60 of total model spend; see
104
+ the benchmark section below.
105
+ - The task is multi-step and its output is checkable. With a validator attached,
106
+ failed attempts feed structured reflection into retries: in the ablation, a
107
+ hard multi-step task went from ladder-exhausted failure to passing, with 68%
108
+ fewer tokens.
109
+ - You need strict multi-field extraction from long documents. The same ablation
110
+ measured a 74% token reduction on a 100KB RFP extraction with the
111
+ plan/verify contract on.
112
+
113
+ Skip the RLM when:
114
+
115
+ - It is a conversation, a rewrite, or a judgment call over text that fits in
116
+ context. There is nothing to compute, so a single LM call wins.
117
+ - The model nails the task in one shot. The ablation caught this failure mode
118
+ directly: on an easy log-extraction task the verify loop spuriously re-checked
119
+ a correct first answer, inflating cost from 6K to 40K tokens and 10 seconds to
120
+ 176. Correctness held, but a plain call would have been 10x cheaper.
121
+ - The task exceeds the model's capability. The loop retries more cheaply, but it
122
+ does not rescue tasks the model fundamentally cannot solve; use a stronger
123
+ model instead.
124
+
125
+ ### A worked example
126
+
127
+ One task, two attempts. The task: from a 140 MB IMF CPI pull (1.5 million rows,
128
+ 194 countries, fetched live from the public SDMX API), build a formatted Excel
129
+ report: a pivot of the 10 highest-inflation countries by year, a merged title
130
+ cell, styled headers, and a second sheet listing every qualifying country.
131
+
132
+ The first attempt gives gpt-5.1 the question plus as much raw CSV as
133
+ fits in a prompt. The second gives gpt-5-mini, about 5x cheaper, the same
134
+ question through the RLM. Measured result:
135
+
136
+ | run | result | workbook | tokens | cost | seconds |
137
+ |---|---|---|---|---|---|
138
+ | plain call, gpt-5.1 | failed | none | 109,480 | $0.138 | 8.4 |
139
+ | RLM, gpt-5-mini | passed | correct, verified | 47,642 | $0.023 | 78.4 |
140
+
141
+ gpt-5.1 burned 109K tokens discovering the data was never in its context.
142
+ The mini model wrote DuckDB and openpyxl code in the subprocess, built the
143
+ workbook, and a deterministic ground-truth query verified every cell. The
144
+ failed call cost six times more than the successful one. The notebook then
145
+ pushes the same mini model through a harder task (finding inflation streaks
146
+ with tie-breaks, conditional formatting, and an embedded chart, cleared for
147
+ about two cents) and closes with an honest skill ablation. Run it yourself:
148
+ [examples/notebooks/rlm_vs_plain_llm_imf_cpi.ipynb](examples/notebooks/rlm_vs_plain_llm_imf_cpi.ipynb).
149
+
150
+ ### Benchmark: SpreadsheetBench Verified-400
151
+
152
+ [SpreadsheetBench](https://github.com/RUCKBReasoning/SpreadsheetBench-2) tests
153
+ whether an agent can carry out real spreadsheet-manipulation instructions,
154
+ graded cell-exactly against golden workbooks. On the full Version 1
155
+ Verified-400 set (all 400 questions, single attempt each, temperature 1.0,
156
+ fabric-rlm 0.2.8 with the `excel_modify` skill and workbook structure context,
157
+ model served from MiniMax's first-party endpoint):
158
+
159
+ | system | model | pass rate | model spend |
160
+ |---|---|---|---|
161
+ | fabric-rlm | MiniMax M3 (open weights, $0.30/M in, $1.20/M out) | **82.5%** (330/400) | $2.61 total, $0.0065 per question |
162
+
163
+ That 82.5 percent is the score reported by the benchmark's own
164
+ `evaluation.py`, run unmodified over our output workbooks, so it is measured the
165
+ same way as every figure below. It is self-reported in the sense that we ran the
166
+ script ourselves; an official submission is planned.
167
+
168
+ For context, the top of the public V1-Verified (400) leaderboard is held by
169
+ commercial spreadsheet products: Qingqiu Agent at 98.25, ByteDance's Data
170
+ Analysis Agent at 96.5, GPT for Excel at 92.5, WPS AI at 91.25. Ours comes from
171
+ an open-source library driving a cheap open-weight model, at a cost of well
172
+ under a cent per task.
173
+
174
+ ```mermaid
175
+ xychart-beta
176
+ title "SpreadsheetBench V1 Verified-400 pass rate (percent)"
177
+ x-axis ["fabric-rlm + MiniMax M3", "WPS AI", "GPT for Excel", "Data Analysis Agent", "Qingqiu Agent"]
178
+ y-axis "pass rate" 0 --> 100
179
+ bar [82.5, 91.25, 92.5, 96.5, 98.25]
180
+ ```
181
+
182
+ For scale, two vendors publish a result for Claude Opus 4.6 driven by a bare
183
+ prompt with no agent loop: 321/400 = 80.2 percent
184
+ ([DealGlass results repo](https://github.com/arthursolwayne/spreadsheet-agents))
185
+ and 80.25 percent (Leni). Opus 4.6 tokens are priced roughly 17 times higher on
186
+ input and 21 times higher on output than MiniMax M3. So an open-source runtime
187
+ driving an open-weight model with a spreadsheet skill scores slightly above a
188
+ frontier model asked directly, at a small fraction of the per-token price. Two
189
+ caveats worth stating plainly: that comparison is against a single-shot prompt,
190
+ and the same Opus 4.6 inside a purpose-built scaffold reaches 95.2 percent, so the
191
+ honest reading is that scaffolding matters more than model choice, not that one
192
+ model beats another. All three figures are self-reported.
193
+
194
+ Reproduce it with
195
+ [examples/notebooks/ssb400_minimax_m3_fabric_repro.ipynb](examples/notebooks/ssb400_minimax_m3_fabric_repro.ipynb);
196
+ the run needs an OpenRouter key and costs a few dollars.
197
+
198
+ ## How it works, in one picture
199
+
200
+ ```mermaid
201
+ flowchart TD
202
+ A["Task + inputs<br/>(values and File handles)"] --> B["RLM runtime<br/>routes skills, builds the prompt"]
203
+ B --> C["Model writes Python"]
204
+ C --> D["CPython subprocess executes it<br/>pandas, DuckDB, openpyxl, Lakehouse Files"]
205
+ D --> E{"SUBMIT(...) called?"}
206
+ E -- "no" --> F["stdout, errors, and a bounded<br/>namespace snapshot go back to the model"]
207
+ F --> C
208
+ E -- "yes" --> G{"Output verifier<br/>accepts?"}
209
+ G -- "rejected, with feedback" --> C
210
+ G -- "accepted" --> H["Result payload + trajectory<br/>(inspect, save, replay)"]
211
+ ```
212
+
213
+ The raw bytes of your files never enter the LM context. The model reads
214
+ summaries, previews, and computed aggregates; the heavy lifting happens in the
215
+ subprocess.
216
+
217
+ ## Use it in a Fabric notebook
218
+
219
+ Install on the Python 3.12 `jupyter_python` kernel, then restart the session:
220
+
221
+ ```python
222
+ %pip install fabric-rlm
223
+ # For the PDF skill and PDF notebooks, add the extra:
224
+ # %pip install fabric-rlm[pdf]
225
+ ```
226
+
227
+ If imports fail on the Synapse PySpark kernel, see
228
+ [docs/fabric-runtime-deps.md](docs/fabric-runtime-deps.md).
229
+
230
+ On a paid Fabric capacity, `FabricLM` uses the capacity's built-in Azure OpenAI
231
+ endpoint. It discovers the endpoint through `synapse.ml.fabric` and authenticates
232
+ with the notebook's AAD token, refreshing it automatically on long runs. You do
233
+ not provision an Azure OpenAI resource and you do not manage an API key:
234
+
235
+ ```python
236
+ from fabric_rlm import RLM, FabricLM, File
237
+
238
+ rlm = RLM.task(
239
+ task="Find the root cause of the failure in this Spark log.",
240
+ inputs={"log": File("/lakehouse/default/Files/logs/app.log")},
241
+ outputs=["root_cause"],
242
+ lm=FabricLM("gpt-5.1"),
243
+ )
244
+ print(rlm.run().root_cause)
245
+ ```
246
+
247
+ Because the subprocess runs inside the notebook, the model's code reads and writes
248
+ mounted Lakehouse `Files` paths directly, and traces can be written back to the
249
+ Lakehouse. See [examples/notebooks/](examples/notebooks/) for ready-to-import
250
+ recipes; start with `rlm_api_tour.ipynb`.
251
+
252
+ ## Run it locally
253
+
254
+ The same code runs on your laptop, in CI, or in an Azure Function. Use `OpenAILM`
255
+ or `AnthropicLM` with the matching API key instead of `FabricLM`:
256
+
257
+ ```bash
258
+ pip install fabric-rlm
259
+ ```
260
+
261
+ ```python
262
+ from fabric_rlm import RLM, OpenAILM # set OPENAI_API_KEY in your environment
263
+
264
+ rlm = RLM.task(
265
+ task="Sum every integer from 1 to 1,000,000 that is divisible by 3 or 5.",
266
+ outputs=["answer"],
267
+ lm=OpenAILM("gpt-4o-mini"),
268
+ )
269
+ print(rlm.run().answer)
270
+ ```
271
+
272
+ `RLM.task(...)` is the short constructor; `RLM.from_task(...)` is the explicit
273
+ form. `OpenAILM`, `AnthropicLM`, and `FabricLM` are thin wrappers over `dspy.LM`,
274
+ so any OpenAI, Anthropic, Azure, or local Ollama model works.
275
+
276
+ Requires Python 3.10 or newer. Optional extras:
277
+
278
+ | Extra | Adds | For |
279
+ |---|---|---|
280
+ | `fabric-rlm[pdf]` | PyMuPDF | the `pdf_document_analysis` skill and PDF notebooks |
281
+ | `fabric-rlm[analytics]` | DuckDB, Polars | large-file EDA with `data_exploration` |
282
+ | `fabric-rlm[fabric]` | SynapseML | Microsoft Fabric notebook integration |
283
+ | `fabric-rlm[dev]` | pytest | running the test suite |
284
+
285
+ ## The worker API
286
+
287
+ Inputs and files. Bind values, including large files, as inputs. Files arrive
288
+ inside the worker as `File(...)` handles with `.path`, `.read_text()`,
289
+ `.read_bytes()`, and `.exists()`, so a Lakehouse path or a local path is just a
290
+ file path.
291
+
292
+ The SUBMIT contract. The runtime injects `SUBMIT(...)`. Call it with keyword
293
+ arguments matching your declared `outputs`, or with positional arguments in the
294
+ same order. After a valid SUBMIT, `result.payload` holds the dict and each field
295
+ is also reachable as an attribute (`result.answer`).
296
+
297
+ Recursive sub-LM calls. Inside its Python, the model can call a nested LM with
298
+ `predict_sync("english -> french", english=phrase)` (or the async `predict`),
299
+ optionally routed to a cheaper `sub_lm=`.
300
+
301
+ ## Engines
302
+
303
+ `RLM` ships with three stable engines, plus the experimental `adaptive`:
304
+
305
+ | Engine | What it does | When to pick it |
306
+ |---|---|---|
307
+ | `"auto"` (default) | Uses `"dspy"` when a non-empty `tools=[...]` is passed, otherwise `"default"` | You don't want to think about it. Recommended. |
308
+ | `"default"` | Custom loop with skills, router, reflection, and verifier | You want skills and multi-turn verifier feedback. |
309
+ | `"dspy"` | Delegates to `dspy.predict.RLM` with the subprocess as its backend | You want dspy-native composability or `tools=`. |
310
+ | `"adaptive"` | Escalates compute (more turns, then higher reasoning effort, then best-of-N, then a stronger LM) when a validator rejects an attempt | Hard, verifiable tasks. Experimental (opt-in `UserWarning`). |
311
+
312
+ The default `core` skill carries a PLAN / VERIFY / REFLECT contract: plan before
313
+ running code, self-check before SUBMIT, and carry prior-attempt failures into
314
+ retries. It is on by default. Set `FABRIC_RLM_PVR=0` to turn it off for
315
+ token-sensitive batch runs on trivial tasks.
316
+
317
+ ## Bundled skills
318
+
319
+ Skills are Markdown playbooks the router loads on demand:
320
+
321
+ - `pdf_document_analysis`: long-document analysis with PyMuPDF
322
+ - `data_exploration`: tabular and log EDA with pandas, Polars, and DuckDB
323
+ - `excel_extract` / `excel_modify`: read and edit `.xlsx` workbooks via openpyxl
324
+ - `core`, `validation`, `error_handling`: default scaffolding
325
+
326
+ Write your own by copying
327
+ [fabric_rlm/skills/SKILL_TEMPLATE.md](fabric_rlm/skills/SKILL_TEMPLATE.md). The
328
+ structure is documented in
329
+ [PLAYBOOK_CONTRACT.md](fabric_rlm/skills/PLAYBOOK_CONTRACT.md).
330
+
331
+ ## Security
332
+
333
+ This library runs model-generated code. The default `SecurityPolicy` provides
334
+ guardrails, not isolation: it scrubs secret-bearing environment variables from the
335
+ worker, screens generated code through a configurable policy before it runs, and
336
+ blocks destructive Lakehouse operations (`notebookutils.fs.rm` / `mv`,
337
+ `mssparkutils` aliases). Read [SECURITY.md](SECURITY.md) before running untrusted
338
+ prompts against sensitive data or credentials.
339
+
340
+ ## CLI
341
+
342
+ ```bash
343
+ fabric-rlm --version
344
+ fabric-rlm run examples/simple_math/task.json # run a task from JSON
345
+ fabric-rlm trace inspect path/to/trajectory.jsonl # summarize and diagnose a saved run
346
+ ```
347
+
348
+ ## Documentation
349
+
350
+ - [QUICKSTART.md](QUICKSTART.md): step-by-step guide covering install, first run,
351
+ Fabric notebook usage, sub-LM calls, traces, and skill authoring.
352
+ - [docs/fabric-runtime-deps.md](docs/fabric-runtime-deps.md): read this if a
353
+ Fabric notebook fails at import time (`Sentinel`, `yarl.Query`,
354
+ `aiohttp.ConnectionTimeoutError`).
355
+ - [docs/lossless-submit-payloads.md](docs/lossless-submit-payloads.md): how final
356
+ payloads avoid namespace-snapshot truncation.
357
+ - [examples/notebooks/](examples/notebooks/): ready-to-import Fabric recipes.
358
+ Start with `rlm_vs_plain_llm_imf_cpi.ipynb` (the with-and-without comparison)
359
+ and `rlm_api_tour.ipynb`, then the PDF workflows, the Spark-log root-cause
360
+ analysis, and the SpreadsheetBench benchmarks.
361
+ - [CHANGELOG.md](CHANGELOG.md): release history.
362
+
363
+ ## Develop
364
+
365
+ ```bash
366
+ git clone https://github.com/pawarbi/fabric-rlm-core.git
367
+ cd fabric-rlm-core
368
+ pip install -e ".[dev]"
369
+ pytest -q
370
+ ```
371
+
372
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow.
373
+
374
+ ## Acknowledgments
375
+
376
+ This project stands on the work of others, and it matters to say so:
377
+
378
+ - The Recursive Language Model paradigm comes from the paper
379
+ [Recursive Language Models](https://arxiv.org/abs/2512.24601) by Alex L.
380
+ Zhang, Tim Kraska, and Omar Khattab (MIT CSAIL), which showed that letting a
381
+ model programmatically examine and recursively query its own prompt beats
382
+ stuffing everything into context.
383
+ - [DSPy](https://github.com/stanfordnlp/dspy) provides the RLM predictor and
384
+ the interpreter protocol this library plugs into, and `dspy.LM` powers every
385
+ model backend here.
386
+ - [Predict-RLM](https://github.com/Trampoline-AI/predict-rlm) by Trampoline AI,
387
+ a production-focused RLM runtime built on DSPy signatures, inspired the
388
+ direction of this project.
389
+
390
+ ## License
391
+
392
+ MIT. See [LICENSE](LICENSE).