ipinb 0.1.0__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 (278) hide show
  1. ipinb-0.1.0/BREAKING_CHANGES.md +1844 -0
  2. ipinb-0.1.0/CHANGELOG.md +248 -0
  3. ipinb-0.1.0/LICENSE +21 -0
  4. ipinb-0.1.0/MANIFEST.in +12 -0
  5. ipinb-0.1.0/PKG-INFO +438 -0
  6. ipinb-0.1.0/README.md +375 -0
  7. ipinb-0.1.0/docs/architecture.md +423 -0
  8. ipinb-0.1.0/docs/autopilot/ipi-mcp-redesign.md +1154 -0
  9. ipinb-0.1.0/docs/autopilot/notebook-service-log.md +98 -0
  10. ipinb-0.1.0/docs/autopilot/notebook-service-plan.md +102 -0
  11. ipinb-0.1.0/docs/autopilot/performance-log.md +154 -0
  12. ipinb-0.1.0/docs/autopilot/performance-plan.md +160 -0
  13. ipinb-0.1.0/docs/autopilot/performance-results.md +188 -0
  14. ipinb-0.1.0/docs/autopilot/reduce-output-tokens-log.md +280 -0
  15. ipinb-0.1.0/docs/autopilot/reduce-output-tokens-plan.md +94 -0
  16. ipinb-0.1.0/docs/debugging.md +36 -0
  17. ipinb-0.1.0/docs/execution-and-results.md +34 -0
  18. ipinb-0.1.0/docs/harness-shell-history.md +132 -0
  19. ipinb-0.1.0/docs/kernel-environments.md +46 -0
  20. ipinb-0.1.0/docs/notebook-publishing.md +55 -0
  21. ipinb-0.1.0/docs/parallel-execution.md +40 -0
  22. ipinb-0.1.0/docs/releasing.md +53 -0
  23. ipinb-0.1.0/docs/rich-output.md +40 -0
  24. ipinb-0.1.0/docs/validation/compact-output-blocks-2026-09-13.md +30 -0
  25. ipinb-0.1.0/docs/validation/execution-api-2026-09-10.md +89 -0
  26. ipinb-0.1.0/docs/validation/fastmcp4-2026-09-13.md +33 -0
  27. ipinb-0.1.0/docs/your-first-extension.md +885 -0
  28. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/SKILL.md +130 -0
  29. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/references/authoring-guide.md +885 -0
  30. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/references/evaluations.md +119 -0
  31. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/references/source-map.md +154 -0
  32. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/references/testing-and-troubleshooting.md +250 -0
  33. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/templates/installed-plugin/pyproject.toml +22 -0
  34. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/templates/installed-plugin/src/example_ipi_plugin/__init__.py +47 -0
  35. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/templates/installed-plugin/tests/test_plugin.py +54 -0
  36. ipinb-0.1.0/ipi/skills/developing-ipi-plugins/templates/project-plugin.py +58 -0
  37. ipinb-0.1.0/ipi/skills/notebook/SKILL.md +24 -0
  38. ipinb-0.1.0/ipi/skills/notebook/references/debugging.md +36 -0
  39. ipinb-0.1.0/ipi/skills/notebook/references/execution-and-results.md +34 -0
  40. ipinb-0.1.0/ipi/skills/notebook/references/kernel-environments.md +46 -0
  41. ipinb-0.1.0/ipi/skills/notebook/references/notebook-publishing.md +55 -0
  42. ipinb-0.1.0/ipi/skills/notebook/references/parallel-execution.md +40 -0
  43. ipinb-0.1.0/ipi/skills/notebook/references/rich-output.md +40 -0
  44. ipinb-0.1.0/ipi/skills/notebook-http/SKILL.md +8 -0
  45. ipinb-0.1.0/jupyterlab-extension/package-lock.json +8386 -0
  46. ipinb-0.1.0/jupyterlab-extension/package.json +47 -0
  47. ipinb-0.1.0/jupyterlab-extension/src/cell_chrome.ts +434 -0
  48. ipinb-0.1.0/jupyterlab-extension/src/index.ts +24 -0
  49. ipinb-0.1.0/jupyterlab-extension/src/intent.ts +1033 -0
  50. ipinb-0.1.0/jupyterlab-extension/src/metadata.ts +297 -0
  51. ipinb-0.1.0/jupyterlab-extension/src/trace_chrome.ts +897 -0
  52. ipinb-0.1.0/jupyterlab-extension/style/index.css +554 -0
  53. ipinb-0.1.0/jupyterlab-extension/tests/index.spec.ts +1800 -0
  54. ipinb-0.1.0/jupyterlab-extension/tests/setup.ts +19 -0
  55. ipinb-0.1.0/jupyterlab-extension/tsconfig.json +17 -0
  56. ipinb-0.1.0/jupyterlab-extension/tsconfig.test.json +15 -0
  57. ipinb-0.1.0/jupyterlab-extension/vitest.config.ts +8 -0
  58. ipinb-0.1.0/pyproject.toml +176 -0
  59. ipinb-0.1.0/setup.cfg +4 -0
  60. ipinb-0.1.0/src/ipi/__init__.py +5 -0
  61. ipinb-0.1.0/src/ipi/_ansi.py +274 -0
  62. ipinb-0.1.0/src/ipi/_dependency_metadata.py +149 -0
  63. ipinb-0.1.0/src/ipi/_exception_groups.py +12 -0
  64. ipinb-0.1.0/src/ipi/_git_provenance.py +122 -0
  65. ipinb-0.1.0/src/ipi/_kernel_argv.py +25 -0
  66. ipinb-0.1.0/src/ipi/_kernel_launcher.py +56 -0
  67. ipinb-0.1.0/src/ipi/_launch_gate.py +29 -0
  68. ipinb-0.1.0/src/ipi/_markdown.py +14 -0
  69. ipinb-0.1.0/src/ipi/_model_output.py +287 -0
  70. ipinb-0.1.0/src/ipi/_names.py +16 -0
  71. ipinb-0.1.0/src/ipi/_notebook_mirror.py +284 -0
  72. ipinb-0.1.0/src/ipi/_owner_watchdog.py +44 -0
  73. ipinb-0.1.0/src/ipi/_parent_process.py +39 -0
  74. ipinb-0.1.0/src/ipi/_platform_support.py +30 -0
  75. ipinb-0.1.0/src/ipi/_self_wheel.py +139 -0
  76. ipinb-0.1.0/src/ipi/_timing.py +34 -0
  77. ipinb-0.1.0/src/ipi/_toml.py +67 -0
  78. ipinb-0.1.0/src/ipi/_transport/__init__.py +41 -0
  79. ipinb-0.1.0/src/ipi/_transport/address.py +190 -0
  80. ipinb-0.1.0/src/ipi/_transport/artifacts.py +23 -0
  81. ipinb-0.1.0/src/ipi/_transport/contract.py +125 -0
  82. ipinb-0.1.0/src/ipi/_transport/local.py +268 -0
  83. ipinb-0.1.0/src/ipi/_transport/master_owner.py +98 -0
  84. ipinb-0.1.0/src/ipi/_transport/owned_directory.py +163 -0
  85. ipinb-0.1.0/src/ipi/_transport/portable_uv.py +158 -0
  86. ipinb-0.1.0/src/ipi/_transport/ssh.py +1816 -0
  87. ipinb-0.1.0/src/ipi/_transport/staging.py +135 -0
  88. ipinb-0.1.0/src/ipi/_transport/supervisor.py +817 -0
  89. ipinb-0.1.0/src/ipi/_transport/supervisor_protocol.py +8 -0
  90. ipinb-0.1.0/src/ipi/_ynotebook_ops.py +235 -0
  91. ipinb-0.1.0/src/ipi/cell_export.py +478 -0
  92. ipinb-0.1.0/src/ipi/config.py +376 -0
  93. ipinb-0.1.0/src/ipi/execution_backend.py +305 -0
  94. ipinb-0.1.0/src/ipi/execution_models.py +193 -0
  95. ipinb-0.1.0/src/ipi/formatting.py +699 -0
  96. ipinb-0.1.0/src/ipi/jupyter_server.py +207 -0
  97. ipinb-0.1.0/src/ipi/kernel.py +7252 -0
  98. ipinb-0.1.0/src/ipi/labextensions/@ipi/notebook-extension/package.json +52 -0
  99. ipinb-0.1.0/src/ipi/labextensions/@ipi/notebook-extension/static/585.5ce41466a93651634fda.js +1 -0
  100. ipinb-0.1.0/src/ipi/labextensions/@ipi/notebook-extension/static/665.bfdd2e9a96724c3cb559.js +1 -0
  101. ipinb-0.1.0/src/ipi/labextensions/@ipi/notebook-extension/static/remoteEntry.e0b57e0b0817ceee899e.js +1 -0
  102. ipinb-0.1.0/src/ipi/labextensions/@ipi/notebook-extension/static/style.js +4 -0
  103. ipinb-0.1.0/src/ipi/labextensions/@ipi/notebook-extension/static/third-party-licenses.json +16 -0
  104. ipinb-0.1.0/src/ipi/notebook_publisher.py +646 -0
  105. ipinb-0.1.0/src/ipi/notebook_trace.py +47 -0
  106. ipinb-0.1.0/src/ipi/plugins/__init__.py +47 -0
  107. ipinb-0.1.0/src/ipi/plugins/_model.py +1119 -0
  108. ipinb-0.1.0/src/ipi/plugins/api.py +568 -0
  109. ipinb-0.1.0/src/ipi/plugins/builtins/__init__.py +47 -0
  110. ipinb-0.1.0/src/ipi/plugins/builtins/_agents_md_impl.py +100 -0
  111. ipinb-0.1.0/src/ipi/plugins/builtins/_image_outputs_impl.py +176 -0
  112. ipinb-0.1.0/src/ipi/plugins/builtins/agents_md.py +25 -0
  113. ipinb-0.1.0/src/ipi/plugins/builtins/autoreload.py +26 -0
  114. ipinb-0.1.0/src/ipi/plugins/builtins/dataframe_display.py +21 -0
  115. ipinb-0.1.0/src/ipi/plugins/builtins/http_user_agent.py +65 -0
  116. ipinb-0.1.0/src/ipi/plugins/builtins/image_outputs.py +41 -0
  117. ipinb-0.1.0/src/ipi/plugins/builtins/import_guidance.py +55 -0
  118. ipinb-0.1.0/src/ipi/plugins/builtins/pdb.py +1029 -0
  119. ipinb-0.1.0/src/ipi/plugins/builtins/pip_guidance.py +193 -0
  120. ipinb-0.1.0/src/ipi/plugins/builtins/project_import.py +28 -0
  121. ipinb-0.1.0/src/ipi/plugins/builtins/shell_history.py +152 -0
  122. ipinb-0.1.0/src/ipi/plugins/catalog.py +387 -0
  123. ipinb-0.1.0/src/ipi/plugins/discovery.py +1058 -0
  124. ipinb-0.1.0/src/ipi/plugins/kernel.py +661 -0
  125. ipinb-0.1.0/src/ipi/plugins/py.typed +0 -0
  126. ipinb-0.1.0/src/ipi/plugins/server_deps.py +372 -0
  127. ipinb-0.1.0/src/ipi/py.typed +1 -0
  128. ipinb-0.1.0/src/ipi/records/__init__.py +433 -0
  129. ipinb-0.1.0/src/ipi/records/py.typed +1 -0
  130. ipinb-0.1.0/src/ipi/redaction.py +136 -0
  131. ipinb-0.1.0/src/ipi/references.py +84 -0
  132. ipinb-0.1.0/src/ipi/resource_registry.py +1386 -0
  133. ipinb-0.1.0/src/ipi/resources/notebook_http_skill.md +8 -0
  134. ipinb-0.1.0/src/ipi/resources/notebook_skill.md +24 -0
  135. ipinb-0.1.0/src/ipi/resources/references/debugging.md +36 -0
  136. ipinb-0.1.0/src/ipi/resources/references/execution-and-results.md +34 -0
  137. ipinb-0.1.0/src/ipi/resources/references/kernel-environments.md +46 -0
  138. ipinb-0.1.0/src/ipi/resources/references/notebook-publishing.md +55 -0
  139. ipinb-0.1.0/src/ipi/resources/references/parallel-execution.md +40 -0
  140. ipinb-0.1.0/src/ipi/resources/references/rich-output.md +40 -0
  141. ipinb-0.1.0/src/ipi/runtime/__init__.py +94 -0
  142. ipinb-0.1.0/src/ipi/runtime/_subprocess.py +233 -0
  143. ipinb-0.1.0/src/ipi/runtime/audit.py +81 -0
  144. ipinb-0.1.0/src/ipi/runtime/bootstrap.py +65 -0
  145. ipinb-0.1.0/src/ipi/runtime/cell_types.py +228 -0
  146. ipinb-0.1.0/src/ipi/runtime/concurrent.py +312 -0
  147. ipinb-0.1.0/src/ipi/runtime/context.py +29 -0
  148. ipinb-0.1.0/src/ipi/runtime/debugger.py +587 -0
  149. ipinb-0.1.0/src/ipi/runtime/environment.py +72 -0
  150. ipinb-0.1.0/src/ipi/runtime/formatters.py +78 -0
  151. ipinb-0.1.0/src/ipi/runtime/magics.py +412 -0
  152. ipinb-0.1.0/src/ipi/runtime/path_context.py +268 -0
  153. ipinb-0.1.0/src/ipi/runtime/project.py +74 -0
  154. ipinb-0.1.0/src/ipi/runtime/py.typed +0 -0
  155. ipinb-0.1.0/src/ipi/runtime/replay.py +80 -0
  156. ipinb-0.1.0/src/ipi/runtime/shell_history.py +458 -0
  157. ipinb-0.1.0/src/ipi/runtime/state_delta.py +55 -0
  158. ipinb-0.1.0/src/ipi/server/__init__.py +1 -0
  159. ipinb-0.1.0/src/ipi/server/__main__.py +747 -0
  160. ipinb-0.1.0/src/ipi/server/builtins/__init__.py +46 -0
  161. ipinb-0.1.0/src/ipi/server/builtins/harness_context.py +246 -0
  162. ipinb-0.1.0/src/ipi/server/builtins/inline_python_guidance.py +191 -0
  163. ipinb-0.1.0/src/ipi/server/builtins/session_trace.py +436 -0
  164. ipinb-0.1.0/src/ipi/server/builtins/shell_forwarding.py +194 -0
  165. ipinb-0.1.0/src/ipi/server/builtins/shell_history.py +236 -0
  166. ipinb-0.1.0/src/ipi/server/builtins/view_image.py +174 -0
  167. ipinb-0.1.0/src/ipi/server/execution_content.py +75 -0
  168. ipinb-0.1.0/src/ipi/server/execution_server.py +663 -0
  169. ipinb-0.1.0/src/ipi/server/harness.py +839 -0
  170. ipinb-0.1.0/src/ipi/server/harness_environment.py +172 -0
  171. ipinb-0.1.0/src/ipi/server/harness_transport.py +105 -0
  172. ipinb-0.1.0/src/ipi/server/integration.py +176 -0
  173. ipinb-0.1.0/src/ipi/server/narration_doctor.py +340 -0
  174. ipinb-0.1.0/src/ipi/server/notebook_channel.py +278 -0
  175. ipinb-0.1.0/src/ipi/server/plugin_resolver.py +296 -0
  176. ipinb-0.1.0/src/ipi/server/plugin_tools.py +208 -0
  177. ipinb-0.1.0/src/ipi/server/py.typed +1 -0
  178. ipinb-0.1.0/src/ipi/server/rollout_narration.py +190 -0
  179. ipinb-0.1.0/src/ipi/server/server.py +204 -0
  180. ipinb-0.1.0/src/ipi/session.py +373 -0
  181. ipinb-0.1.0/src/ipi/tunnel.py +352 -0
  182. ipinb-0.1.0/src/ipinb.egg-info/PKG-INFO +438 -0
  183. ipinb-0.1.0/src/ipinb.egg-info/SOURCES.txt +276 -0
  184. ipinb-0.1.0/src/ipinb.egg-info/dependency_links.txt +1 -0
  185. ipinb-0.1.0/src/ipinb.egg-info/entry_points.txt +2 -0
  186. ipinb-0.1.0/src/ipinb.egg-info/requires.txt +49 -0
  187. ipinb-0.1.0/src/ipinb.egg-info/top_level.txt +1 -0
  188. ipinb-0.1.0/tests/core/test_ansi.py +95 -0
  189. ipinb-0.1.0/tests/core/test_build_info.py +114 -0
  190. ipinb-0.1.0/tests/core/test_config.py +392 -0
  191. ipinb-0.1.0/tests/core/test_context_runtime.py +211 -0
  192. ipinb-0.1.0/tests/core/test_credential_boundaries.py +108 -0
  193. ipinb-0.1.0/tests/core/test_credential_redaction.py +131 -0
  194. ipinb-0.1.0/tests/core/test_debugger.py +804 -0
  195. ipinb-0.1.0/tests/core/test_editable_requirements.py +172 -0
  196. ipinb-0.1.0/tests/core/test_iopub_reducer.py +523 -0
  197. ipinb-0.1.0/tests/core/test_jupyter_server.py +31 -0
  198. ipinb-0.1.0/tests/core/test_kernel.py +3381 -0
  199. ipinb-0.1.0/tests/core/test_kernel_address.py +154 -0
  200. ipinb-0.1.0/tests/core/test_kernel_regressions.py +695 -0
  201. ipinb-0.1.0/tests/core/test_late_iopub.py +406 -0
  202. ipinb-0.1.0/tests/core/test_notebook_mirror.py +410 -0
  203. ipinb-0.1.0/tests/core/test_notebook_mirror_writer.py +315 -0
  204. ipinb-0.1.0/tests/core/test_notebook_publisher.py +307 -0
  205. ipinb-0.1.0/tests/core/test_notebook_publisher_e2e.py +150 -0
  206. ipinb-0.1.0/tests/core/test_output_formatting.py +616 -0
  207. ipinb-0.1.0/tests/core/test_output_noise.py +181 -0
  208. ipinb-0.1.0/tests/core/test_owner_watchdog.py +40 -0
  209. ipinb-0.1.0/tests/core/test_packaging_namespace.py +38 -0
  210. ipinb-0.1.0/tests/core/test_platform_support.py +39 -0
  211. ipinb-0.1.0/tests/core/test_plugin_kernel_integration.py +154 -0
  212. ipinb-0.1.0/tests/core/test_portable_uv.py +143 -0
  213. ipinb-0.1.0/tests/core/test_references.py +80 -0
  214. ipinb-0.1.0/tests/core/test_self_wheel.py +104 -0
  215. ipinb-0.1.0/tests/core/test_session.py +573 -0
  216. ipinb-0.1.0/tests/core/test_smoke.py +15 -0
  217. ipinb-0.1.0/tests/core/test_ssh_transport.py +1605 -0
  218. ipinb-0.1.0/tests/core/test_timing.py +48 -0
  219. ipinb-0.1.0/tests/core/test_toml.py +59 -0
  220. ipinb-0.1.0/tests/core/test_transport_contract.py +593 -0
  221. ipinb-0.1.0/tests/core/test_transport_staging.py +71 -0
  222. ipinb-0.1.0/tests/core/test_tunnel.py +226 -0
  223. ipinb-0.1.0/tests/release_smoke.py +77 -0
  224. ipinb-0.1.0/tests/runtime/test_bootstrap.py +115 -0
  225. ipinb-0.1.0/tests/runtime/test_builtin_agents_md.py +171 -0
  226. ipinb-0.1.0/tests/runtime/test_builtin_autoreload.py +29 -0
  227. ipinb-0.1.0/tests/runtime/test_builtin_dataframe_display.py +74 -0
  228. ipinb-0.1.0/tests/runtime/test_builtin_http_user_agent.py +131 -0
  229. ipinb-0.1.0/tests/runtime/test_builtin_image_outputs.py +231 -0
  230. ipinb-0.1.0/tests/runtime/test_builtin_import_guidance.py +144 -0
  231. ipinb-0.1.0/tests/runtime/test_builtin_pdb.py +266 -0
  232. ipinb-0.1.0/tests/runtime/test_builtin_pip_guidance.py +117 -0
  233. ipinb-0.1.0/tests/runtime/test_builtin_project_import.py +54 -0
  234. ipinb-0.1.0/tests/runtime/test_builtin_shell_history.py +161 -0
  235. ipinb-0.1.0/tests/runtime/test_context.py +38 -0
  236. ipinb-0.1.0/tests/runtime/test_environment.py +43 -0
  237. ipinb-0.1.0/tests/runtime/test_formatters.py +43 -0
  238. ipinb-0.1.0/tests/runtime/test_import_path.py +43 -0
  239. ipinb-0.1.0/tests/runtime/test_magics.py +234 -0
  240. ipinb-0.1.0/tests/runtime/test_model_output.py +89 -0
  241. ipinb-0.1.0/tests/runtime/test_path_context.py +147 -0
  242. ipinb-0.1.0/tests/runtime/test_plugin_api.py +451 -0
  243. ipinb-0.1.0/tests/runtime/test_plugin_authoring_skill.py +315 -0
  244. ipinb-0.1.0/tests/runtime/test_plugin_catalog.py +477 -0
  245. ipinb-0.1.0/tests/runtime/test_plugin_discovery.py +1189 -0
  246. ipinb-0.1.0/tests/runtime/test_plugin_kernel.py +1104 -0
  247. ipinb-0.1.0/tests/runtime/test_plugin_server_deps.py +464 -0
  248. ipinb-0.1.0/tests/runtime/test_project.py +71 -0
  249. ipinb-0.1.0/tests/runtime/test_shell_history.py +214 -0
  250. ipinb-0.1.0/tests/runtime/test_smoke.py +76 -0
  251. ipinb-0.1.0/tests/server/conftest.py +31 -0
  252. ipinb-0.1.0/tests/server/test_builtin_harness_context.py +581 -0
  253. ipinb-0.1.0/tests/server/test_builtin_inline_python_guidance.py +269 -0
  254. ipinb-0.1.0/tests/server/test_builtin_shell_history.py +275 -0
  255. ipinb-0.1.0/tests/server/test_builtin_view_image.py +184 -0
  256. ipinb-0.1.0/tests/server/test_harness_bridge.py +1305 -0
  257. ipinb-0.1.0/tests/server/test_harness_environment.py +85 -0
  258. ipinb-0.1.0/tests/server/test_integration.py +140 -0
  259. ipinb-0.1.0/tests/server/test_main.py +1270 -0
  260. ipinb-0.1.0/tests/server/test_multi_project_plugins.py +91 -0
  261. ipinb-0.1.0/tests/server/test_narration_doctor.py +140 -0
  262. ipinb-0.1.0/tests/server/test_notebook_channel.py +215 -0
  263. ipinb-0.1.0/tests/server/test_plugin_resolver.py +145 -0
  264. ipinb-0.1.0/tests/server/test_plugin_server_integration.py +53 -0
  265. ipinb-0.1.0/tests/server/test_plugin_tools.py +251 -0
  266. ipinb-0.1.0/tests/server/test_rollout_narration.py +219 -0
  267. ipinb-0.1.0/tests/server/test_server.py +261 -0
  268. ipinb-0.1.0/tests/server/test_session_trace.py +531 -0
  269. ipinb-0.1.0/tests/server/test_shell_forwarding.py +275 -0
  270. ipinb-0.1.0/tests/test_cell_export.py +344 -0
  271. ipinb-0.1.0/tests/test_consolidated_tools.py +233 -0
  272. ipinb-0.1.0/tests/test_debugger.py +715 -0
  273. ipinb-0.1.0/tests/test_execution_content_errors.py +44 -0
  274. ipinb-0.1.0/tests/test_execution_registry.py +1061 -0
  275. ipinb-0.1.0/tests/test_execution_transports.py +286 -0
  276. ipinb-0.1.0/tests/test_namespace.py +47 -0
  277. ipinb-0.1.0/tests/test_package_boundaries.py +311 -0
  278. ipinb-0.1.0/tests/test_resource_registry.py +609 -0
@@ -0,0 +1,1844 @@
1
+ # Breaking Changes
2
+
3
+ ## 2026-10-03: PyPI distribution is `ipinb`
4
+
5
+ IPi is published to PyPI as `ipinb` starting with version 0.1.0. Python imports, the `ipi` command, configuration keys, plugin entry-point groups, and notebook metadata stay unchanged. The PyPI project named `ipi` belongs to unrelated software.
6
+
7
+ **Migration:** Replace package requirements such as `ipi[mcp] @ git+...` with `ipinb[mcp]` or a pinned `ipinb[mcp]==0.1.0`. Git installations use the new requirement name with the same repository URL. Reinstall native integrations with `uvx --isolated --from 'ipinb[mcp]' ipi install --provider codex --replace` after reviewing the existing configuration. Kernel overlays, version reporting, remote wheel staging, and notebook publisher wheels now resolve `ipinb`.
8
+
9
+ ## 2026-10-03: Optional asynchronous Codex startup
10
+
11
+ `install-hooks --async-startup` is an opt-in Codex path for hosts that accept messages while IPi prepares. SessionStart and SubagentStart forwarding runs in the background with a positive 300-second bridge readiness wait. Existing synchronous installation remains the default. Requires typed-agent-hooks 0.2.4 or newer. No migration is needed unless choosing asynchronous startup.
12
+
13
+ ## 2026-09-25: Portable native host installation
14
+
15
+ `ipi install` now configures MCP startup and forwarding hooks together for
16
+ Codex and Claude Code. Project scope is the default; `--scope user` selects
17
+ personal configuration. Existing `install-hooks` remains available when MCP
18
+ startup is managed separately. No running server or kernel is restarted.
19
+
20
+ The installer refuses to overwrite a different `ipi` MCP entry unless
21
+ `--replace` is explicit. Review that entry before replacing it. Local/editable
22
+ IPi installations require an explicit portable `--requirement`; Git and index
23
+ installs derive a pinned requirement from installed metadata.
24
+
25
+ ## 2026-09-24: Concise Markdown submission receipts
26
+
27
+ - `markdown` now returns only `{"kind":"markdown","cell_id":"..."}` for new submissions and identical retries. It no longer repeats the submitted `source` or its metadata in the write response.
28
+ - Call `read_cell(cell_id)` when the retained Markdown source, origin, creation time, request ID, or revision is needed. Large sources remain available through the resource URI returned by `read_cell`.
29
+
30
+ ## 2026-09-24: Native Claude provenance and narration
31
+
32
+ - Claude MCP calls now use the exact `claudecode/toolUseId` metadata and native hook `prompt_id` to retain their originating session and prompt. Prompt groups are explicitly named `prompt:<id>`; Claude's distinct display turn UUID is not substituted for its prompt ID. Subagent origins remain separate from the parent's trace.
33
+ - Kernel environments expose `AGENT_SESSION_PROVIDER` and `AGENT_SESSION_ID` for either harness. Codex retains `CODEX_THREAD_ID`; Claude clears inherited Codex attribution. Use the neutral pair in shared tooling.
34
+ - Claude `MessageDisplay` batches become notebook narration, with retry-safe assembly and no duplicate Stop final message. Retained execution cells now have a stable prompt parent, including the first cell before a prompt hook could be mirrored. No tool-call migration is required.
35
+ - Requires `typed-agent-hooks==0.2.2`. The redundant root `CLAUDE.md` link is removed; Claude Code 2.1.277 or newer reads `AGENTS.md` natively.
36
+
37
+ ## 2026-09-21: Lean Python cell reads
38
+
39
+ - Remove `include_source` from every `read_cell` call. The argument is no longer accepted. Python replies always contain state and output without `source`, `origin`, `created_at`, or `request_id`, matching the former `include_source=False` behavior.
40
+ - These fields remain stored internally. Retrieve retained Python source through the MCP resource `ipi://cells/{cell_id}/source` when needed. Markdown reads still include source and metadata. Observation windows, caller-owned cursors, cancellation, and output artifact metadata are unchanged.
41
+
42
+ ## Per-cell replay exports
43
+
44
+ - Completed Python cells expose `files` when `read_cell(..., include_artifact_metadata=True)` is requested: raw `input.py` and dependency-selected PEP 723 `replay.py`. These are additive; the existing `source` JSON resource and lean polling are unchanged.
45
+ - File resource bodies are Python text. Clients may read their MCP URIs or request host paths with `include_artifact_metadata=True`. The replay's diagnostics describe limitations; export failures never turn a successful cell into a failed execution. No migration is required.
46
+
47
+ ## Per-execution debugger (feature/per-cell-debugger)
48
+
49
+ - Adds `ipdb(kernel_id, command, cell_id=None, yield_time_ms=1000, request_id=None)`. Kernel identity is always required; cell identity additionally targets a particular pause or failed execution. This does not restore the retired `pdb` tool or its implicit active target.
50
+ - Unhandled Python exceptions finish as before, with an optional debugger inspection hint. `breakpoint()` and configured source breakpoints can now pause a live execution for stepping. `exceptions raised` opts into stops before exception handling; `exceptions off` restores normal handling.
51
+ - A new ordinary execution to the same kernel discards existing postmortem inspection and aborts existing live pauses, allowing cleanup before admitting the new work. Already-running siblings are unaffected. Deliberately configured breakpoints persist until cleared or the kernel closes.
52
+ - Debugger commands may yield with a durable `result_uri`. Read that resource or the cell transcript to recover completion; do not repeat a yielded command. Notebook debugger transcripts remain attached to their original cells.
53
+
54
+ ## 2026-09-15: Consolidated notebook tools and one execution runtime
55
+
56
+ - Replace `wait(cell_id, ...)` with `read_cell(cell_id, yield_time_ms=10000, ...)`. `read_cell` defaults to an immediate read (`yield_time_ms=0`); Markdown never waits. The original source-inclusion switch was removed on 2026-09-21 as described above. Cursor and immutable artifact semantics are unchanged.
57
+ - Replace `show(cell_id, caption)` and `publish_notebook(kernel_id)` with `publish(kernel_id, cells={cell_id: caption})`. The optional map highlights terminal Python cells in the full live notebook; it does not filter or rerun cells. All selected cells must belong to that kernel. Null captions highlight without text; omitted cells retain their existing highlights. Publishing without a map leaves highlights unchanged and supports empty notebooks. Publication creates or repairs the live URL and retains the kernel until close or owner exit.
58
+ - Bash remains available but is disabled by default. Set `IPI_MCP_ENABLE_BASH_TOOL=1` before server startup to expose it. Remove `tools.bash` from configuration; the environment variable is the only opt-in. Remove the obsolete `tools.auto_create_kernel_for_python` field; use explicit kernel IDs and `server.prewarm_kernel` for optional startup provisioning.
59
+ - Remove `request_id` from `list_kernels` and `list_cells`; those arguments were ignored. Retry keys remain supported on creation, execution, and Markdown submission.
60
+ - The old `RuntimeManager`, `ToolRuntime`, implicit-kernel dispatchers, kernel-pool/worker lifecycle, logical-session registry, and skill-reading tool helpers are removed. Internal consumers and hook routing use the explicit `ExecutionRuntime`/`Registry` path. Direct importers of those internal modules must migrate. The current kernel implementation, transports, archival records, publication, and plugin APIs remain supported.
61
+
62
+
63
+ This document records user-visible breaking changes made during the MCP-first
64
+ redesign. Entries describe the old contract, the replacement, the reason, and
65
+ the required migration.
66
+
67
+ ## Unreleased MCP-First Redesign
68
+
69
+ ### Compact cell IDs, cursors, and artifact references
70
+
71
+ New cell IDs use one persisted registry-wide counter: `c1`, `c2`, ..., shared by Python, Bash, markdown, and browser executions across all kernels. Allocation never reuses a handle after close, expiry, failed acceptance, or reopening the same registry. Retained IDs are not renamed. Like kernel IDs, cell IDs are scoped to their originating registry; an independent registry can issue the same IDs. Cell lookup tools still take `cell_id` alone.
72
+
73
+ Output cursors now have the form `c1:17`; kernel-list cursors use `k:50`, and cell-list cursors use `k0:50`. They remain bound to their resource and page type. Treat cursors as opaque: pass the returned `next_cursor` unchanged. Previously retained cursors using `output:`, `kernels:`, or `cells:` prefixes must be discarded; omit the cursor to replay from the beginning and obtain a new one.
74
+
75
+ Large source and event references now contain only `uri` and `bytes` by default. Read the URI through MCP resources to retrieve the full JSON body. Call `read_cell(cell_id=..., include_artifact_metadata=True)` when filesystem paths, full SHA-256 checksums, and MIME types are needed. Consumers that previously accessed `artifact`, `sha256`, or `mime_type` directly must opt into metadata or use the resource URI. Full content hashes and immutable artifact files remain intact internally; authentication and upstream protocol IDs are unchanged.
76
+
77
+ ### Compact ordered output blocks
78
+
79
+ Execution results expose `output.blocks` instead of `output.events`. Adjacent stdout or stderr writes become one `{"type":"stdout","text":"..."}` or `{"type":"stderr","text":"..."}` block. Stream blocks omit transport sequence numbers and the nested content wrapper. Rich outputs, control events, and large-event artifact references retain their event identity and content. Update programmatic consumers to the block schema; the MCP output schema describes both variants.
80
+
81
+ The immutable event log and caller-owned cursors are unchanged. A page cursor advances over the original events consumed, independently of the number of blocks returned. Response limits apply to compact blocks; stream ordering, exact text, replay, and image delivery are preserved.
82
+
83
+ ### Structured-only execution results, compact kernel IDs, and FastMCP 4
84
+
85
+ Execution tools return their JSON payload once in `structuredContent`. The duplicate JSON text block is removed; `content` is empty unless it carries images. The MCP output schema describes the result, and the host's standard MCP handling applies. Image MIME payloads are retained as artifacts instead of also embedding their bytes in structured output.
86
+
87
+ Kernel IDs are unpadded registry-local counters: `k0`, `k1`, ..., `k11`. Allocation persists in the registry database and does not recycle IDs after close, expiry, or owner restart. An independent registry begins at `k0`; IDs are not globally unique and must be used with their originating registry. Existing retained IDs are not renamed. The subsequent cell-ID change is described above.
88
+
89
+ The MCP extra now pins FastMCP 4.0.3 and MCP SDK 2.2.0. Reinstall the extra to upgrade the environment. Python protocol-model attributes now use snake_case, and request metadata is a dictionary; JSON wire aliases remain governed by MCP. Codex itself requires no changes.
90
+
91
+ ### Explicit kernels and concurrent retained executions
92
+
93
+ The stdio and HTTP MCP APIs now share one explicit resource contract. `ipython`, `markdown`, publication, Bash, and plugin tools require `kernel_id`; observation and interruption require `cell_id`. Replace `switch_kernel` and `list_active_kernels` with explicit IDs and `list_kernels`. Replace `kill_session` with `close_kernel` for each kernel whose state you intend to discard. HTTP no longer accepts a logical `session_id`.
94
+
95
+ Independent submissions to one kernel overlap by default and share globals. Observe terminal success before submitting dependent code. `yield_time_ms=0` always returns a handle. Observation cancellation leaves accepted work running; use explicit best-effort interruption for cancellation. Output cursors belong to each reader and can replay retained output. Optional `request_id` deduplicates identical accepted create, Python, Bash, and markdown operations; reuse with another payload is rejected.
96
+
97
+ Live notebook publication remains executable and receives later updates. Closing its kernel or exiting the server ends the endpoint. Published kernels are exempt from idle expiry. Retained records and retry keys survive under the registry policy, while process restart truthfully marks unfinished work and live state lost.
98
+
99
+ The old global PDB MCP plugin is not exposed by this release. Its behavior is incompatible with independent concurrent cells; the optional per-cell debugger validation gate must pass before it is advertised again. Dependency upgrades outside the validated ipykernel/IPython pins fail setup explicitly until the local concurrency repair is revalidated.
100
+
101
+
102
+ ### Codex Apps and web search move out of IPi
103
+
104
+ **Status:** Implemented.
105
+
106
+ **Change:** The `ipi.codex_apps` built-in, `ipi_codex_tools` generated package, authenticated host bridge, connector file staging, and Codex-backed `web_search` helper are removed.
107
+
108
+ **Why:** Connected services and general web search are independent agent tools, not notebook runtime responsibilities. Keeping their authentication, discovery, transport, code generation, and file handling inside IPi coupled notebook startup and remote-kernel lifecycle to unrelated services.
109
+
110
+ **Migration:** Install the standalone `codex-apps` and `web-search` toolfuncs. Import them through `import toolfuncs as tools`, then call flat generated operations such as `tools.codex_apps.google_drive_search(...)` or `tools.web_search.search(...)`. Run `codex-apps login` once to authenticate its dedicated ChatGPT account; `web-search` uses the normal active `codex login` profile.
111
+
112
+ ### Script-only path imports are delegated to toolfuncs
113
+
114
+ **Status:** Implemented.
115
+
116
+ **Change:** `ipi.import_path` is now the public `toolfuncs.import_path` function. It imports one local `.py` file or extensionless script and derives the module name from the filename. Optional PEP 723 requirements are installed into the running kernel before import; `dependency_installer=` may replace the default pip operation with a callable accepting `list[str]` and returning `None`. IPi's package, project, distribution-artifact, fsspec URL, `module=`, and `storage_options=` import paths are removed.
117
+
118
+ **Why:** Importing a standalone script and preparing its optional inline dependencies is the reusable operation shared with toolfuncs. Package acquisition and arbitrary project installation made IPi own a second, much broader source loader that no current toolfunc contract requires.
119
+
120
+ **Migration:** Keep calls that pass one local script path. Remove `module=` and `storage_options=`. Install packages, projects, wheels, and source distributions through the environment manager, then use normal Python imports. Materialize remote scripts locally before calling `import_path`. Pass `dependency_installer=` only when the caller must replace installation into the current kernel environment.
121
+
122
+ ### Native harness sessions replace IPi artifact IDs in model-facing provenance
123
+
124
+ **Status:** Implemented.
125
+
126
+ **Change:** Harness grounding now emits the exact native Codex or Claude Code session identity in an `agent-session` block. Stateful runtime output and notebook publication no longer expose the durable IPi artifact-directory ID as a standalone `session` attribute. They expose only the explicitly labeled `IPi artifact root` path. Stateless HTTP-family transports still return their required short-lived routing handle as `logical-session-id` instead of the generic `session` attribute.
127
+
128
+ **Why:** Durable IPi artifact IDs such as `8212394626483155999-558398bc` identify `.agents/sessions/...` storage, not Codex or Claude history. Advertising that value as a generic session caused agents to record it as native session provenance, which cannot be resolved by harness-history tools.
129
+
130
+ **Migration:** Use the `agent-session` block's `provider` and `id` attributes for note provenance and harness-history lookup. Use `IPi artifact root` to inspect notebook artifacts; an ID embedded in that path is storage metadata. Stateless clients must read `logical-session-id` from `runtime-state` and continue passing it unchanged as the `session_id` tool argument.
131
+
132
+ ### Notebook frontend dependencies are explicitly shared
133
+
134
+ **Status:** Implemented.
135
+
136
+ **Change:** Strict configuration now accepts `publication.shared_dependencies`, a list of PEP 508 requirements installed in both each matching kernel and its lazily created Notebook 7 publisher. A caller-supplied `additional_dependencies` requirement for the same distribution wins on both sides. Plotly is no longer a built-in member of the `publish` extra.
137
+
138
+ **Why:** Packages such as Plotly and anywidget contain browser-side JupyterLab code as well as kernel-side Python code. Installing them in only one of IPi's isolated environments either cannot create the output or cannot render it. Declaring the shared set once makes that two-environment requirement explicit without inspecting `%pip`, mutating live publishers, or hard-coding optional visualization libraries into IPi.
139
+
140
+ **Migration:** Move Plotly, anywidget, and any other distribution whose JupyterLab extension is required from `kernel.additional_dependencies` to `publication.shared_dependencies`. Keep packages built on a generic shared provider, such as ipymolstar on anywidget, in `kernel.additional_dependencies`. Recreate the kernel and publication after changing or upgrading a shared provider; ordinary live kernel package installs need no publication restart.
141
+
142
+ ### Runtime-only state uses temporary storage and notebook publication dependencies are lazy
143
+
144
+ **Status:** Implemented.
145
+
146
+ **Change:** The generated `ipi_codex_tools` project, Codex Apps bridge staging, connector-file staging, Jupyter configuration, Jupyter runtime data, IPython configuration, collaboration state, and exact-running-IPi publication wheel now live in process-owned operating-system temporary directories. Codex Apps uses one authenticated loopback TCP server for local and remote kernels; it no longer creates request, response, tombstone, heartbeat, or stale-root-sweep files. `generated_package_dir` and `stale_root_ttl_s` are removed and now fail directly when present.
147
+
148
+ Codex configuration and auth are loaded once per bridge lifetime, outside the event loop. An explicit inventory refresh reloads them. Durable auth updates still use `fsync`, but generated packages, connector staging, and other disposable files do not. Repeated durability requests for the same notebook artifact are coalesced to one pending flush, plus at most one follow-up when a new generation arrives during an active flush.
149
+
150
+ The base distribution now includes `nbformat`, which core notebook persistence requires. Notebook 7, Jupyter collaboration, YDoc, PyCRDT, and Tornado are in the `publish` extra instead of the MCP host extra. The first `publish_notebook` call builds one process-cached wheel from the exact running `ipi` package and starts the publisher with `uv run --isolated --no-project --no-config --with "ipi[publish] @ file://..."`. Concurrent first publications share that build. The child inherits `UV_CACHE_DIR`, but all Jupyter state directories are redirected into the publication's private temporary root.
151
+
152
+ The default local-plugin server-dependency cache now uses this precedence: `IPI_SERVER_DEPS_CACHE`, then `$UV_CACHE_DIR/ipi/server-deps`, then `$XDG_CACHE_HOME/ipi/server-deps`, then `~/.cache/ipi/server-deps`. Slow runtime boundaries emit value-free warnings at one second or longer, naming only a fixed operation, fixed phase, and elapsed milliseconds.
153
+
154
+ Every IPi-managed local or SSH kernel now sets `HistoryManager.hist_file=:memory:`. The IPi notebook and session log remain the durable execution record; kernels no longer create or share `~/.ipython/profile_default/history.sqlite`. A recorded kernel-start failure raises one internal `KernelLaunchError` instead of returning content that can be mistaken for a live kernel. Failed prewarm state is discarded, automatic execution stops after that one failed launch, and cancellation uses an operation-local launch-abort signal that does not poison the next attempt. Cancelled launch ownership is retained until the worker has closed any unpublished child and transport state.
155
+
156
+ **Why:** Runtime-only metadata was doing small-file creation, polling, sweeping, SQLite locking, and synchronous durability work on user homes and shared filesystems. On Lustre and NFS, metadata latency dominates despite abundant CPU and RAM. Temporary local state, in-memory kernel history, and a single socket remove that filesystem from the request path, while the UV cache remains durable because reusing resolved dependencies is valuable. Treating a failed launch as ordinary content could also make one `ipython` call start a second kernel and wait through the readiness timeout twice.
157
+
158
+ **Migration:** Delete `generated_package_dir` and `stale_root_ttl_s` from every `ipi.codex_apps` plugin configuration. Set `UV_CACHE_DIR` to node-local storage, or set `IPI_SERVER_DEPS_CACHE` explicitly, when the default home or XDG cache is remote. Keep `uv` available for the first notebook publication; a cold publish cache may require dependency network access. Install `ipi[publish]` explicitly only when a preinstalled publisher environment is useful. After changing Codex config or auth in a running host, call the generated tools refresh or restart IPi to reload it immediately. Code that called IPython's cross-process persistent history must use the IPi notebook or session artifacts instead. Callers must treat a failed `create_kernel` response as a tool error rather than parsing a normal content result.
159
+
160
+ ### Structured turn traces replace narration Markdown cells
161
+
162
+ **Status:** Implemented.
163
+
164
+ **Change:** The `ipi.reasoning_forwarding` built-in and `ipi-reasoning` Markdown cells are removed. `ipi.session_trace` records each user prompt in a structured raw `session-turn` anchor and adds chronological `session-activity` children for the associated agent messages and Codex reasoning summaries. Only immediately consecutive reasoning updates share an activity. Portable tools without a richer notebook cell use a stable `tool-activity` child keyed within its turn. Every top-level member receives a notebook-wide `activity_index`; late work is inserted at its turn's tail. Executions, authored Markdown, host-shell records, and suspension transcripts name their exact parent with `metadata.ipi.parent_id`. The Notebook 7 extension presents this flat notebook log as nested turn, user, agent, activity, and execution disclosures, and renders prompt and agent text as untrusted Markdown through Jupyter's rendermime registry. Every raw trace source remains a readable plain-text fallback.
165
+
166
+ **Why:** One Markdown cell per narration sweep consumed excessive vertical space and made forwarded agent text look like deliberate notebook prose. A turn is the useful reading unit, but a single mutable cell per turn cannot preserve the actual chronology between narration, executions, and tools. A flat cell graph with explicit parent IDs and one total activity order preserves chronology and notebook interoperability while letting the extension render the hierarchy without reparenting Jupyter widgets.
167
+
168
+ **Migration:** Remove `ipi.reasoning_forwarding` from any built-in plugin overrides and use `ipi.session_trace`. Consumers that inspect notebooks should find raw cells with `metadata.ipi.kind` equal to `session-turn`, `session-activity`, or `tool-activity`; group them by `session_id` and `turn_id`, and resolve `parent_id` by the notebook cell model ID. Prompt, reasoning, and message records retain `metadata.ipi.items` entries with `role`, `kind`, and `text`. The former single-cell `ipi-session-trace` representation is not supported.
169
+
170
+ ### Explicit per-kernel publication replaces the global notebook service
171
+
172
+ **Status:** Implemented.
173
+
174
+ **Change:** `publish_notebook` now lazily publishes one existing notebook and
175
+ returns its complete tokenized Cloudflare quick-tunnel URL. Stateful mode takes
176
+ `kernel_id`; stateless mode takes `session_id`. A successful publication starts
177
+ one attached Notebook 7 child and one tunnel, retains the kernel past kernel and
178
+ logical-session idle expiry, and lasts until the kernel is killed or its owning
179
+ IPi process exits. The `ipi notebook-service` command, global dashboard,
180
+ per-user rendezvous, registration client/API, symlink farm, GC and
181
+ re-registration machinery are removed.
182
+
183
+ The older automatic per-kernel Jupyter sidecar design is also gone. The entire
184
+ `[jupyter]` configuration table is removed and now fails validation; live
185
+ Notebook 7 processes exist only after an explicit publication call.
186
+
187
+ **Why:** Notebook sharing is sparse and explicit. Kernel ownership already
188
+ provides the necessary lifetime boundary, so a machine-wide discovery and
189
+ registration system made every launch and deployment more complex without
190
+ improving the selected-notebook workflow.
191
+
192
+ **Migration:** Delete user services and wrappers that launch
193
+ `ipi notebook-service`, including dashboard URL lookup scripts. Remove any
194
+ service runtime-directory configuration and every `[jupyter]` table. Call
195
+ `publish_notebook(kernel_id="kN")` or
196
+ `publish_notebook(session_id="sNNNNNNNNN")` when a notebook must be shared, and
197
+ treat the returned URL as a bearer credential. Install `cloudflared`; the tool
198
+ fails rather than returning a localhost fallback when no public tunnel can be
199
+ created.
200
+
201
+ ### Session termination uses `kill_session` in every transport
202
+
203
+ **Status:** Implemented.
204
+
205
+ **Change:** `kill_session` is the terminal session lifecycle tool in every
206
+ transport. In stateful stdio, `kill_session()` cancels active work and prewarm,
207
+ closes every kernel, and permanently ends the implicit notebook session; later
208
+ notebook calls fail. In stateless HTTP-family transports,
209
+ `kill_session(session_id)` removes and closes that logical session and
210
+ invalidates its ID. The stateless-only `close_session` tool is removed.
211
+
212
+ **Why:** Session termination is destructive and permanent in both ownership
213
+ modes. One explicit verb and one terminal contract avoid a transport-specific
214
+ alias that described the same lifecycle less precisely.
215
+
216
+ **Migration:** Replace `close_session(session_id=...)` with
217
+ `kill_session(session_id=...)` for stateless clients. Stateful clients may call
218
+ `kill_session()` only as their final notebook operation.
219
+
220
+ ### Harness hook configuration is code-first
221
+
222
+ **Status:** Implemented.
223
+
224
+ **Change:** IPi now delegates hook configuration directly to `typed_agent_hooks.fastmcp.ForwardingHooks`. The removed TAH hookset manifests, compiler, loader, and global `typed-agent-hooks` CLI are no longer part of IPi's installation path. Forwarding commands invoke the dedicated `tah-fastmcp-forward` Cyclopts function and carry the stable `--managed-app ipi` ownership marker.
225
+
226
+ **Why:** The executable TAH application now owns its dependency environment, provider metadata, installation behavior, and Python API in one code-first definition. Keeping IPi's parallel hookset construction would restore the duplicate configuration model that TAH removed.
227
+
228
+ **Migration:** Remove existing IPi hook handlers containing `--hookset-name ipi`, then run `ipi install-hooks --provider codex --scope <project|user>` and `ipi install-hooks --provider claude_code --scope <project|user>` as applicable. Restart the harness after reinstalling. Existing entries are deliberately not interpreted as the new format.
229
+
230
+ ### Kernel idle reaping defaults to one hour and published kernels are retained
231
+
232
+ **Status:** Implemented.
233
+
234
+ **Change:** `server.kernel_idle_timeout_s` defaults to `3600`. An explicitly
235
+ published kernel is exempt from that reaper, and its stateless manager is exempt
236
+ from logical-session TTL expiry, until explicit kill or IPi process exit.
237
+
238
+ **Why:** Ordinary forgotten kernels should release resources, while a notebook
239
+ that was deliberately shared must remain usable for the publication lifetime.
240
+
241
+ **Migration:** Set `kernel_idle_timeout_s = 0` under `[server]` to disable idle
242
+ reaping globally. Prefer `publish_notebook` when only a specific shared kernel
243
+ needs retention.
244
+
245
+ ### Flat string results and typed model-facing handles
246
+
247
+ **Status:** Implemented.
248
+
249
+ **Change:** Agent-facing tools retain their string return contract, but their text now uses flat, non-nested, product-neutral XML-like delimiters. Compact facts that describe a block, such as its identity, kind, state, source, counts, and flags, live in opening-tag attributes. What the block says or delivers remains in Markdown or verbatim bodies, including explanations, source, logs, errors, and raw output. Small inventories are Markdown tables. The delimiters are not a strict XML contract. Opening and closing tags provide the block boundary without a separate length attribute. IPi reparses only the generated top-level blocks needed for incremental output delivery. Runtime inspection consolidates the former scalar `status`, `session-id`, `kernel-state`, and related blocks into one `runtime-state` block with descriptive attributes and a body for its substantive details.
250
+
251
+ Model-facing cells retain `cN`; live kernels now use `kN`, including
252
+ `switch_kernel(kernel_id="k1")`; cell-local outputs use `oN`; stateless HTTP
253
+ sessions use `s` plus nine random decimal digits. New session artifacts use the
254
+ same handles in `kernels/kN/outputs/cN/g-<hex>/oN-*`. Durable session directory
255
+ IDs, `g-<hex>` generation IDs, Jupyter IDs, and other external or internal
256
+ protocol IDs are unchanged.
257
+
258
+ **Why:** Flat semantic boundaries make mixed instructions, Markdown, and raw
259
+ payloads easy for an agent to distinguish without turning prompts into a
260
+ machine data protocol. Short type prefixes disambiguate local handles while
261
+ retaining the measured token behavior of decimal counters and random suffixes.
262
+
263
+ **Migration:** Treat result tags as descriptive boundaries, not strict XML. Read facts that describe a block from its opening-tag attributes and read what the block says or delivers from its body. Replace tab-record parsing with the corresponding top-level blocks and Markdown fields. Pass returned cell, kernel, and session handles unchanged; replace numeric `switch_kernel` arguments with `kN` strings. Follow returned artifact paths rather than constructing the former padded path components.
264
+
265
+ ### Python execution uses fixed yielding and addressed cell controls
266
+
267
+ **Status:** Implemented.
268
+
269
+ **Change:** `ipython` is now `ipython(code)` and uses a fixed 5-second initial observation window. A still-running cell yields with `cell_id=cN` and status `yielded`. `wait` is now `wait(cell_id, yield_time_ms=10_000)`, `interrupt` requires `cell_id`, and `show` is now `show(cell_id, caption=None)`. Stateless forms additionally require `session_id`. `show` promotes a terminal whole cell only; output references and the `last` sentinel are removed. Explicit interruption returns status `interrupted` without a synthetic `TimeoutError`. Execution records expose their count, state, duration, cell handle, exit code, and post-mortem flag as `status` attributes; any changed cwd, `sys.path`, or environment values remain in the body.
270
+
271
+ **Why:** These names and continuation semantics match the agent harness's cooperative-yield UX. Explicit cell addressing prevents stale continuation calls from controlling the wrong execution, and whole-cell presentation removes an unnecessary output-addressing protocol.
272
+
273
+ **Migration:** Remove `timeout_s` from every `ipython` call. Read the returned `cell_id`, then call `wait(cell_id=..., yield_time_ms=...)` or `interrupt(cell_id=...)`. Replace `show(target=...)`, `cell_ref`, and `show_ref` handling with `show(cell_id=...)` and `cell_id`. Do not pass `last` or composite values such as `c7/o2`.
274
+
275
+ ### Object-driven Agent Introspection Protocol and auto-context are removed
276
+
277
+ **Status:** Implemented.
278
+
279
+ **Change:** The Agent Introspection Protocol object model and the generic automatic context coordinator are deleted. IPi no longer inspects imports, new bindings, displayed values, or exceptions for `__agent_summary__`, `__agent_doc__`, resolver methods, synthesized dataclass or enum guidance, or inspection debt. The AIP tier model, well-known-object policy, suppression API, debugger suppression state, `ipi.auto_context` built-in, and public runtime enable/disable/status functions are removed. Generated Codex Apps modules no longer define `__agent_summary__`, and generated package schema 15 invalidates old stubs.
280
+
281
+ Structured context blocks remain, under neutral `ContextBlock` and `PersistedContextBlock` names, as the transport for explicitly authored semantic events. The built-in producers are AGENTS file discovery after path or cwd changes, written-image advice, `ModuleNotFoundError` guidance, and pip guidance. Each producer emits directly and owns its recurrence policy. Kernel cwd, `sys.path`, and environment deltas are captured by a separate private tracker. Host-side context providers, harness context, state-specific result notices, and session-history context-block deduplication are unchanged.
282
+
283
+ **Why:** Object inspection guessed relevance from ordinary Python activity and spent tokens on information already available through normal language knowledge and `help()`. It also coupled unrelated concerns: object metadata, exception scanning, context recurrence, plugin coordination, debugger behavior, and process-state tracking. Explicit semantic producers preserve the passive hints that carry hidden or local information while removing the speculative path and its policy machinery.
284
+
285
+ **Migration:** There is no compatibility layer or phased migration. Delete uses of the removed AIP dunders, resolver and formatting APIs, inspection registry, trigger scanners, auto-context registration and suppression APIs, and `ipi.auto_context` plugin ID. Use standard `help(...)` for object and generated-tool inspection. A plugin that owns genuinely hidden semantic information should emit a `ContextBlock` directly from the event where that information becomes relevant and should own its own recurrence rule.
286
+
287
+ ### Kernel execution locations are per-kernel local paths or SSH URIs
288
+
289
+ **Status:** Implemented; pending user acceptance.
290
+
291
+ **Change:** `create_kernel(cwd=...)` now selects an immutable execution
292
+ location for that kernel. An ordinary absolute path remains local;
293
+ `ssh://[user@]host[:port]/absolute/path` creates a kernel at that absolute path
294
+ through non-interactive system OpenSSH. One server can own local kernels and
295
+ kernels on multiple SSH targets. Each SSH kernel owns its private control
296
+ master, five host-loopback Jupyter forwards, target supervisor, approximately
297
+ 60-second owner lease, latest officially released portable `uv` verified with
298
+ its publisher checksum, and an exact staged build of the running IPi. Remote
299
+ projects require no host mirror. Remote kernels run built-in plugins only;
300
+ external project, user, and installed plugins are ignored with one redacted
301
+ warning and are not imported, executed, or staged.
302
+
303
+ Native Windows and WSL support, the `ipi-windows` and `ipi-remote` Jupyter
304
+ provisioners, path mapping, static command prefixes, Windows harness transport,
305
+ and every process-wide `IPI_KERNEL_*` selector have been removed. IPi now
306
+ supports Linux and macOS only. A removed variable fails startup rather than
307
+ being silently ignored.
308
+
309
+ **Why:** A process-wide launch prefix cannot represent mixed local/multi-host
310
+ kernels and does not own target Jupyter ports, authentication, staged runtime,
311
+ health, or cleanup. WSL path maps also required a host mirror and allowed host
312
+ plugin execution to be confused with target execution. The resource-owned
313
+ transport boundary makes target identity transactional and gives every acquired
314
+ host and target resource a bounded rollback and cleanup path. It is private but
315
+ is shaped for later native Docker and Kubernetes transports without treating
316
+ those systems as SSH command prefixes.
317
+
318
+ **Migration:** Remove all `IPI_KERNEL_*` variables and Windows/WSL provisioner
319
+ configuration. Run IPi on Linux or macOS. Keep local calls as
320
+ `create_kernel(cwd="/absolute/local/path")`; replace remote prefixes and path
321
+ maps with `create_kernel(cwd="ssh://user@host/absolute/target/path")`. Configure
322
+ aliases, keys, ports, `ProxyJump`, and host trust in OpenSSH, and verify the
323
+ connection works non-interactively before launch. Move required remote behavior
324
+ into built-ins or ordinary target project code; external IPi plugins do not run
325
+ for SSH kernels. Targets must provide `python3` 3.10 or newer for the isolated
326
+ session owner; the kernel environment itself continues to use the verified
327
+ portable `uv` and exact staged IPi build. The last reviewed pre-change lineage is
328
+ `origin/autopilot/ipi-http-session-ids@ae37c31502eb46c17e4df537f4f74babcccd49a2`.
329
+
330
+ ### HTTP logical session IDs shrink to nine digits behind a session limit
331
+
332
+ **Status:** Implemented.
333
+
334
+ **Change:** The logical `session_id` returned by stateless HTTP `create_kernel`
335
+ is now an opaque fixed-width 9-digit decimal string (previously 18 digits).
336
+ Allocation is unique-by-retry: a candidate that collides with a live session or
337
+ a leftover `.agents/sessions/` directory is redrawn, `Session.create` and
338
+ `SessionRegistry.register` raise the new `SessionIdCollisionError` for those
339
+ collisions (`register` previously raised `ValueError`), and the new
340
+ `server.stateless_max_sessions` config (default 256; `0` disables) makes
341
+ `create_kernel` fail with a clear error instead of launching a kernel once that
342
+ many sessions are live.
343
+
344
+ **Why:** The ID is repeated in every session-bound tool call. Nine decimal
345
+ digits cost three tokens bare and four inside JSON tool arguments under both
346
+ benchmark tokenizers, versus six and seven for 18 digits. The $9 \times
347
+ 10^8$-value space is sized for loud failure, not secrecy: at 100 live sessions
348
+ a stale ID hits a live one with probability about $10^{-7}$ per retry, so
349
+ expired or pre-restart IDs keep dying with `UnknownSessionError` instead of
350
+ aliasing another session. Authentication remains the HTTP token, never the ID.
351
+ The cap exists because an authorized client loop could otherwise fork-bomb the
352
+ host with kernel launches.
353
+
354
+ **Migration:** Treat session IDs as opaque strings of any width and retain the
355
+ exact value returned by `create_kernel`. Logical sessions never survive a
356
+ server restart, so there is no persisted-ID migration. Callers that caught
357
+ `ValueError` from `SessionRegistry.register` must catch
358
+ `SessionIdCollisionError`. Deployments expecting more than 256 concurrent
359
+ sessions must raise `server.stateless_max_sessions`.
360
+
361
+ ### Codex Apps tool results carry file bytes, and kernels name themselves to HTTP
362
+
363
+ **Status:** Implemented.
364
+
365
+ **Change:** A Codex Apps tool call that returns a file now performs a host-side
366
+ download during the call. The stored copy lands in a `files` directory beside
367
+ the endpoint's bridge spools, each file reference in the result gains an
368
+ `__ipi_file__` entry, and the kernel receives it as a `CodexAppFile` exposing
369
+ `path`, `read_bytes()`, and `mime_type`. A result holding at least one file is a
370
+ `CodexAppResult` with a `files` tuple, and renders when it holds a single image.
371
+ Both are `dict` subclasses, so every key the connector returned still indexes as
372
+ before. A non-text content block returned alongside `structuredContent` is no
373
+ longer discarded; it appears under `__ipi_content__`. Separately, kernels now
374
+ install a `urllib.request` opener that sends `ipi/<version>` instead of the
375
+ stdlib default User-Agent.
376
+
377
+ **Why:** Connectors return a file as a credentialed URL that expires in minutes,
378
+ never as bytes, and nothing fetched it. Notebook code had to, but a kernel
379
+ environment carries no HTTP client unless the project depends on one, so code
380
+ fell back to `urllib.request` — whose exact `Python-urllib/<version>`
381
+ User-Agent the bot rules in front of those file hosts reject outright, with a
382
+ 403 whose explanation is only in a response body `urlopen` raises away. The
383
+ result read as a permission error on the file. Two adjacent defects made the
384
+ same path worse: `structuredContent` silently dropped any content block beside
385
+ it, and content blocks were dumped in python mode, so a `ResourceLink` or
386
+ `EmbeddedResource` carried an `AnyUrl` that could not be serialized and failed
387
+ the whole call at the bridge with an unrelated-looking error.
388
+
389
+ **Migration:** None required for reading tool results. Code that downloaded
390
+ `download_url` itself still can — the URL is preserved — but should prefer
391
+ `read_bytes()`, which does not race the expiry. A failed fetch never fails the
392
+ call: `path` is `None` and `error` explains why, including the response body.
393
+ Files over 25 MiB are not stored. Set
394
+ `[plugins.config."ipi.http_user_agent"] user_agent` to choose a different
395
+ User-Agent, `""` to keep urllib's own, or disable `ipi.http_user_agent`
396
+ entirely; an explicit per-request header or a later `install_opener` already
397
+ takes precedence.
398
+
399
+ ### The stdio server shuts down on SIGTERM and Ctrl-C
400
+
401
+ **Status:** Implemented.
402
+
403
+ **Change:** The stdio entrypoint now installs its own SIGINT and SIGTERM
404
+ handlers and, once serving has ended, runs `atexit` hooks, flushes, and exits
405
+ the process directly. HTTP transports are untouched, since uvicorn installs its
406
+ own handlers.
407
+
408
+ **Why:** Two defects met on this path. FastMCP installs no SIGTERM handler, so
409
+ stdio ran under Python's default disposition and the normal end of a Codex or
410
+ Claude Code session killed the process outright, skipping the lifespan
411
+ `finally` and every `atexit` hook. Ctrl-C did reach Python, but `asyncio.Runner`
412
+ claimed SIGINT and cancelled the main task instead, and that cancellation never
413
+ completed, so the server hung until something force-killed it. Together these
414
+ meant no owned cleanup operation ran on either stop path, which is why
415
+ generated package roots accumulated. Even after cleanup was made to run, the
416
+ interpreter still could not exit: anyio's worker threads are not daemons and
417
+ park on an unbounded queue, so `threading._shutdown` waited on them forever.
418
+
419
+ **Migration:** None required. A stop signal now completes teardown and exits 0.
420
+ Anything embedding IPi that installs its own SIGINT or SIGTERM handler before
421
+ `main()` keeps it; IPi only claims a signal still on its default disposition.
422
+
423
+ ### Generated Codex Apps stubs document their parameters
424
+
425
+ **Status:** Implemented.
426
+
427
+ **Change:** Generated tool docstrings gain a Google-style `Args:` section built
428
+ from each JSON Schema property's `description`, declared choices, and `default`.
429
+ Signatures render `Literal[...]` for a declared `enum` or `const` when the value
430
+ list is small enough to stay readable, and fall back to the plain type
431
+ otherwise. Generated modules now begin with `from __future__ import
432
+ annotations`, the `_CodexAppTool` constructor takes `doc=` instead of
433
+ `description=`, and each module's `TOOLS` entry carries a `parameters` tuple.
434
+ `GENERATED_PACKAGE_SCHEMA` is bumped to 11. A failed tool call now appends what
435
+ the named parameter accepts, or a pointer to `help(tools.<app>.<tool>)` when no
436
+ known parameter is named.
437
+
438
+ **Why:** Codegen kept only the property name, type, and requiredness, so
439
+ everything an agent needed in order to choose a value was discarded. In the real
440
+ inventory 96% of properties carry a description and only 3% declare an `enum`,
441
+ so the values are usually stated in prose rather than machine-readable. Slack's
442
+ `response_format` is the case that motivated this: it advertises no `enum` while
443
+ the server enforces one, and agents that inspected the tool first still had to
444
+ guess. That guess failed 67 times across recorded sessions.
445
+
446
+ **Migration:** None required. The generated package is rebuilt on every host
447
+ process, schema `default`s are still never sent on the wire, and the rendered
448
+ annotations are display-only and unenforced.
449
+
450
+ ### Stale generated package roots are reclaimed
451
+
452
+ **Status:** Implemented.
453
+
454
+ **Superseded:** Generated package roots now use process-owned operating-system temporary storage, so this sweep, its heartbeat, and both related configuration keys have been removed. See “Runtime-only state uses temporary storage and notebook publication dependencies are lazy.”
455
+
456
+ **Change:** On startup each host process sweeps sibling generated-package roots
457
+ under its cache directory on a background thread, removing those whose owner is
458
+ gone and whose age exceeds `stale_root_ttl_s` (default 24 hours; `0` disables).
459
+ The owning process refreshes a `.alive` heartbeat so a live root is never
460
+ mistaken for an abandoned one. Setting `generated_package_dir` opts out of both
461
+ the sweep and the existing teardown, since the caller owns that directory.
462
+
463
+ **Why:** Roots are unique per host process and are removed at teardown, but a
464
+ stdio server killed with `SIGTERM` never runs that teardown, so nothing reclaimed
465
+ them. One observed cache had grown to 452 roots and 160 MB, 441 of them owned by
466
+ dead processes, with 409 byte-identical copies of a single module.
467
+
468
+ **Migration:** None required. To keep the previous behavior, set
469
+ `stale_root_ttl_s = 0` in the `ipi.codex_apps` plugin config.
470
+
471
+ ### Agent work cells collapse by default and `show` promotes
472
+
473
+ **Status:** Implemented.
474
+
475
+ **Change:** A new `show(cell_id, caption)` MCP tool promotes a completed cell
476
+ into the human's view. `ipython` results carry `cell_id=c7` on the status record
477
+ as the address `show` accepts. The bundled extension collapses agent work cells
478
+ to a one-line header and renders promoted material expanded.
479
+
480
+ **Why:** The notebook served the agent's scratch work and the human's reading at
481
+ once, with no way to tell them apart. Promotion is retroactive because a cell
482
+ becomes interesting when its result is surprising, which the agent cannot
483
+ predict before running it.
484
+
485
+ **Migration:** None required. `[jupyter] collapse_cells` still writes Jupyter's
486
+ own `collapsed` and `source_hidden` keys and remains off by default; the
487
+ frontend now collapses without it, which keeps those keys owned by the reader so
488
+ promotion never reverts a manual collapse.
489
+
490
+ ### `action_description` and `description_display` are removed
491
+
492
+ **Status:** Implemented.
493
+
494
+ **Change:** The optional `[tools] action_descriptions` mode, the
495
+ `action_description` argument on `ipython`, `bash`, and `pdb`, and the
496
+ `[jupyter] description_display` renderer choice are all gone. Cell metadata no
497
+ longer carries `description` or `description_display`, the `#### Intent:`
498
+ Markdown cell is no longer emitted, and PDB suspension transcripts no longer
499
+ show an `**Intent:**` line. The bundled Notebook 7 extension now always loads
500
+ rather than being selected by `description_display = "extension"`.
501
+
502
+ **Why:** The argument asked the agent to pay tokens on every call to restate
503
+ intent the model had already produced. IPi now records the agent's own
504
+ reasoning summaries and preambles in the structured turn trace
505
+ (`ipi.session_trace`), which is both cheaper and more faithful: there is no
506
+ reliable pairing between reasoning and calls, so a per-call field could never
507
+ represent what the agent was actually doing.
508
+
509
+ **Migration:** Remove `action_descriptions` from `[tools]` and
510
+ `description_display` from `[jupyter]` in `~/.ipi/config.toml` and any
511
+ `.ipi.toml`. Unknown config keys fail validation, so a stale file stops the
512
+ server at startup with the offending key named. Callers passing
513
+ `action_description` to a tool must drop it; extra arguments are rejected.
514
+ Reasoning forwarding requires `model_reasoning_summary` to be set in
515
+ `~/.codex/config.toml`, and is Codex-only.
516
+
517
+ ### Codex Apps startup refresh is a background kernel thread
518
+
519
+ **Status:** Implemented.
520
+
521
+ **Change:** The `refresh` bootstrap cell no longer blocks kernel readiness on
522
+ the inventory refresh (a real Codex backend round trip, ~0.5 s). The cell
523
+ imports the generated package, wires the bridge, installs the `tools` alias,
524
+ arms a settle event, and runs the refresh on a daemon thread. First attribute
525
+ access on an app module that is not materialized yet waits (bounded, 10 s)
526
+ for the armed refresh before failing, so `tools.<app>` still behaves as if
527
+ the refresh were synchronous when the inventory is genuinely needed.
528
+ `GENERATED_PACKAGE_SCHEMA` bumped to 10.
529
+
530
+ **Why:** Kernel creation (and therefore prewarm completion and the cold first
531
+ call) paid the network refresh serially even though the server's own startup
532
+ refresh freshens the same package concurrently.
533
+
534
+ **Migration:** Code that asserted on the bootstrap cell's source must match
535
+ `arm_startup_refresh` / `run_startup_refresh` instead of a synchronous
536
+ `refresh(silent=True, ...)` call.
537
+
538
+ ### `ipi.runtime.bootstrap(ip)` renamed to `bootstrap_shell(ip)`
539
+
540
+ **Status:** Implemented.
541
+
542
+ **Change:** The package-level convenience function `ipi.runtime.bootstrap`
543
+ collided with the `ipi.runtime.bootstrap` submodule: importing the submodule
544
+ rebinds the package attribute to the module object, which silently shadowed
545
+ the function once imports became lazy. The function is now
546
+ `ipi.runtime.bootstrap_shell`; `%load_ext ipi.runtime` is unaffected. The
547
+ heavy `ipi.runtime` members also import lazily now, so host-side code can use
548
+ light leaves such as `ipi.runtime.project` without paying for IPython.
549
+
550
+ **Migration:** Call `bootstrap_shell(ip)` (or `load_ipython_extension(ip)`).
551
+
552
+ ### On-disk notebook writes are coalesced and asynchronous without collaboration
553
+
554
+ **Status:** Implemented.
555
+
556
+ **Change:** Without the collaborative mirror (`jupyter.enabled = false`, or a
557
+ sidecar with `collaboration = false`), `notebook.ipynb` is no longer rewritten
558
+ synchronously on every cell mutation. A background saver persists the latest
559
+ notebook state, debounced to at most one full rewrite per ~200 ms burst, and
560
+ `Kernel.close()` still writes the authoritative final notebook synchronously.
561
+ The new `Kernel.flush_notebook()` is the read-back barrier for anything that
562
+ inspects the file mid-session. Cell artifacts (`input.py`, outputs,
563
+ `manifest.json`) and `session.jsonl` are unchanged: still written before the
564
+ tool result is returned. Relatedly, per-cell artifact fsyncs moved to a
565
+ background durability flusher earlier in this cycle; atomic tmp-write + rename
566
+ still happens on the response path.
567
+
568
+ **Why:** A full-notebook rewrite per mutation made warm call latency grow
569
+ linearly with session length (~13 ms at cell 25 to ~31 ms at cell 150 on the
570
+ benchmark machine). Collaboration mode already treated the on-disk file as
571
+ eventually consistent (the live room owns the document); this aligns the
572
+ non-collaborative path with the same contract and keeps per-call latency flat.
573
+
574
+ **Migration:** Tools or tests that read `notebook.ipynb` while the kernel is
575
+ alive must call `flush_notebook()` first (or accept up to ~200 ms of
576
+ staleness). Readers that only look after `close()` are unaffected.
577
+
578
+ ### Exception results are inline errors with an optional armed post-mortem
579
+
580
+ **Status:** Implemented.
581
+
582
+ **Change:** An uncaught exception in an `ipython` cell now publishes its
583
+ concise, chain-aware traceback as that cell's own error output at throw time,
584
+ exactly like plain IPython, and the tool result renders the cell as `err` with
585
+ a `post-mortem=armed` status marker plus a one-line optional `pdb` hint. The
586
+ previous shape suppressed the traceback from the cell output ("Traceback
587
+ omitted here"), framed the result as a debugger stop ("Kernel is suspended"
588
+ with `w`/`q`/`c` instructions and a `Location:`/`Exception:` suspension body),
589
+ and appended an internally-run `pdb w` stack section. Dismissal is now silent
590
+ and honest: the next `ipython` or `bash` call no longer prepends
591
+ "automatically aborted the pdb suspension before ...", and the failed cell
592
+ finalizes as `err` carrying the original exception instead of `aborted` (for
593
+ automatic dismissal and for a manual `pdb` `q` alike). `breakpoint()` stops
594
+ are unchanged: they keep the "Kernel is suspended" framing, the automatic
595
+ `pdb w` stack, and the explicit auto-quit prefix with `aborted` status.
596
+
597
+ **Why:** Agents reflexively answered every exception with `pdb("q")` even
598
+ though ignoring the suspension always worked, and the failing cell's notebook
599
+ record only received its traceback once the suspension resolved on the next
600
+ call. Traceback-in-the-throwing-cell parity removes the ceremony while the
601
+ armed post-mortem keeps live-state inspection one optional call away.
602
+
603
+ **Migration:** Consumers matching "Traceback omitted", the exception-pause
604
+ "Kernel is suspended" notice, or "automatically aborted the pdb suspension"
605
+ must update their patterns. The exception suspension record is now a bare
606
+ `suspension<TAB>ipi.pdb<TAB>exception<TAB>tool=pdb` line with no body, and the
607
+ inline traceback strips IPython infrastructure frames and elides long middles
608
+ (`... N frame(s) omitted ...`).
609
+
610
+ ### The stateful server prewarms the default kernel at startup
611
+
612
+ **Status:** Implemented.
613
+
614
+ **Change:** A stateful server now launches the default kernel in a background
615
+ task as soon as its lifespan starts, so the first `ipython` call usually
616
+ attaches to a warm kernel instead of paying kernel startup. The prewarm
617
+ respects `tools.auto_create_kernel_for_python`, never blocks or fails server
618
+ startup, and a failed prewarm logs a warning and falls back to the existing
619
+ lazy auto-create. The first-result `auto_created_kernel` wrapper now appears
620
+ only when that call actually created the kernel, and the auto-read notebook
621
+ skill rides the first execution result even when the kernel was prewarmed.
622
+
623
+ **Why:** The redesign makes bare `ipython` the whole default workflow; hiding
624
+ kernel startup behind server startup removes the last visible setup cost, and
625
+ the session-start harness context can advertise a kernel that already exists.
626
+
627
+ **Migration:** Set `[server] prewarm_kernel = false` to opt out (for example
628
+ on hosts where most sessions never run Python).
629
+
630
+ ### Auto-created kernels reuse the last kernel spec; reap notices target executions
631
+
632
+ **Status:** Implemented.
633
+
634
+ **Change:** When `ipython` or `bash` auto-creates a kernel because the previous
635
+ one died or was idle-reaped, the new kernel reuses the most recently created
636
+ or activated kernel's cwd, venv, and `additional_dependencies` instead of the
637
+ server-default cwd with no overlay. The idle-reap "state is gone" notice is
638
+ now delivered only into the next `create_kernel`, `ipython`, or `bash` result;
639
+ `state`, `list_active_kernels`, and `switch_kernel` no longer consume it, so a
640
+ read-only call between the reap and the next execution cannot swallow the
641
+ warning.
642
+
643
+ **Why:** Auto-create previously used a cwd frozen at first runtime creation
644
+ (often the server working directory), silently moving the session to a
645
+ different directory with no venv or dependency overlay after a reap. The
646
+ notice draining into whichever call arrived first meant the execution that
647
+ actually hit the empty kernel could see no warning at all.
648
+
649
+ **Migration:** None. To land in a different cwd after a reap, call
650
+ `create_kernel` explicitly, as before.
651
+
652
+ ### Written-image guidance no longer recommends an absent `view_image` tool
653
+
654
+ **Status:** Implemented.
655
+
656
+ **Change:** The `ipi.view_image` kernel plugin's post-cell context block
657
+ ("Images saved this cell. ...") now depends on whether the public `view_image`
658
+ MCP tool is actually registered. The server resolves the effective
659
+ `tools.view_image` value (transport default or explicit override) and injects
660
+ it into the kernel plugin's spec config at kernel launch. When the tool
661
+ exists, the guidance keeps recommending `view_image` with kernel-cwd-relative
662
+ paths; when it does not (the stdio default), the guidance lists absolute paths
663
+ and points at the client's own file/image reading instead.
664
+
665
+ **Why:** Stdio agents were told to "Use `view_image`" for a tool their client
666
+ does not have, which taught them to route figures through `savefig` plus
667
+ harness file reading. Relative paths were also only resolvable against the
668
+ kernel cwd, not the client cwd.
669
+
670
+ **Migration:** None. Anything matching the old guidance string must accept
671
+ both variants; the block's `source` (`path:written-images`) and `reason`
672
+ (`path-written`) are unchanged.
673
+
674
+ ### `%pip` no longer shells out through `$SHELL`
675
+
676
+ **Status:** Implemented.
677
+
678
+ **Change:** IPi registers its own `%pip` line magic that executes
679
+ `sys.executable -m pip ...` as a direct argv subprocess with piped output,
680
+ replacing IPython's stock magic, which runs the installer through
681
+ `$SHELL -c`. A failed install now raises `CalledProcessError`, so the cell
682
+ reports `err` instead of quietly printing a nonzero exit; the stock
683
+ "restart the kernel" note is printed only on success.
684
+
685
+ **Why:** Routing installs through the user's login shell let shell startup
686
+ hooks (fnm, nvm, direnv, ...) interleave their own errors into `%pip` output —
687
+ for example fnm's "Requested version ... is not currently installed" in any
688
+ repo with a `.node-version` — and made install output depend on per-machine
689
+ shell configuration.
690
+
691
+ **Migration:** None for normal use; `%pip install <pkg>` behaves the same but
692
+ with deterministic output. Anything that relied on shell syntax inside the
693
+ `%pip` line (environment prefixes, `&&` chains) must move to `%%bash` or a
694
+ plain cell.
695
+
696
+ ### Idle kernels are shut down after one hour by default
697
+
698
+ **Status:** Implemented.
699
+
700
+ **Change:** A kernel with no agent or browser executions for
701
+ `server.kernel_idle_timeout_s` seconds (default `3600`) is now closed
702
+ automatically; previously kernels lived until server shutdown. Executing,
703
+ suspended, and browser-busy kernels are never culled. The durable notebook and
704
+ output artifacts are saved as usual, the shutdown is recorded as a
705
+ `kernel`/`death` event with reason `idle_timeout`, and the next tool response
706
+ carries a notice. An open JupyterLab tab alone does not keep a kernel alive;
707
+ only executions reset the timer.
708
+
709
+ **Why:** Each live kernel holds an IPython child process and usually a Jupyter
710
+ sidecar process. Sessions left open for hours accumulated idle kernels that
711
+ wasted memory and CPU indefinitely.
712
+
713
+ **Migration:** To restore forever-lived kernels, set
714
+ `kernel_idle_timeout_s = 0` under `[server]` in `~/.ipi/config.toml` or
715
+ `<cwd>/.ipi.toml`, or pass `-c server.kernel_idle_timeout_s=0`. Raise or lower
716
+ the value to tune the horizon.
717
+
718
+ ### Action descriptions are opt-in
719
+
720
+ **Status:** Implemented.
721
+
722
+ **Change:** `ipython`, the optional `bash` tool, and the built-in `pdb` tool no
723
+ longer expose `action_description` by default. IPi injects an empty internal
724
+ description, does not render an Intent block for it, and otherwise keeps the
725
+ execution and debugger behavior unchanged. Set
726
+ `[tools] action_descriptions = true` before server startup to restore the
727
+ required, nonblank argument and its notebook intent metadata. This is a strict
728
+ boolean startup schema setting.
729
+
730
+ **Why:** Requiring a prose preamble for every notebook action adds tool-schema
731
+ and call overhead even when the code or debugger command already states the
732
+ operation clearly. Keeping intent opt-in preserves the auditable notebook
733
+ workflow for users who value it without imposing it on every client.
734
+
735
+ **Migration:** Remove `action_description` from default `ipython`, `bash`, and
736
+ `pdb` calls. To retain the former signatures and nonblank validation, add
737
+ `action_descriptions = true` under `[tools]` and restart the MCP server.
738
+
739
+ ### `view_image` availability follows the MCP transport
740
+
741
+ **Status:** Implemented.
742
+
743
+ **Change:** `[tools] view_image` is now a strict `bool | None` startup setting.
744
+ When omitted, `view_image` is absent from every stdio server and present on
745
+ every HTTP-family transport (`http`, `sse`, and `streamable-http`). Explicit
746
+ `true` or `false` overrides that transport default. Filtering removes only the
747
+ server-side MCP tool contribution; the `ipi.view_image` kernel plugin continues
748
+ to report image files written by cells. Stateless plugin schemas now put
749
+ `session_id` first, including `view_image(session_id, path)` and
750
+ `pdb(session_id, command)` with default action-description settings.
751
+
752
+ **Why:** Stdio clients can inspect local image artifacts through their native
753
+ filesystem/image surface, while HTTP clients need an MCP tool that reads from
754
+ the session kernel's filesystem. Transport-aware defaults avoid a duplicate
755
+ stdio tool without removing runtime image-output context.
756
+
757
+ **Migration:** Stdio clients that still need IPi's MCP image loader must set
758
+ `view_image = true` under `[tools]`. HTTP clients can omit the setting, or set
759
+ it to `false` to remove the tool. Treat stateless `session_id` as the first
760
+ schema field; MCP invocation remains named-argument based.
761
+
762
+ ### Model-facing MCP tool references use canonical bare names
763
+
764
+ **Status:** Implemented.
765
+
766
+ **Change:** Server instructions, runtime hints and errors, harness guidance, and
767
+ the local and HTTP notebook skills now refer to tools by their canonical names,
768
+ such as `ipython`, `create_kernel`, `wait`, and `pdb`. The model-facing alias
769
+ resolver and `IPI_MCP_TOOL_PREFIX` environment variable have been removed. The
770
+ registered FastMCP tool names were already bare and are unchanged.
771
+
772
+ **Why:** The `mcp__<alias>__<tool>` spelling is a client-specific presentation
773
+ detail. Repeating it in IPi-authored prose added noise, coupled the instructions
774
+ to one host naming convention, and could become incorrect when the configured
775
+ server alias differed from the client-visible alias.
776
+
777
+ **Migration:** Remove `IPI_MCP_TOOL_PREFIX` from launch environments and refresh
778
+ or reinstall any copied notebook skills. Integrations that inspect IPi-authored
779
+ guidance must expect canonical bare names. MCP clients remain responsible for
780
+ any namespace they add to transport-level tool identifiers.
781
+
782
+ ### Python execution tool renamed to `ipython`
783
+
784
+ **Status:** Implemented.
785
+
786
+ **Change:** The stateful and stateless Python/IPython execution MCP tool is now
787
+ registered as `ipython` (previously `python`). Both notebook skills, the
788
+ README, server instructions, and error/collision messages now reference the
789
+ new name. The optional `bash` tool and its `IPI_MCP_ENABLE_BASH_TOOL`/
790
+ `tools.bash` gating are unchanged.
791
+
792
+ **Why:** `ipython` more accurately reflects that the tool runs a persistent
793
+ IPython kernel, not just plain Python.
794
+
795
+ **Migration:** Any client-side permission allowlist, config, or automation
796
+ that references the `python` MCP tool name (e.g. `mcp__notebook__python`) must
797
+ be updated to `ipython`. Refresh or reinstall any copied notebook skills.
798
+
799
+ ### Jupyter Cloudflare tunnels are quick-tunnel only
800
+
801
+ **Status:** Implemented.
802
+
803
+ **Change:** `jupyter.tunnel = true` now launches only an accountless Cloudflare
804
+ quick tunnel. The `jupyter.tunnel_domain` and
805
+ `jupyter.tunnel_hostname_prefix` fields are removed and rejected. Jupyter waits
806
+ up to 30 seconds for `cloudflared` to publish the quick-tunnel URL so
807
+ `create_kernel` can return it immediately; failure stops the tunnel and falls
808
+ back to the local notebook URL with a diagnostic notice.
809
+
810
+ **Why:** Named Jupyter tunnels required Cloudflare login state, tunnel and DNS
811
+ provisioning, generated credentials, and custom-hostname configuration. Quick
812
+ tunnels need none of that setup. The previous asynchronous startup also returned
813
+ localhost before the quick-tunnel URL appeared a few seconds later.
814
+
815
+ **Migration:** Remove `tunnel_domain` and `tunnel_hostname_prefix` from every
816
+ `[jupyter]` table. Keep `jupyter.tunnel = true` to request a quick tunnel. MCP
817
+ HTTP named tunnels and the `--tunnel-domain` CLI option are unchanged.
818
+
819
+ ### HTTP logical session IDs use compact decimal strings
820
+
821
+ **Status:** Implemented.
822
+
823
+ **Change:** The logical `session_id` returned by stateless HTTP `create_kernel`
824
+ is now an opaque fixed-width 18-digit decimal string. The prior implementation
825
+ reused the 28-character timestamp-sortable durable-session directory ID.
826
+
827
+ **Why:** The logical ID is repeated in every later HTTP tool call. Measurements
828
+ over 10,000 samples with `o200k_base` and `cl100k_base` reduced its mean cost
829
+ from 12.56 tokens to exactly 6. The new $9 \times 10^{17}$-value random space
830
+ retains about 59.6 bits of entropy; even one million generated IDs have an
831
+ approximate birthday-collision probability of $5.6 \times 10^{-7}$.
832
+
833
+ **Migration:** Treat session IDs as opaque strings and retain the exact value
834
+ returned by `create_kernel`. Do not parse timestamps, UUID fragments, or numeric
835
+ meaning from either format. Logical sessions never survive an MCP server
836
+ restart, so there is no persisted-ID migration.
837
+
838
+ ### Parked execution control returns incremental output
839
+
840
+ **Status:** Implemented.
841
+
842
+ **Change:** After the initial Python result parks a running cell, each `wait`
843
+ and `interrupt` result now contains current status plus only output not already
844
+ returned. New output records include `update=new`; a growing stream uses
845
+ `update=append from_chars=N` and contains only its suffix. An unchanged poll has
846
+ no output record. Non-append changes emit `notice\tkind=output-reset` followed by
847
+ the complete current snapshot. Artifact paths continue to name complete output,
848
+ and canonical cell records remain full snapshots.
849
+
850
+ **Why:** Replaying all output accumulated so far on every poll made repeated
851
+ waits consume quadratic model tokens and obscured which output was actually new.
852
+
853
+ **Migration:** Clients that assemble a live view must retain earlier output,
854
+ add `update=new` records, append untruncated suffixes at the Unicode code-point
855
+ offset `from_chars=N`, and replace the assembled view after
856
+ `kind=output-reset`. A truncated suffix is only a preview; read its complete
857
+ `PATH` and replace that output when lossless assembly is required. Clients that
858
+ need a stateless complete snapshot should likewise read the referenced artifact
859
+ or current manifest instead of expecting every `wait` response to repeat prior
860
+ output.
861
+
862
+ ### AGENTS context hints use absolute paths
863
+
864
+ **Status:** Implemented.
865
+
866
+ **Change:** The `source` field emitted by the `ipi.agents_md` built-in now uses
867
+ the resolved absolute `AGENTS.md` path with portable forward slashes. It no
868
+ longer renders the path relative to a detected project or ancestor directory.
869
+
870
+ **Why:** Relative hints can contain several `..` components when applicable
871
+ instructions live above the nearest project root, making the model-visible path
872
+ hard to identify and resolve correctly.
873
+
874
+ **Migration:** Treat the text after `agents-md:` as an absolute path and open it
875
+ directly. Consumers must stop joining the hint to the kernel cwd or project
876
+ root.
877
+
878
+ ### Model-facing output no longer contains ANSI controls
879
+
880
+ **Status:** Implemented.
881
+
882
+ **Change:** MCP responses, live output callbacks, PDB results, and persisted
883
+ text artifacts remove both terminal control bytes and serialized spellings such
884
+ as `\x1b[31m`. Runtime bootstrap also undoes ipykernel's forced-color child
885
+ process environment. Raw notebook stream/error output retains explicitly
886
+ emitted controls so Jupyter can render them, but ordinary child CLI output is
887
+ no longer forced to use color.
888
+
889
+ **Why:** ANSI syntax expands further when MCP JSON is encoded, consumes model
890
+ tokens without adding information, and can make captured machine-readable
891
+ output invalid before the agent can parse it.
892
+
893
+ **Migration:** Do not depend on ANSI syntax in model-facing output or captured
894
+ subprocess data. Emit semantic text or a structured rich display instead. Code
895
+ that intentionally needs colored child-process output must opt in explicitly
896
+ for that subprocess and consume the resulting control sequences itself.
897
+
898
+ ### Execution descriptions become required action intent
899
+
900
+ **Status:** Implemented.
901
+
902
+ **Change:** Execution intent is now the first required argument of Python,
903
+ bash, and PDB. The public signatures are
904
+ `python(action_description, code, timeout_s=10)`,
905
+ `bash(action_description, cmd, timeout=None)`, and
906
+ `pdb(action_description, command)`. The former optional Python `description`
907
+ argument, internal Python `source` argument, and bash `command` argument have
908
+ been removed rather than aliased. Blank descriptions are rejected.
909
+
910
+ **Why:** The old Python description was effectively a no-op after the tool call.
911
+ Putting user-facing intent first makes the agent state what an execution is for
912
+ before composing it, and preserving that intent makes notebook history easier
913
+ to follow and audit.
914
+
915
+ **Migration:** Rename Python `description` to `action_description` and place it
916
+ before `code`; rename Python `source` payloads to `code`; rename bash `command`
917
+ to `cmd`; and add `action_description` before every bash and PDB command. The raw
918
+ tool-call arguments remain available in session records, while normalized intent
919
+ is stored on execution records and notebook metadata. Notebook 7 uses the
920
+ bundled intent-header extension by default. Set
921
+ `[jupyter] description_display = "markdown"` to stage an intent Markdown cell
922
+ before each agent code cell instead.
923
+
924
+ ### Python execution yields after 10 seconds by default
925
+
926
+ **Status:** Implemented.
927
+
928
+ **Change:** Omitting `python.timeout_s` now uses a 10-second cooperative yield
929
+ budget instead of inheriting the kernel's 1,800-second timeout. If the cell is
930
+ still running, IPi returns it as parked with partial output; the cell continues
931
+ until `wait` collects it or `interrupt` stops it. `timeout_s=0` parks
932
+ immediately. The MCP parameter is now a non-null integer greater than or equal
933
+ to zero.
934
+
935
+ **Why:** The prior default kept the MCP request occupied for up to 30 minutes,
936
+ so an agent that did not predict a slow call could not reach the control tools
937
+ that cooperative timeout was designed to expose.
938
+
939
+ **Migration:** Omit `timeout_s` for the new responsive default. Pass an explicit
940
+ larger value when one blocking call is intentional; `timeout_s=1800` recreates
941
+ the former wait. Replace explicit `null` with either omission or a non-negative
942
+ integer. Existing MCP processes must be restarted before they advertise and use
943
+ the new schema.
944
+
945
+ ### TUI and embedded LLM harness removal
946
+
947
+ **Status:** Implemented.
948
+
949
+ **Change:** The terminal UI and its `ipi.tui` package have been removed. The
950
+ root `ipi` command now starts the MCP server. The embedded agent loop,
951
+ `ipi.agent`, `ipi.client`, `ipi.core`, provider prompt assembly, diagnostics,
952
+ and client transport APIs have also been removed.
953
+
954
+ **Why:** These layers have no active TUI users, duplicate responsibilities
955
+ already owned by Codex and Claude Code, and force the MCP path through a much
956
+ larger compatibility architecture. Removing them makes the supported product
957
+ boundary explicit and allows the runtime and extension APIs to model only real
958
+ use cases.
959
+
960
+ **Migration:** Configure IPi as an MCP server through the `ipi` command. The
961
+ `ipi-mcp` command no longer exists. Existing users of the old TUI must remain on
962
+ an older release; there is no replacement embedded agent harness.
963
+
964
+ ### Harness-only configuration removal
965
+
966
+ **Status:** Implemented.
967
+
968
+ **Change:** The `[provider]`, `[display]`, and `[tui]` configuration sections
969
+ and their Python model types have been removed. IPi config now describes MCP
970
+ tools, Jupyter behavior, and extensions only. Removed sections are rejected as
971
+ invalid configuration.
972
+
973
+ **Why:** Provider selection, reasoning effort, display verbosity, and terminal
974
+ keybindings belonged to the deleted embedded harness. Accepting those settings
975
+ would imply behavior the MCP server cannot provide.
976
+
977
+ **Migration:** Remove those sections from user and project `.ipi.toml` files.
978
+ Configure models, reasoning, display, and keybindings in Codex or Claude Code.
979
+
980
+ ### Provider-era package removal
981
+
982
+ **Status:** Implemented.
983
+
984
+ **Change:** Every bundled `ipi-plugin-*` distribution has been removed. The
985
+ following provider-era capabilities were deleted rather than migrated:
986
+
987
+ - `ipi-plugin-provider-codex`
988
+ - `ipi-plugin-provider-generic`
989
+ - `ipi-plugin-compaction`
990
+ - `ipi-plugin-skills`
991
+ - `ipi-plugin-slash-lifecycle`
992
+ - `ipi-plugin-write-tool`
993
+ - `ipi-plugin-notebook-transcript`
994
+
995
+ Actively retained behavior, including AGENTS path hints, automatic context,
996
+ autoreload, project imports, dataframe formatting, image output tracking, PDB,
997
+ Codex Apps, and harness context, now ships as in-tree built-in plugins in the
998
+ single `ipi` distribution.
999
+
1000
+ **Why:** Codex and Claude Code already own provider calls, skills, slash
1001
+ commands, patch/write tools, conversation transcripts, and context compaction.
1002
+ Keeping duplicate implementations in IPi enlarged the dependency and extension
1003
+ surface without contributing to the MCP execution product.
1004
+
1005
+ **Migration:** Remove these package names from IPi configuration and launch
1006
+ requirements. Use the corresponding Codex or Claude Code harness capability.
1007
+ AGENTS instructions are discovered by the `ipi.agents_md` built-in, which emits
1008
+ file path hints for the harness to read rather than injecting full file
1009
+ contents. Executed notebook cells and outputs remain recorded by core;
1010
+ conversation messages are no longer mirrored into the notebook.
1011
+
1012
+ ### Extension API replacement
1013
+
1014
+ **Status:** Implemented.
1015
+
1016
+ **Change:** The provider/TUI-oriented `HostInitAPI` and its priority registries,
1017
+ string event namespace, tool-object/dispatcher pairs, provider registration,
1018
+ slash commands, record renderers, and agent control handle were replaced by
1019
+ plain plugin callbacks with separate server, per-kernel environment, and
1020
+ in-kernel scopes.
1021
+
1022
+ First-class MCP tools are registered only by bundled, installed,
1023
+ user-level, and startup-project plugins discovered before the MCP server starts.
1024
+ Project plugins discovered later through `create_kernel(cwd=...)` may customize
1025
+ that kernel and its environment, but cannot add a new MCP tool schema because
1026
+ Codex does not currently refresh its model tool registry after
1027
+ `tools/list_changed`.
1028
+
1029
+ **Why:** The current API combines unrelated process lifetimes and obsolete
1030
+ agent-harness concepts. Explicit scopes make ownership, failure behavior, and
1031
+ project isolation understandable while preserving the extensibility that is
1032
+ the product's central requirement.
1033
+
1034
+ **Migration:** Export one direct `plugin` value from each extension entry point
1035
+ or local file:
1036
+
1037
+ ```python
1038
+ from ipi.plugins import EnvironmentSetup, Plugin, ServerSetup, ToolContext
1039
+
1040
+
1041
+ async def inspect(ctx: ToolContext, name: str) -> str:
1042
+ return f"{ctx.environment}: {name}"
1043
+
1044
+
1045
+ def setup_server(setup: ServerSetup) -> None:
1046
+ setup.tool("inspect", inspect)
1047
+
1048
+
1049
+ def setup_environment(setup: EnvironmentSetup) -> None:
1050
+ setup.bootstrap("imports", "import ipi_codex_tools as tools")
1051
+ setup.require("example-dependency>=1")
1052
+
1053
+
1054
+ plugin = Plugin(server=setup_server, environment=setup_environment)
1055
+ ```
1056
+
1057
+ Installed distributions must expose `module:plugin` in the `ipi.extensions`
1058
+ entry-point group and already be present in the host environment. User plugins
1059
+ move to `~/.ipi/extensions/`; project plugins move to
1060
+ `<cwd>/.ipi/extensions/`. The old `.agents/extensions` loader, PEP 723
1061
+ sidecars, split `host.py`/`kernel.py` packages, priority values, and trust
1062
+ allowlist are not part of the replacement.
1063
+
1064
+ Configuration moves from `[extensions]` to:
1065
+
1066
+ ```toml
1067
+ [plugins]
1068
+ installed = ["my-ipi-plugin==1.2.3"]
1069
+ disabled = ["ipi.optional_feature"]
1070
+
1071
+ [plugins.config."my.plugin"]
1072
+ enabled = true
1073
+ ```
1074
+
1075
+ Configuration selects installed entry points but never installs a missing host
1076
+ package; add external distributions to the `uvx` launch environment. Tool
1077
+ extensions must be discoverable from the server startup project. Kernel-only
1078
+ project behavior remains independently discoverable for each
1079
+ `create_kernel(cwd)`.
1080
+
1081
+ ### Strict setup callbacks and threaded synchronous tools
1082
+
1083
+ **Status:** Implemented.
1084
+
1085
+ **Change:** `Plugin.server`, `Plugin.environment`, and `Plugin.kernel` callbacks
1086
+ must be synchronous and return `None`. Coroutine functions, async callable
1087
+ objects, decorated callbacks that return an awaitable, and any other non-`None`
1088
+ result fail setup with plugin and phase attribution. Synchronous plugin MCP
1089
+ tools now execute in a worker thread; async tools continue on the serving loop.
1090
+ Tool names must contain 1-128 ASCII letters, digits, underscores, hyphens, or
1091
+ dots. A tool must expose an inspectable signature whose first positional
1092
+ parameter is named `ctx`; public positional-only, `*args`, and `**kwargs`
1093
+ parameters are rejected because they do not define one object-shaped MCP input
1094
+ schema.
1095
+
1096
+ Every contributed tool invocation now participates in the connection's runtime
1097
+ operation owner. It is serialized with kernel control calls and shutdown. A
1098
+ cancelled synchronous tool remains owned until its worker exits. Direct calls
1099
+ such as `await ctx.session.cell(...)` are reentrant; child tasks spawned by a
1100
+ plugin are tracked and drained before that invocation or session shutdown can
1101
+ complete.
1102
+
1103
+ **Why:** An async setup callback previously produced an unawaited coroutine,
1104
+ contributed nothing, and still let startup succeed. A synchronous MCP tool ran
1105
+ inside IPi's async wrapper on the shared event loop, so one blocking file or
1106
+ remote operation stalled every HTTP session and harness event.
1107
+
1108
+ **Migration:** Define setup with ordinary `def` and do all registration before
1109
+ returning `None`. Give tools stable valid names and ordinary typed keyword
1110
+ parameters after `ctx`; replace variadic or positional-only public APIs with an
1111
+ explicit object schema. Put asynchronous behavior inside the registered async
1112
+ tool, handler, observer, or provider rather than making setup async. Make
1113
+ synchronous MCP tools thread-safe and avoid event-loop-local state, or convert
1114
+ them to `async def` and use nonblocking APIs. Do not use a plugin task as a way
1115
+ to detach notebook operations beyond the MCP call lifetime.
1116
+
1117
+ ### Recursively immutable plugin configuration
1118
+
1119
+ **Status:** Implemented.
1120
+
1121
+ **Change:** `EnvironmentSetup.config` and `KernelSetup.config` are isolated,
1122
+ recursively immutable views. Nested mappings are read-only, lists and tuples
1123
+ become tuples, sets become frozensets, and bytearrays become bytes.
1124
+
1125
+ **Why:** A top-level mapping proxy still allowed a plugin to mutate nested
1126
+ configuration shared with later callbacks. Setup is transactional only when its
1127
+ inputs cannot be changed behind the loader's back.
1128
+
1129
+ **Migration:** Treat configuration as `Mapping` and immutable sequences rather
1130
+ than concrete `dict` and `list` values. Make an explicit local copy inside the
1131
+ callback when an algorithm genuinely needs mutable working state.
1132
+
1133
+ ### Explicit HTTP logical sessions and per-kernel project ownership
1134
+
1135
+ **Status:** Implemented for the MCP runtime and new plugin system.
1136
+
1137
+ **Change:** Session ownership is selected by strict `server.session_mode`
1138
+ configuration. Stateful mode remains the stdio default and owns one implicit,
1139
+ multi-kernel runtime. Every HTTP transport requires stateless mode and uses
1140
+ FastMCP's request-stateless transport. In that mode `create_kernel` creates one
1141
+ logical session with one kernel and returns its `session_id`; every later
1142
+ session-bound core or plugin tool requires the explicit ID. Stateless mode adds
1143
+ `kill_session`, removes `switch_kernel` and `list_active_kernels`, and safely
1144
+ expires idle sessions after `server.stateless_session_idle_timeout_s`. Each
1145
+ kernel still owns the config and plugins resolved from its exact cwd. The
1146
+ optional `read_notebook_skill` tool is session-independent and returns the
1147
+ common notebook workflow composed with the stateless lifecycle in this mode.
1148
+
1149
+ **Why:** MCP clients may reconnect or create a new protocol session for every
1150
+ tool call. Keying notebook ownership by that transport detail silently replaced
1151
+ the active runtime, lost Python state, and made `list_active_kernels` appear
1152
+ empty. Logical ownership must survive transport churn and remain visible in the
1153
+ tool contract.
1154
+
1155
+ **Migration:** Remove `--session-isolation` and `--no-session-isolation`. Set
1156
+ `[server] session_mode = "stateless"` for HTTP, or pass
1157
+ `-c 'server.session_mode="stateless"'`. Call `create_kernel` first, retain its
1158
+ logical ID, pass `session_id` to every later tool call, and close it explicitly.
1159
+ HTTP transport lifecycle guidance remains in server instructions and
1160
+ `ipi/skills/notebook-http/SKILL.md`; `read_notebook_skill` returns it together
1161
+ with the common notebook workflow. Local stdio agents continue to use
1162
+ `ipi/skills/notebook/SKILL.md`. Plugins must read active state from their
1163
+ injected `ToolContext`, not module globals or the server startup cwd.
1164
+
1165
+ ### Explicit programmatic runtime ownership
1166
+
1167
+ **Status:** Implemented.
1168
+
1169
+ **Change:** `create_server` now accepts an explicit `session_mode`.
1170
+ `shared_manager=` is valid only for stateful mode. `manager_factory=` is valid
1171
+ only for stateless mode and receives the newly generated logical session ID.
1172
+ Omitting the applicable owner constructs the corresponding default manager or
1173
+ registry.
1174
+
1175
+ **Why:** The old parameter names did not describe whether state was shared or
1176
+ isolated. Injecting a mutable registry also split ownership across the caller
1177
+ and server and could cache a manager before server plugin binding succeeded.
1178
+ The factory boundary keeps construction, binding, insertion, and shutdown under
1179
+ one owner.
1180
+
1181
+ **Migration:** Pass `session_mode="stateful"` with `shared_manager=` for the
1182
+ implicit multi-kernel server. Pass `session_mode="stateless"` with
1183
+ `manager_factory=lambda session_id: CustomRuntimeManager(session_id=...)` for
1184
+ explicit one-kernel sessions. Supplying an owner intended for the other mode is
1185
+ an error.
1186
+
1187
+ ### Initial built-in plugin IDs and image result
1188
+
1189
+ **Status:** Implemented.
1190
+
1191
+ **Change:** The lightweight AGENTS and automatic context behavior now use
1192
+ reserved built-in IDs `ipi.agents_md` and `ipi.auto_context`. The image tool is
1193
+ the server plugin `ipi.view_image` and returns native MCP `ImageContent` for
1194
+ PNG, JPEG, GIF, and WebP rather than a text-only path description. The old
1195
+ `%%view_image` magic is not retained.
1196
+
1197
+ **Why:** Built-ins should exercise the same small public contract as external
1198
+ plugins. Native MCP image content lets capable clients render the actual file
1199
+ without provider-specific text or base64 conventions.
1200
+
1201
+ **Migration:** Replace old `ipi-plugin-*` config tables with
1202
+ `[plugins.config."ipi.<name>"]` tables and disabled IDs with the new reserved
1203
+ IDs. Call the `view_image(path=...)` MCP tool directly; remove uses of the cell
1204
+ magic or assumptions that its result is plain text.
1205
+
1206
+ ### Internal extension tool calls
1207
+
1208
+ **Status:** Implemented.
1209
+
1210
+ **Change:** `ctx.impersonate(...)`, the `impersonated` tool-hook flag, and the
1211
+ synthetic tool-call helpers have been replaced by `ctx.run_tool(...)`, the
1212
+ `internal` flag, and `run_internal_tool(...)`. Internal calls no longer append
1213
+ a synthetic assistant-message record.
1214
+
1215
+ **Why:** IPi is no longer an LLM provider or transcript owner. Startup probes
1216
+ and debugger automation still need to execute tools through normal hooks, but
1217
+ representing those operations as an assistant message is inaccurate and tied
1218
+ the execution core to provider wire formats.
1219
+
1220
+ **Migration:** Replace `ctx.impersonate(name, ...)` with
1221
+ `ctx.run_tool(name, ...)` and `ctx.impersonated` with `ctx.internal`. Consumers
1222
+ of session JSONL must no longer expect an assistant-message record before
1223
+ host-initiated tool calls; the tool-call and result records remain.
1224
+
1225
+ ### Canonical MCP tool arguments
1226
+
1227
+ **Status:** Implemented.
1228
+
1229
+ **Change:** Provider-specific tool normalization, custom/freeform tool metadata,
1230
+ Lark grammar declarations, wire serializers, and the public
1231
+ `ToolCallRecord.wire_kind` field have been removed. PDB now accepts the normal
1232
+ MCP argument object `{"command": "..."}` only. The live-only record schema is
1233
+ strict; existing JSONL records with a `wire_kind` key must be migrated before
1234
+ IPi reads them.
1235
+
1236
+ **Why:** The deleted embedded providers were the only consumers of alternate
1237
+ wire shapes. Retaining two representations forced every execution and record
1238
+ path to normalize data that already arrives from MCP in canonical form.
1239
+
1240
+ **Migration:** Extension tools must define one typed MCP argument schema and
1241
+ consume it directly. Remove `freeform`, `grammar`, `normalize_arguments`, and
1242
+ `wire_arguments` members. Replace raw PDB custom-tool bodies with the `command`
1243
+ field.
1244
+
1245
+ ### Conversation record removal
1246
+
1247
+ **Status:** Implemented.
1248
+
1249
+ **Change:** `UserMessageRecord`, `AssistantMessageRecord`, `ProviderMessage`,
1250
+ `ContextRecord`, `UnknownRecord`, visibility/render policies, provider-history
1251
+ rendering, baked context-text parsing, and user-message rewind helpers have been
1252
+ removed. The session log accepts only the current execution record schema;
1253
+ unknown and provider-era lines are no longer preserved or interpreted. Output
1254
+ summaries affect only the current MCP tool result after durable persistence.
1255
+
1256
+ **Why:** Codex and Claude Code own the conversation transcript, compaction, and
1257
+ rewind lifecycle. IPi owns notebook execution records and output artifacts.
1258
+ Maintaining a second partial transcript created incorrect ownership and coupled
1259
+ kernel persistence to provider request formats.
1260
+
1261
+ **Migration:** Consumers of session JSONL should use the current cell, bash,
1262
+ tool-call, tool-result, and kernel event records only. Read conversation and
1263
+ context history from the active agent harness. Migrate any retained legacy log
1264
+ before passing it to IPi; the runtime does not provide a compatibility loader.
1265
+
1266
+ ### Session resume and fork removal
1267
+
1268
+ **Status:** Implemented.
1269
+
1270
+ **Change:** `Session.open`, `SessionInfo`, `list_sessions`, `resolve_session`,
1271
+ `fork_session`, and the associated resolution errors and artifact-copy helpers
1272
+ have been removed. A session exists only for its live MCP connection and is not
1273
+ reopened after process exit.
1274
+
1275
+ **Why:** The agreed product has disposable per-connection kernels and no restart
1276
+ resumability. Keeping discovery and fork code implied a durable lifecycle that
1277
+ the kernel processes, plugin environments, and HTTP ownership model did not
1278
+ support.
1279
+
1280
+ **Migration:** Treat `.agents/sessions/<id>` as an append-only execution and
1281
+ artifact directory, not a resumable IPi object. Inspect or copy files with
1282
+ ordinary filesystem tools when archival workflows need them.
1283
+
1284
+ ### Embedded provider runtime removal
1285
+
1286
+ **Status:** Implemented.
1287
+
1288
+ **Change:** The internal `Provider` API, provider stream/response models,
1289
+ `LLMSessionInfo`, `ipi.llm`, `ipi.runtime.llm`, `%ipi_set_session`, provider
1290
+ debug sinks, credential storage, reasoning models, and agent-side LLM helpers
1291
+ have been removed. Kernels no longer receive provider/model metadata in their
1292
+ environment or notebook metadata.
1293
+
1294
+ **Why:** IPi does not call an LLM in the MCP product. Codex and Claude Code own
1295
+ models, credentials, reasoning settings, provider requests, and conversation
1296
+ state. Propagating a fake `provider_name="mcp"` session into every kernel added
1297
+ state and API surface without representing a real capability.
1298
+
1299
+ **Migration:** Remove imports from `ipi.tools.providers`, `ipi.tools.agent`,
1300
+ `ipi.auth`, `ipi.reasoning`, `ipi.llm`, and `ipi.runtime.llm`. Extensions that
1301
+ need harness event data must use the new typed Codex/Claude harness hook API;
1302
+ kernel extensions must not infer the active model.
1303
+
1304
+ ### Public prompt helper removal
1305
+
1306
+ **Status:** Implemented.
1307
+
1308
+ **Change:** `ipi.prompts`, `Prompt`, and the public prompt/tag helper package
1309
+ have been removed. IPi retains only a private model-output renderer used by its
1310
+ own MCP result formatting.
1311
+
1312
+ **Why:** IPi no longer assembles system prompts or owns model conversations.
1313
+ Keeping a public prompt object after deleting the embedded agent loop preserved
1314
+ an API with no supported product consumer.
1315
+
1316
+ **Migration:** Build any harness-owned prompt text in Codex or Claude Code.
1317
+ Plugins should return typed harness results, context strings, or ordinary MCP
1318
+ tool values rather than importing IPi prompt helpers.
1319
+
1320
+ ### Legacy tool abstraction removal
1321
+
1322
+ **Status:** Implemented.
1323
+
1324
+ **Change:** The kernel-side `ipi.tools` priority registry, canonical `Tool`
1325
+ object classes, provider/TUI tool-name aliases, and IPi's internal
1326
+ `apply_patch` parser and dispatcher have been removed. IPi now retains only
1327
+ the argument validation used by its fixed notebook-control dispatchers and the
1328
+ packaged Notebook Agent Skill helpers. `apply_patch` remains a capability of
1329
+ supporting coding harnesses, not an IPi MCP tool.
1330
+
1331
+ **Why:** None of these abstractions had an MCP consumer. The legacy
1332
+ `apply_patch` dispatcher targeted a `%%apply_patch` kernel magic that no longer
1333
+ exists, and the provider-name translation layer described an embedded agent
1334
+ runtime that IPi no longer owns.
1335
+
1336
+ **Migration:** Register first-class MCP tools through `ServerSetup.tool()` and
1337
+ kernel behavior through `EnvironmentSetup` callbacks. Use `ipi.plugins.ToolContext`
1338
+ for tool access to the active session and kernel. Remove imports from
1339
+ `ipi.tools`, `ipi.tools.names`, and `ipi._patch`; call the coding harness's
1340
+ native patch tool when file patching is required.
1341
+
1342
+ ### Single distribution and MCP extra
1343
+
1344
+ **Status:** Implemented.
1345
+
1346
+ **Change:** The `ipi-core`, `ipi-runtime`, `ipi-mcp`, `ipi-tui`, and bundled
1347
+ `ipi-plugin-*` workspace distributions were replaced by one installable
1348
+ `ipi` distribution. Built-in plugins become modules in that distribution. The
1349
+ base dependency set supports kernel/shared code; the `mcp` extra supplies host,
1350
+ Jupyter, and harness dependencies.
1351
+
1352
+ **Why:** The workspace members share one version, repository, release, and
1353
+ runtime product. Their manifests and exact cross-pins add resolution and
1354
+ maintenance complexity without providing independent ownership or reuse. A
1355
+ clean uv experiment verified that a Git-installed `ipi[mcp]` host can launch a
1356
+ base `ipi` kernel pinned to the same resolved commit without installing host-only
1357
+ dependencies in the kernel.
1358
+
1359
+ **Migration:** Replace dependencies on internal IPi distributions with `ipi`.
1360
+ Replace `ipi_mcp` imports with `ipi.server`; imports from the shared `ipi.*`
1361
+ modules otherwise retain their names. Replace `ipi-mcp` launchers with:
1362
+
1363
+ ```bash
1364
+ uvx --isolated --from "ipi[mcp] @ git+https://github.com/nimashoghi/ipi.git@<ref>" ipi
1365
+ ```
1366
+
1367
+ External plugins remain separate distributions using the `ipi.extensions`
1368
+ entry-point group and are added to the host with `uvx --with`. IPi installs its
1369
+ base distribution into each child kernel at the exact source checkout, Git
1370
+ commit, or released version used by the host; launchers must not add the `mcp`
1371
+ extra to child kernels.
1372
+
1373
+ ### Host pins and kernel compatibility ranges
1374
+
1375
+ **Status:** Implemented.
1376
+
1377
+ **Change:** IPi now exactly pins its build dependency and every direct host
1378
+ dependency in the `mcp` extra to the versions validated for the release. The
1379
+ extra repeats exact versions for base packages used by the host. Base
1380
+ dependencies retain bounded compatibility ranges so a child-kernel overlay can
1381
+ select another supported version. `typed-agent-hooks` remains pinned to an
1382
+ immutable Git commit.
1383
+
1384
+ **Why:** `uvx` installs from wheel metadata and does not consume the
1385
+ repository's `uv.lock`. Broad lower bounds allowed an unchanged Git tag to
1386
+ resolve a different FastMCP, MCP, IPython, ipykernel, Notebook, or Plotly release
1387
+ depending on installation date. IPi is an application whose host and child
1388
+ runtime need a tested compatibility envelope.
1389
+
1390
+ **Migration:** Resolve plugin host dependencies against the exact direct host
1391
+ versions in the target IPi release. When a plugin genuinely needs a conflicting
1392
+ host dependency, release and validate a new IPi dependency set instead of
1393
+ relying on the installer to choose an untested combination. Kernel-only
1394
+ dependencies may select any version inside the declared base ranges when they
1395
+ do not conflict with IPi or another plugin contribution. Do not treat wheel
1396
+ metadata as a complete transitive lock; use the committed `uv.lock` for
1397
+ development and CI reproduction.
1398
+
1399
+ ### Server package import boundary
1400
+
1401
+ **Status:** Implemented.
1402
+
1403
+ **Change:** `ipi.server` no longer eagerly imports or re-exports
1404
+ `create_server`. Importing the package now works in a base child-kernel install,
1405
+ while executing the `ipi` command without the `mcp` extra reports the missing
1406
+ extra directly instead of raising `ModuleNotFoundError: fastmcp`.
1407
+
1408
+ **Why:** The base distribution is intentionally usable in child kernels without
1409
+ FastMCP or other host-only dependencies. Eager package initialization violated
1410
+ that boundary and made even metadata-level imports fail.
1411
+
1412
+ **Migration:** Replace:
1413
+
1414
+ ```python
1415
+ from ipi.server import create_server
1416
+ ```
1417
+
1418
+ with:
1419
+
1420
+ ```python
1421
+ from ipi.server.server import create_server
1422
+ ```
1423
+
1424
+ Launch the server from `ipi[mcp]`; the base-only console entry point is not a
1425
+ server installation.
1426
+
1427
+ ### Exact kernel dependency and plugin provenance
1428
+
1429
+ **Status:** Implemented.
1430
+
1431
+ **Change:** Caller and plugin kernel dependencies are now resolved by canonical
1432
+ distribution name before launch. Exact duplicates are deduplicated, but
1433
+ different requirements or editable modes for the same distribution are
1434
+ rejected. Explicit `pip` and `ipykernel` requirements replace IPi's defaults.
1435
+ Versioned and direct requirements are no longer rewritten to an implicit
1436
+ workspace editable.
1437
+
1438
+ Installed plugin selection validates the configured requirement against the
1439
+ host distribution version and PEP 610 `direct_url.json`. The child validates
1440
+ its installed version, direct source, owning distribution, and entry-point
1441
+ target before loading the callback. Directory distributions prove their owned
1442
+ files or bounded editable runtime tree plus resolved import roots. Built-in,
1443
+ user, and project runtime trees are hashed at discovery and must have the same
1444
+ content when the child imports them.
1445
+
1446
+ **Why:** Allowing pip to resolve conflicting plugin contributions made the
1447
+ child source depend on argument order and resolver behavior. Rewriting pinned
1448
+ requirements to a nearby checkout silently violated the selected source.
1449
+ Loading an entry point by name alone could then run a different plugin version,
1450
+ Git revision, archive, subdirectory, or mutated local file in the child.
1451
+
1452
+ **Migration:** Make every contributor use one identical PEP 508 requirement and
1453
+ editable mode for each distribution. Declare an editable local path explicitly
1454
+ when live source is intended; relative paths passed through `EnvironmentSetup`
1455
+ resolve against that environment's cwd. Ensure selected direct URLs match the
1456
+ host installation's VCS source/revision, archive hash, and subdirectory;
1457
+ reinstall the host distribution from the intended source when they do not.
1458
+ Finish editing local plugin code before creating a kernel, or create a new
1459
+ kernel after rediscovery. Restart the IPi process after changing an installed
1460
+ directory/editable plugin: once its module is admitted, later source drift is
1461
+ rejected rather than executing bytecode from a different revision. A
1462
+ kernel-enabled entry-point module is imported in both the host and base-only
1463
+ child, so move host-only imports inside host callbacks and declare all
1464
+ module-level and kernel imports as child dependencies.
1465
+
1466
+ ### Credential-safe dependency reporting
1467
+
1468
+ **Status:** Implemented.
1469
+
1470
+ **Change:** Authenticated PEP 508 URLs remain available only in transient launch
1471
+ inputs and private live-kernel state. User information is removed from notebook
1472
+ metadata, child plugin payloads, session records, manifests, launch diagnostics,
1473
+ logs, kernel inventories, snapshots, MCP-visible runtime state, and validation,
1474
+ plugin-setup, or kernel-creation errors.
1475
+
1476
+ **Why:** A private Git requirement must remain usable by `uv`, but copying that
1477
+ requirement into durable artifacts made it easy to commit a credential by
1478
+ accident. Provenance identity and child validation do not require URL user
1479
+ information.
1480
+
1481
+ **Migration:** Do not read authenticated requirements back from inventory or
1482
+ session output. For private Git metadata with redacted credentials, provide the
1483
+ matching authenticated source through `IPI_INSTALL_REQUIREMENT` for child
1484
+ launches. `IPI_UV_WITH` is deprecated. IPi verifies repository identity and
1485
+ never writes these credential-bearing values to its artifacts. Hook forwarding
1486
+ now runs from the public `typed-agent-hooks` source and needs no IPi repository
1487
+ credential.
1488
+ `IPI_INSTALL_REQUIREMENT` authenticates only the IPi overlay itself and is no
1489
+ longer reused for unrelated installed plugin distributions. Private plugins
1490
+ must supply their own usable direct requirement through their plugin dependency
1491
+ declaration or host installation.
1492
+
1493
+ ### Strict and credential-safe configuration errors
1494
+
1495
+ **Status:** Implemented.
1496
+
1497
+ **Change:** `load_config()` now converts Pydantic `ValidationError` values into
1498
+ an attributed, input-free `ConfigError`. A missing TOML file remains optional,
1499
+ but permission errors, directory paths, and other non-missing I/O failures now
1500
+ propagate instead of silently loading defaults. `PluginSetupError` no longer
1501
+ retains a raw source or callback exception; its safe fields are `source_kind`,
1502
+ `source_location`, `error_type`, and `detail`. Runtime operations preserve a
1503
+ safe exception unchanged and replace only an exception whose text contains URL
1504
+ user information before FastMCP renders or logs it.
1505
+
1506
+ Boolean tool/Jupyter fields and integer Jupyter fields are now strict: strings,
1507
+ integer booleans, floats, and Python booleans used as ports are not coerced.
1508
+ `jupyter.tunnel = true` requires Jupyter itself to be enabled. The named
1509
+ Jupyter tunnel fields described by the original change were subsequently
1510
+ removed; see "Jupyter Cloudflare tunnels are quick-tunnel only" above.
1511
+
1512
+ **Why:** Pydantic's default error text includes the raw input value, and Python
1513
+ exception chains retain the original callback exception. Sanitizing only the
1514
+ outer message still exposed authenticated requirements through MCP failures,
1515
+ logs, or formatted tracebacks. Treating every `OSError` as a missing config also
1516
+ made an unreadable project silently run with unrelated defaults.
1517
+
1518
+ **Migration:** Catch `ConfigError`, not Pydantic `ValidationError`, around
1519
+ `load_config()`. Code inspecting `PluginSetupError.source` or `.error` must use
1520
+ the safe fields above. Fix filesystem access errors rather than expecting IPi to
1521
+ ignore them. Error consumers must not depend on authenticated URL user
1522
+ information being present in an exception string. Replace coercible config
1523
+ values with literal TOML booleans and integers, and make tunnel dependencies
1524
+ explicit.
1525
+
1526
+ ### Immutable content-addressed output generations
1527
+
1528
+ **Status:** Implemented.
1529
+
1530
+ **Change:** Cell inputs and output artifacts now live below
1531
+ `outputs/cN/g-<sha256-prefix>/` rather than directly below `outputs/cN/`.
1532
+ The usual prefix is eight hexadecimal characters and extends automatically if
1533
+ another generation has the same prefix. Each generation stores its full digest
1534
+ in `.sha256`, and `manifest.json` records it as `generation_sha256`. Every path
1535
+ returned in a tool result or written to a session record names an immutable
1536
+ snapshot. Late output, `clear_output`, and cross-cell display updates publish a
1537
+ new generation and atomically replace `outputs/manifest.json`; they do not
1538
+ rewrite a path already given to an agent. Prior generations remain readable for
1539
+ the lifetime of the session.
1540
+
1541
+ **Why:** Reusing one filename meant a late update or failed manifest commit
1542
+ could change or delete bytes referenced by an earlier tool result or JSONL
1543
+ record. Publishing complete immutable generations before swapping one manifest
1544
+ keeps old references valid and makes the latest-state commit failure-atomic.
1545
+
1546
+ **Migration:** Do not construct artifact paths from handles or assume an
1547
+ artifact path will change in place. Use paths recorded in a tool result or
1548
+ `session.jsonl` when the exact response-time snapshot is required. Read
1549
+ `outputs/manifest.json` and follow its recorded input/artifact paths when the
1550
+ latest reduced state is required, including late display updates.
1551
+
1552
+ ### Compact lossless result protocol and identifiers
1553
+
1554
+ **Status:** Implemented.
1555
+
1556
+ **Change:** Model-visible MCP text results no longer use XML-shaped tags. They
1557
+ use tab-separated line records. Fields escape backslash, tab, carriage return,
1558
+ and line feed reversibly. A multiline header ends with an exact
1559
+ Unicode-code-point count; its body remains unchanged and the record ends with
1560
+ `end<TAB><record-name>`. Alternative
1561
+ artifact `part` records follow their exact output preview and carry its ordinal.
1562
+ Kernel inventories use counted tables with explicit columns and `\N` for null.
1563
+
1564
+ Persisted Python and bash inputs are returned as `input<TAB><language><TAB><path>`
1565
+ references instead of echoing source already present in the tool call. The path
1566
+ names the exact immutable input artifact. Output bodies, errors, state changes,
1567
+ artifact MIME types, alternative parts, and suspension actions remain in the
1568
+ result. When the automatic PDB stack repeats a suspension location and exception,
1569
+ the live response emits those details once while the durable cell record retains
1570
+ its concise summary.
1571
+
1572
+ Session directories now use `<inverted-nanoseconds>-<random-hex>` instead of
1573
+ also repeating a readable UTC timestamp. The inverted timestamp still preserves
1574
+ exact creation time and newest-first lexical ordering. PEP 610 build output
1575
+ omits a requested revision only when it is byte-for-byte identical to the full
1576
+ commit ID. Shortened hashes remain visible because the metadata cannot
1577
+ distinguish one from a hexadecimal branch or tag name.
1578
+
1579
+ **Why:** Structural punctuation, repeated source, high-entropy path text, and
1580
+ duplicate identity fields consumed model context without adding information.
1581
+ The replacement keeps arbitrary bodies exact and makes every omitted repetition
1582
+ recoverable from an explicit artifact or durable record.
1583
+
1584
+ **Migration:** Replace XML parsing and literal tag assertions with the line
1585
+ grammar above. Use the Unicode-code-point count, not a search for the terminator,
1586
+ to read multiline bodies. Split ordinary fields on tabs and reverse their
1587
+ escapes; treat `\N` as null only in table cells. Resolve input and output paths
1588
+ below the printed kernel output directory. Treat session and generation
1589
+ directory names as opaque identifiers; read `.sha256` or `manifest.json` when
1590
+ the full generation digest is required.
1591
+
1592
+ ### Bounded MCP output delivery
1593
+
1594
+ **Status:** Implemented.
1595
+
1596
+ **Change:** Individual text previews are limited to 64,000 characters and the
1597
+ aggregate content returned by one MCP tool call is limited to 256,000 UTF-8
1598
+ bytes. A bounded response retains its head and tail and includes the session
1599
+ artifact directory. Complete retained raw kernel outputs are still written to
1600
+ disk.
1601
+
1602
+ **Why:** Unbounded MCP results can exceed client or model context limits and can
1603
+ make otherwise successful notebook execution unusable. Persistence, rather than
1604
+ the model-facing preview, is the authoritative output record.
1605
+
1606
+ **Migration:** Consumers that require complete large outputs must read the
1607
+ artifact path recorded in the cell output or inspect the session's `kernels/`
1608
+ directory. Do not treat an MCP response preview as a lossless transport for
1609
+ arbitrarily large stdout, stderr, display data, or result text.
1610
+
1611
+ ### Typed Codex and Claude hook contract
1612
+
1613
+ **Status:** Implemented.
1614
+
1615
+ **Change:** Harness plugins now subscribe with `EnvironmentSetup.on_harness()`
1616
+ or `observe_harness()` using exact `typed-agent-hooks` shared or provider-native
1617
+ event classes. Both callbacks must be declared with `async def`; synchronous
1618
+ callbacks are rejected during plugin setup. The old `harness:*` string events
1619
+ and mutable `Harness*Ctx` values are no longer dispatched. Hook installation
1620
+ forwards every native event for the selected provider. For a recognized harness
1621
+ ancestor, failure to attach its expected bridge stops
1622
+ stdio startup; an unrecognized ancestor simply runs without forwarding.
1623
+ Individual hook callbacks still fail open.
1624
+
1625
+ **Why:** Parallel wrapper models drifted from the provider schemas and made event
1626
+ composition, correlation, and failure semantics implicit. Direct typed models
1627
+ keep Codex and Claude behavior explicit while preserving a small IPi-owned
1628
+ runtime snapshot. Async-only callbacks can run on IPi's isolated callback loop;
1629
+ arbitrary synchronous Python cannot be stopped at a deadline.
1630
+
1631
+ **Migration:** Replace string event registrations with typed event classes and
1632
+ return `typed_agent_hooks.shared.outputs` result values rather than mutating a
1633
+ context object. Convert every registered handler and observer from `def` to
1634
+ `async def`, use async I/O, and allow cancellation to propagate. Re-run
1635
+ `ipi install-hooks --provider codex` or
1636
+ `ipi install-hooks --provider claude_code` and restart the harness so all native
1637
+ events are installed.
1638
+
1639
+ ### Isolated hook callbacks and hard deadlines
1640
+
1641
+ **Status:** Implemented.
1642
+
1643
+ **Change:** `EnvironmentSetup.context()` now accepts only providers declared
1644
+ with `async def`, matching harness handlers and native observers. The timed hook
1645
+ path no longer loads config, discovers or imports plugins, or runs setup
1646
+ callbacks. It uses the pre-resolved startup environment for the exact startup
1647
+ cwd or the atomically published active environment for its exact cwd. An event
1648
+ from any other cwd fails open without plugin output.
1649
+
1650
+ Actual handlers, observers, and context providers now run on one IPi-owned
1651
+ daemon event-loop thread rather than FastMCP's serving loop. The five-second
1652
+ callback and 25-second dispatch limits are hard response deadlines: IPi requests
1653
+ cancellation, fails open, and ignores a late result without waiting for a
1654
+ callback that suppresses `CancelledError`. Shutdown cancels submitted callback
1655
+ futures and boundedly joins the worker; truly stuck user code remains only on a
1656
+ daemon thread and cannot keep the MCP process alive.
1657
+
1658
+ `HarnessRuntime.manager` has been removed. The callback runtime exposes only
1659
+ the immutable `snapshot`, `environment`, and `config` data needed to observe the
1660
+ current IPi state.
1661
+
1662
+ **Why:** Cancellation-resistant plugin code must not block every HTTP operation,
1663
+ prevent the server event loop from closing, or retain direct access to mutable
1664
+ manager operations from another event loop. A daemon callback owner makes the
1665
+ response and process-lifecycle boundaries enforceable even though Python cannot
1666
+ forcibly terminate arbitrary user code.
1667
+
1668
+ **Migration:** Convert every context provider to `async def`, use async I/O, and
1669
+ propagate `CancelledError`; code that suppresses it may continue running, but its
1670
+ result is discarded. Replace `runtime.manager` access with
1671
+ `runtime.snapshot`, `runtime.environment`, or `runtime.config`. Do not capture
1672
+ FastMCP-loop-bound futures, tasks, transports, locks, or clients in a callback;
1673
+ create async resources on the callback loop and use ordinary thread-safe state
1674
+ when sharing data with server setup. Create and activate a kernel for a project
1675
+ cwd, or restart IPi from that cwd, before expecting its project-specific hooks.
1676
+ Do not rely on a hook event to discover a new project environment.
1677
+
1678
+ ### Harness forwarding uses typed-agent-hooks directly
1679
+
1680
+ **Status:** Implemented.
1681
+
1682
+ **Change:** `ipi install-hooks` now writes a self-bootstrapping TAH forwarding
1683
+ command from the installed TAH Git source.
1684
+ IPi no longer installs an `ipi-hook-forward` tool environment or exposes the
1685
+ `ipi forward-hook` command. Hook configuration contains no IPi source or
1686
+ repository credential. Linux uses the existing Unix-socket rendezvous; macOS
1687
+ and stdio processes without a recognized Codex or Claude Code ancestor continue
1688
+ as notebook servers with forwarding inactive.
1689
+
1690
+ **Why:** The forwarder belongs to TAH and imports no IPi or FastMCP runtime.
1691
+ Installing the full private IPi MCP distribution duplicated ownership, created
1692
+ a 156-package environment, required private Git authentication, and could place
1693
+ that environment on a different filesystem from uv's cache. TAH now emits an
1694
+ immutable uvx launcher itself, so its small cached environment survives the
1695
+ installer process without a second environment lifecycle.
1696
+
1697
+ **Migration:** Re-run `ipi install-hooks` for Codex and Claude Code, then restart
1698
+ the harness. Keep `uv` available when hooks run. Remove
1699
+ `IPI_HOOK_INSTALL_REQUIREMENT` and `IPI_HOOK_RUNTIME_ROOT`; they are no longer
1700
+ read. The old `~/.local/share/ipi/hooks` directory can be deleted after both
1701
+ provider configs have been regenerated.
1702
+
1703
+ ### Authenticated MCP HTTP tunnels
1704
+
1705
+ **Status:** Implemented.
1706
+
1707
+ **Change:** Non-stdio transports accept `--tunnel` and optional
1708
+ `--tunnel-domain`. A public MCP tunnel now requires
1709
+ `IPI_MCP_HTTP_AUTH_TOKEN`; setup failure stops the server rather than silently
1710
+ advertising a local URL. Stdio rejects tunnel flags.
1711
+
1712
+ **Why:** A public endpoint exposes trusted arbitrary code execution. A local
1713
+ fallback is acceptable for the Jupyter follow view but is misleading for an
1714
+ explicit MCP publication command, and an unauthenticated public tunnel is not a
1715
+ reasonable default even under the trusted-local execution model.
1716
+
1717
+ **Migration:** Set `IPI_MCP_HTTP_AUTH_TOKEN` and give the remote MCP client the
1718
+ same bearer token or `token` query parameter. Use `--tunnel-domain` only with a
1719
+ configured named Cloudflare tunnel; omit `--tunnel` for local HTTP.
1720
+
1721
+ ### Built-in initialization and debugger plugins
1722
+
1723
+ **Status:** Implemented.
1724
+
1725
+ **Change:** Autoreload, project import, dataframe formatting, PDB, and Codex Apps
1726
+ now use reserved IDs `ipi.autoreload`, `ipi.project_import`,
1727
+ `ipi.dataframe_display`, `ipi.pdb`, and `ipi.codex_apps`. Startup actions are
1728
+ visible bootstrap cells. Bootstrap failure discards the new kernel and restores
1729
+ the previous active kernel. PDB is a normal annotated plugin tool, and Codex
1730
+ Apps supplies a generated kernel-side `ipi_codex_tools` package instead of
1731
+ dynamic MCP tools. The MCP host owns configuration, credentials, keyring access,
1732
+ network clients, and web search; the child package uses a private local bridge.
1733
+ IPi registers the generated package as top-level `tools` only when that import
1734
+ name is not already owned by the project or another dependency.
1735
+
1736
+ **Why:** These features should exercise the same scoped plugin contract as
1737
+ third-party extensions. Transactional bootstraps prevent a half-initialized
1738
+ kernel from becoming active, and a static MCP catalog matches Codex's current
1739
+ tool-discovery behavior.
1740
+
1741
+ **Migration:** Move configuration to `[plugins.config."ipi.<name>"]`. Import and
1742
+ call Codex app wrappers with `import ipi_codex_tools as tools`. Existing
1743
+ `import tools` cells still work only when the environment has no real `tools`
1744
+ module or package; reusable code must use the canonical name. Do not depend on
1745
+ old `ipi-plugin-*` setup hooks, dynamic Codex-app MCP schemas, child-side Codex
1746
+ credentials or clients, or provider/model metadata inside the kernel.
1747
+
1748
+ ### Bounded image reads
1749
+
1750
+ **Status:** Implemented.
1751
+
1752
+ **Change:** `view_image` rejects local or remote files larger than 25 MiB before
1753
+ base64 encoding them into an MCP image result. In remote mode, relative paths
1754
+ resolve against the target kernel cwd; unmapped target-native files are read by
1755
+ a bounded helper on the target instead of being resolved against the host.
1756
+
1757
+ **Why:** Base64 expands the payload and MCP transports generally buffer the
1758
+ complete image. An unbounded read can exhaust the host or client before either
1759
+ can inspect the result. Target-aware resolution also prevents a WSL path from
1760
+ accidentally naming an unrelated host file.
1761
+
1762
+ **Migration:** Resize or recompress larger images, or inspect them through the
1763
+ saved artifact and Jupyter notebook instead of `view_image`. Remote callers do
1764
+ not need a path-map entry for target-native image files.
1765
+
1766
+ The old bundled distributions and transition loader have now been deleted. The
1767
+ Python subprocess hint and subprocess-pip gate are intentionally not replaced:
1768
+ IPi is a trusted local execution environment, and package-install policy belongs
1769
+ to the calling harness or project. Existing users that depended on the one-shot
1770
+ block must enforce that policy outside IPi.
1771
+
1772
+ ### Legacy extension runtime removal
1773
+
1774
+ **Status:** Implemented.
1775
+
1776
+ **Change:** `ipi.extension`, `HostExtensionsState`, `HostInitAPI`,
1777
+ `KernelInitAPI`, mutable tool registries, priority overrides, execution hooks,
1778
+ PEP 723 extension sidecars, `.agents/extensions`, and the `[extensions]`
1779
+ configuration namespace have been removed. The bundled `ipi-plugin-*`
1780
+ distributions no longer exist. Core notebook tools use one fixed dispatcher;
1781
+ plugin MCP tools register directly with FastMCP.
1782
+
1783
+ The replacement `EnvironmentSetup` exposes only behavior that has a live
1784
+ consumer: typed harness handlers/observers, context providers, visible
1785
+ bootstrap cells, and kernel requirements. The speculative generic `on(...)`
1786
+ lifecycle API was removed until concrete, stable core events exist.
1787
+
1788
+ **Why:** The legacy system maintained two competing plugin models and carried
1789
+ agent-loop concepts through every kernel launch and tool call. Inert parameters
1790
+ and compatibility loaders made ownership unclear and allowed extensions to
1791
+ replace core state-machine operations. The smaller contract keeps MCP schemas
1792
+ static, kernel setup attributable, and project state isolated without a general
1793
+ dependency-injection framework.
1794
+
1795
+ **Migration:** Use `ipi.plugins.Plugin` and export a direct `plugin` value as
1796
+ described above. Move local files to `~/.ipi/extensions/` or
1797
+ `<project>/.ipi/extensions/`, and move configuration to `[plugins]`. Register
1798
+ model-facing tools with `ServerSetup.tool`; do not attempt to override
1799
+ `python`, `create_kernel`, `switch_kernel`, `wait`, or `interrupt`. Replace
1800
+ legacy execution hooks with a first-class MCP tool, kernel callback, visible
1801
+ bootstrap, context provider, or typed Codex/Claude hook according to the
1802
+ behavior being implemented.
1803
+
1804
+ ### Remote project paths require a shared host view
1805
+
1806
+ **Status:** Implemented.
1807
+
1808
+ **Change:** In remote kernel mode, `create_kernel(cwd=...)` now treats `cwd` as
1809
+ the kernel-side POSIX project path and resolves a separate, host-visible project
1810
+ root for `.ipi.toml`, local plugin discovery, MCP callbacks, harness callbacks,
1811
+ and context providers. Kernel creation fails when that host view is unavailable.
1812
+ Local user and project plugin paths are translated back into the kernel view
1813
+ before their kernel callbacks load.
1814
+
1815
+ **Why:** Resolving `/home/...` with the host's `pathlib.Path` silently loaded
1816
+ defaults and plugins from the wrong filesystem under a Windows-host/WSL setup.
1817
+ Host callbacks cannot safely execute project code that exists only on the
1818
+ kernel target, so the shared boundary must be explicit rather than best effort.
1819
+
1820
+ **Migration:** Configure `IPI_KERNEL_PATH_MAP` with semicolon-separated
1821
+ `<host-prefix>=><kernel-prefix>` pairs that make every remote project visible to
1822
+ both processes. For example, map a WSL UNC project root on Windows to its WSL
1823
+ POSIX root. Windows drive paths under `/mnt/<drive>` continue to use the built-in
1824
+ drive mapping. Move host-only project extensions to a shared location or install
1825
+ them as selected `ipi.extensions` distributions.
1826
+
1827
+ ### Portable model-facing paths on Windows
1828
+
1829
+ **Status:** Implemented.
1830
+
1831
+ **Change:** Artifact paths stored in cell records and AGENTS context identifiers
1832
+ now use `/` separators on every host platform. Native filesystem operations
1833
+ continue to use `pathlib.Path`; only strings exposed to models, records, and
1834
+ context consumers are normalized.
1835
+
1836
+ **Why:** These values form a portable protocol between IPi, MCP clients, and
1837
+ saved session artifacts. Emitting Windows-only `\\` separators made otherwise
1838
+ identical sessions host-dependent and contradicted the documented relative-path
1839
+ examples.
1840
+
1841
+ **Migration:** Consumers that compare these strings literally must expect `/`
1842
+ on Windows. Convert a returned path with `Path(value)` before performing native
1843
+ filesystem operations instead of constructing paths by splitting on either
1844
+ separator.