ipinb 0.2.0__tar.gz → 0.4.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 (247) hide show
  1. {ipinb-0.2.0 → ipinb-0.4.0}/BREAKING_CHANGES.md +14 -0
  2. {ipinb-0.2.0 → ipinb-0.4.0}/CHANGELOG.md +13 -0
  3. {ipinb-0.2.0 → ipinb-0.4.0}/PKG-INFO +10 -10
  4. {ipinb-0.2.0 → ipinb-0.4.0}/README.md +9 -9
  5. {ipinb-0.2.0 → ipinb-0.4.0}/docs/architecture.md +8 -2
  6. {ipinb-0.2.0 → ipinb-0.4.0}/docs/debugging.md +3 -3
  7. {ipinb-0.2.0/ipi/skills/notebook/references → ipinb-0.4.0/docs}/execution-and-results.md +5 -5
  8. {ipinb-0.2.0/ipi/skills/notebook/references → ipinb-0.4.0/docs}/kernel-environments.md +4 -4
  9. {ipinb-0.2.0/ipi/skills/notebook/references → ipinb-0.4.0/docs}/notebook-publishing.md +1 -1
  10. {ipinb-0.2.0/ipi/skills/notebook/references → ipinb-0.4.0/docs}/parallel-execution.md +2 -2
  11. {ipinb-0.2.0/src/ipi/resources/references → ipinb-0.4.0/docs}/rich-output.md +3 -3
  12. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/notebook/SKILL.md +3 -1
  13. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/notebook/references/debugging.md +3 -3
  14. {ipinb-0.2.0/docs → ipinb-0.4.0/ipi/skills/notebook/references}/execution-and-results.md +5 -5
  15. {ipinb-0.2.0/docs → ipinb-0.4.0/ipi/skills/notebook/references}/kernel-environments.md +4 -4
  16. {ipinb-0.2.0/src/ipi/resources → ipinb-0.4.0/ipi/skills/notebook}/references/notebook-publishing.md +1 -1
  17. {ipinb-0.2.0/src/ipi/resources → ipinb-0.4.0/ipi/skills/notebook}/references/parallel-execution.md +2 -2
  18. {ipinb-0.2.0/docs → ipinb-0.4.0/ipi/skills/notebook/references}/rich-output.md +3 -3
  19. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/notebook-http/SKILL.md +1 -1
  20. {ipinb-0.2.0 → ipinb-0.4.0}/pyproject.toml +1 -1
  21. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/contract.py +0 -1
  22. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/ssh.py +7 -8
  23. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/supervisor.py +13 -20
  24. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_uv_environment.py +34 -68
  25. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/execution_models.py +0 -1
  26. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/kernel.py +7 -47
  27. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/resource_registry.py +42 -6
  28. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/resources/notebook_http_skill.md +1 -1
  29. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/resources/notebook_skill.md +3 -1
  30. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/resources/references/debugging.md +3 -3
  31. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/resources/references/execution-and-results.md +5 -5
  32. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/resources/references/kernel-environments.md +4 -4
  33. {ipinb-0.2.0/docs → ipinb-0.4.0/src/ipi/resources/references}/notebook-publishing.md +1 -1
  34. {ipinb-0.2.0/docs → ipinb-0.4.0/src/ipi/resources/references}/parallel-execution.md +2 -2
  35. {ipinb-0.2.0/ipi/skills/notebook → ipinb-0.4.0/src/ipi/resources}/references/rich-output.md +3 -3
  36. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/environment.py +1 -11
  37. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/execution_server.py +9 -13
  38. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipinb.egg-info/PKG-INFO +10 -10
  39. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_kernel.py +29 -96
  40. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_ssh_transport.py +54 -52
  41. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_transport_contract.py +0 -4
  42. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_uv_environment.py +146 -15
  43. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_environment.py +1 -1
  44. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_plugin_authoring_skill.py +1 -2
  45. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_multi_project_plugins.py +4 -5
  46. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_plugin_server_integration.py +1 -2
  47. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_execution_registry.py +62 -15
  48. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_execution_transports.py +3 -3
  49. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_resource_registry.py +177 -37
  50. {ipinb-0.2.0 → ipinb-0.4.0}/LICENSE +0 -0
  51. {ipinb-0.2.0 → ipinb-0.4.0}/MANIFEST.in +0 -0
  52. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/ipi-mcp-redesign.md +0 -0
  53. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/notebook-service-log.md +0 -0
  54. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/notebook-service-plan.md +0 -0
  55. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/performance-log.md +0 -0
  56. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/performance-plan.md +0 -0
  57. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/performance-results.md +0 -0
  58. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/reduce-output-tokens-log.md +0 -0
  59. {ipinb-0.2.0 → ipinb-0.4.0}/docs/autopilot/reduce-output-tokens-plan.md +0 -0
  60. {ipinb-0.2.0 → ipinb-0.4.0}/docs/releasing.md +0 -0
  61. {ipinb-0.2.0 → ipinb-0.4.0}/docs/validation/compact-output-blocks-2026-09-13.md +0 -0
  62. {ipinb-0.2.0 → ipinb-0.4.0}/docs/validation/execution-api-2026-09-10.md +0 -0
  63. {ipinb-0.2.0 → ipinb-0.4.0}/docs/validation/fastmcp4-2026-09-13.md +0 -0
  64. {ipinb-0.2.0 → ipinb-0.4.0}/docs/your-first-extension.md +0 -0
  65. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/SKILL.md +0 -0
  66. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/references/authoring-guide.md +0 -0
  67. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/references/evaluations.md +0 -0
  68. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/references/source-map.md +0 -0
  69. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/references/testing-and-troubleshooting.md +0 -0
  70. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/templates/installed-plugin/pyproject.toml +0 -0
  71. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/templates/installed-plugin/src/example_ipi_plugin/__init__.py +0 -0
  72. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/templates/installed-plugin/tests/test_plugin.py +0 -0
  73. {ipinb-0.2.0 → ipinb-0.4.0}/ipi/skills/developing-ipi-plugins/templates/project-plugin.py +0 -0
  74. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/package-lock.json +0 -0
  75. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/package.json +0 -0
  76. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/src/cell_chrome.ts +0 -0
  77. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/src/index.ts +0 -0
  78. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/src/intent.ts +0 -0
  79. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/src/metadata.ts +0 -0
  80. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/src/setup_chrome.ts +0 -0
  81. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/style/index.css +0 -0
  82. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/tests/index.spec.ts +0 -0
  83. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/tests/setup.ts +0 -0
  84. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/tsconfig.json +0 -0
  85. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/tsconfig.test.json +0 -0
  86. {ipinb-0.2.0 → ipinb-0.4.0}/jupyterlab-extension/vitest.config.ts +0 -0
  87. {ipinb-0.2.0 → ipinb-0.4.0}/setup.cfg +0 -0
  88. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/__init__.py +0 -0
  89. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_ansi.py +0 -0
  90. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_dependency_metadata.py +0 -0
  91. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_exception_groups.py +0 -0
  92. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_git_provenance.py +0 -0
  93. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_kernel_argv.py +0 -0
  94. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_kernel_launcher.py +0 -0
  95. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_launch_gate.py +0 -0
  96. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_markdown.py +0 -0
  97. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_model_output.py +0 -0
  98. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_notebook_mirror.py +0 -0
  99. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_owner_watchdog.py +0 -0
  100. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_parent_process.py +0 -0
  101. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_platform_support.py +0 -0
  102. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_self_wheel.py +0 -0
  103. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_timing.py +0 -0
  104. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_toml.py +0 -0
  105. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/__init__.py +0 -0
  106. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/address.py +0 -0
  107. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/artifacts.py +0 -0
  108. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/local.py +0 -0
  109. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/master_owner.py +0 -0
  110. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/owned_directory.py +0 -0
  111. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/portable_uv.py +0 -0
  112. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/staging.py +0 -0
  113. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_transport/supervisor_protocol.py +0 -0
  114. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/_ynotebook_ops.py +0 -0
  115. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/cell_export.py +0 -0
  116. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/config.py +0 -0
  117. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/execution_backend.py +0 -0
  118. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/jupyter_server.py +0 -0
  119. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/labextensions/@ipi/notebook-extension/package.json +0 -0
  120. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/labextensions/@ipi/notebook-extension/static/40.5dddbc48f9255afab0d7.js +0 -0
  121. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/labextensions/@ipi/notebook-extension/static/665.890acd7e2a9ea1a043aa.js +0 -0
  122. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/labextensions/@ipi/notebook-extension/static/remoteEntry.aa9e35c0f1b6fe93a828.js +0 -0
  123. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/labextensions/@ipi/notebook-extension/static/style.js +0 -0
  124. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/labextensions/@ipi/notebook-extension/static/third-party-licenses.json +0 -0
  125. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/notebook_publisher.py +0 -0
  126. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/__init__.py +0 -0
  127. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/_model.py +0 -0
  128. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/api.py +0 -0
  129. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/__init__.py +0 -0
  130. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/_agents_md_impl.py +0 -0
  131. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/_image_outputs_impl.py +0 -0
  132. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/agents_md.py +0 -0
  133. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/autoreload.py +0 -0
  134. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/dataframe_display.py +0 -0
  135. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/http_user_agent.py +0 -0
  136. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/image_outputs.py +0 -0
  137. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/import_guidance.py +0 -0
  138. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/pdb.py +0 -0
  139. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/pip_guidance.py +0 -0
  140. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/builtins/project_import.py +0 -0
  141. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/catalog.py +0 -0
  142. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/discovery.py +0 -0
  143. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/kernel.py +0 -0
  144. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/py.typed +0 -0
  145. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/plugins/server_deps.py +0 -0
  146. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/py.typed +0 -0
  147. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/records/__init__.py +0 -0
  148. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/records/py.typed +0 -0
  149. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/redaction.py +0 -0
  150. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/references.py +0 -0
  151. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/__init__.py +0 -0
  152. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/_subprocess.py +0 -0
  153. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/audit.py +0 -0
  154. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/bootstrap.py +0 -0
  155. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/cell_types.py +0 -0
  156. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/concurrent.py +0 -0
  157. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/context.py +0 -0
  158. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/debugger.py +0 -0
  159. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/formatters.py +0 -0
  160. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/magics.py +0 -0
  161. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/path_context.py +0 -0
  162. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/project.py +0 -0
  163. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/py.typed +0 -0
  164. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/replay.py +0 -0
  165. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/runtime/state_delta.py +0 -0
  166. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/__init__.py +0 -0
  167. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/__main__.py +0 -0
  168. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/builtins/__init__.py +0 -0
  169. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/builtins/view_image.py +0 -0
  170. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/execution_content.py +0 -0
  171. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/plugin_resolver.py +0 -0
  172. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/plugin_tools.py +0 -0
  173. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/py.typed +0 -0
  174. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/server/server.py +0 -0
  175. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/session.py +0 -0
  176. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipi/tunnel.py +0 -0
  177. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipinb.egg-info/SOURCES.txt +0 -0
  178. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipinb.egg-info/dependency_links.txt +0 -0
  179. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipinb.egg-info/entry_points.txt +0 -0
  180. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipinb.egg-info/requires.txt +0 -0
  181. {ipinb-0.2.0 → ipinb-0.4.0}/src/ipinb.egg-info/top_level.txt +0 -0
  182. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_ansi.py +0 -0
  183. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_config.py +0 -0
  184. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_context_runtime.py +0 -0
  185. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_credential_boundaries.py +0 -0
  186. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_credential_redaction.py +0 -0
  187. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_debugger.py +0 -0
  188. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_editable_requirements.py +0 -0
  189. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_iopub_reducer.py +0 -0
  190. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_jupyter_server.py +0 -0
  191. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_kernel_address.py +0 -0
  192. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_kernel_regressions.py +0 -0
  193. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_late_iopub.py +0 -0
  194. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_notebook_mirror.py +0 -0
  195. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_notebook_mirror_writer.py +0 -0
  196. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_notebook_publisher.py +0 -0
  197. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_notebook_publisher_e2e.py +0 -0
  198. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_output_noise.py +0 -0
  199. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_owner_watchdog.py +0 -0
  200. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_packaging_namespace.py +0 -0
  201. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_platform_support.py +0 -0
  202. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_plugin_kernel_integration.py +0 -0
  203. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_portable_uv.py +0 -0
  204. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_references.py +0 -0
  205. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_self_wheel.py +0 -0
  206. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_session.py +0 -0
  207. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_smoke.py +0 -0
  208. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_timing.py +0 -0
  209. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_toml.py +0 -0
  210. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_transport_staging.py +0 -0
  211. {ipinb-0.2.0 → ipinb-0.4.0}/tests/core/test_tunnel.py +0 -0
  212. {ipinb-0.2.0 → ipinb-0.4.0}/tests/release_smoke.py +0 -0
  213. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_bootstrap.py +0 -0
  214. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_agents_md.py +0 -0
  215. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_autoreload.py +0 -0
  216. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_dataframe_display.py +0 -0
  217. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_http_user_agent.py +0 -0
  218. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_image_outputs.py +0 -0
  219. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_import_guidance.py +0 -0
  220. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_pdb.py +0 -0
  221. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_pip_guidance.py +0 -0
  222. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_builtin_project_import.py +0 -0
  223. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_context.py +0 -0
  224. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_formatters.py +0 -0
  225. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_magics.py +0 -0
  226. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_model_output.py +0 -0
  227. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_path_context.py +0 -0
  228. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_plugin_api.py +0 -0
  229. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_plugin_catalog.py +0 -0
  230. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_plugin_discovery.py +0 -0
  231. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_plugin_kernel.py +0 -0
  232. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_plugin_server_deps.py +0 -0
  233. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_project.py +0 -0
  234. {ipinb-0.2.0 → ipinb-0.4.0}/tests/runtime/test_smoke.py +0 -0
  235. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/conftest.py +0 -0
  236. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_builtin_view_image.py +0 -0
  237. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_main.py +0 -0
  238. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_plugin_resolver.py +0 -0
  239. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_plugin_tools.py +0 -0
  240. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_server.py +0 -0
  241. {ipinb-0.2.0 → ipinb-0.4.0}/tests/server/test_stdio_shutdown.py +0 -0
  242. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_cell_export.py +0 -0
  243. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_consolidated_tools.py +0 -0
  244. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_debugger.py +0 -0
  245. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_execution_content_errors.py +0 -0
  246. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_namespace.py +0 -0
  247. {ipinb-0.2.0 → ipinb-0.4.0}/tests/test_package_boundaries.py +0 -0
@@ -1,5 +1,19 @@
1
1
  # Breaking Changes
2
2
 
3
+ ## 2026-10-28: Registry-qualified handles (0.4.0)
4
+
5
+ Public kernel and cell IDs now include a persisted nine-digit decimal registry prefix, for example `123456789k0` and `123456789c1`. Kernel inventory cursors include that prefix too. IDs and cursors belong to their issuing server; pass returned values unchanged. Independent servers no longer issue identical handles by construction, although a random prefix collision remains possible. MCP kernel directories use the qualified kernel ID. Internal numeric execution indices and low-level session helpers are unchanged.
6
+
7
+ **Migration:** Bare `k0`/`c1` handles and old cursors are not supported. Start a fresh IPi server and use its returned IDs. If `server.registry_root` points to a database with retained unqualified IDs, choose a new directory; startup rejects the old registry and preserves its records. Keep that directory with the previous IPi version to inspect historical records. Existing servers retain their current behavior until stopped; live Python state cannot transfer to a replacement server.
8
+
9
+ ## 2026-10-27: Private uv kernel environments (0.3.0)
10
+
11
+ Every local and SSH kernel uses an isolated uv environment. Runtime and additional dependencies may override project pins inside that environment. IPi no longer turns every installed project version into a constraint. Project `.venv` and `uv.lock` stay unchanged. Live `%pip` changes remain private to the kernel. Strict `kernel.locked` checks the base project's lock; overlay versions may still differ.
12
+
13
+ The `venv` argument is removed from `create_kernel`, `Kernel.launch`, and transport launch requests, and from returned kernel records. IPi requires uv 0.12.3 or later on the execution host. Existing running servers and kernels retain their old behavior until restarted.
14
+
15
+ **Migration:** Upgrade uv, remove `venv` arguments, and set `cwd` to the intended uv project or scratch directory. Put project requirements in its `pyproject.toml`, and pass kernel-only requirements through `additional_dependencies`. Use project Python selection such as `.python-version`; do not build a separate venv for IPi. Review workflows that assumed kernel imports must use exactly the project's locked versions. New calls to a failed kernel now report `kernel_setup_failed` with the stored cause instead of `kernel_closed`; explicitly closed kernels keep the closed response.
16
+
3
17
  ## 2026-10-24: No host hook support (0.2.0)
4
18
 
5
19
  IPi 0.2.0 is a plain MCP notebook server. It installs no hooks and receives no host events.
@@ -3,6 +3,19 @@
3
3
  All notable user-facing changes are recorded here. Detailed API removals and
4
4
  migration steps are maintained in [BREAKING_CHANGES.md](BREAKING_CHANGES.md).
5
5
 
6
+ ## 0.4.0
7
+
8
+ - Qualify public kernel and cell handles with a persisted nine-digit decimal registry prefix while retaining compact, never-reused counters. Kernel inventory cursors include the registry prefix too.
9
+ - Explain server ownership in unknown-handle errors and notebook guidance. Cross-agent work goes through the agent that owns the notebook; there is no cross-server routing.
10
+ - Breaking: bare handles and old cursors are no longer supported. Registries with retained unqualified IDs require a new registry directory; existing records are preserved for inspection with the previous version. See [BREAKING_CHANGES.md](BREAKING_CHANGES.md).
11
+
12
+ ## 0.3.0
13
+
14
+ - Restore native uv dependency overrides in private per-kernel environments. Runtime dependencies can override project pins without changing project environments or lockfiles; live installs stay private to each kernel.
15
+ - Remove explicit `venv` attachment from kernel APIs and records. Select the project through `cwd` and request extra packages through `additional_dependencies`.
16
+ - Require uv 0.12.3 or later for local and SSH kernels. Keep temporary lock resolution by default and optional strict base-lock validation.
17
+ - Return the original redacted kernel setup error when Python is submitted after startup failed, rather than the generic `kernel_closed` error.
18
+
6
19
  ## 0.2.0
7
20
 
8
21
  - Breaking: IPi has no host hook support. Start context, the session trace, harness Bash commands as notebook cells, `bash[...]` shell history, the inline-Python nudge, and Claude session identity in kernels are gone (Codex kernels keep `CODEX_THREAD_ID`); the `install`, `install-hooks` and `doctor-narration` commands, the plugin hook API, `ipi.import_path` (use `toolfuncs.load`), the `ipi` command alias and the empty `mcp` extra are removed; IPi requires `toolfuncs>=3,<4`. See [BREAKING_CHANGES.md](BREAKING_CHANGES.md).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipinb
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Persistent IPython notebooks for MCP agents
5
5
  Author-email: Nima Shoghi <nimashoghi@gmail.com>
6
6
  License-Expression: MIT
@@ -104,7 +104,7 @@ Execution requires an explicit `kernel_id`. The default observation window is fi
104
104
 
105
105
  Managed kernels keep IPython history in memory because the notebook and registry are the durable execution record. Kernel creation is explicit, apart from optional discoverable startup prewarming. A closed or lost handle never creates a replacement.
106
106
 
107
- Handles are registry-local counters beginning with `k` for kernels and `c` for cells, such as `k0` and `c1`. Pass them unchanged to their originating registry; IDs are never reused within that registry. Tool responses provide one structured JSON payload with typed resource, handle, cell, inventory, or error envelopes. Output cursors belong to each reader. Large sources and events provide immutable MCP resource URIs and byte counts. Use `read_cell(..., include_artifact_metadata=True)` for artifact filesystem paths, SHA-256 checksums, and MIME types.
107
+ Handles combine a persisted nine-digit decimal registry prefix with `k` or `c` and a durable counter, such as `123456789k0` and `123456789c1`. Pass them unchanged to their originating registry; IDs are never reused within that registry. Tool responses provide one structured JSON payload with typed resource, handle, cell, inventory, or error envelopes. Output cursors belong to each reader. Large sources and events provide immutable MCP resource URIs and byte counts. Use `read_cell(..., include_artifact_metadata=True)` for artifact filesystem paths, SHA-256 checksums, and MIME types.
108
108
 
109
109
  `ipython` returns its bounded cell record plus any images the cell displayed as MCP
110
110
  image content, so `plt.show()` is one step instead of a path the model has to
@@ -117,10 +117,10 @@ Each notebook runtime gets a new session. Local sessions are stored under:
117
117
  ```text
118
118
  <initial-cwd>/.agents/sessions/<session-id>/
119
119
  session.jsonl
120
- kernels/k1/notebook.ipynb
121
- kernels/k1/outputs/manifest.json
122
- kernels/k1/outputs/c1/g-<sha256-prefix>/input.py
123
- kernels/k1/outputs/c1/g-<sha256-prefix>/o1-stdout.txt
120
+ kernels/123456789k1/notebook.ipynb
121
+ kernels/123456789k1/outputs/manifest.json
122
+ kernels/123456789k1/outputs/c1/g-<sha256-prefix>/input.py
123
+ kernels/123456789k1/outputs/c1/g-<sha256-prefix>/o1-stdout.txt
124
124
  ```
125
125
 
126
126
  Every input and every retained Jupyter MIME output has a durable local
@@ -158,7 +158,7 @@ Kernels always write an archival `notebook.ipynb`. Publish a specific live
158
158
  notebook only when someone needs to open it:
159
159
 
160
160
  ```text
161
- publish(kernel_id="k1", cells={"c1": "Result"})
161
+ publish(kernel_id=kernel_id, cells={cell_id: "Result"})
162
162
  ```
163
163
 
164
164
  The tool starts one private Notebook 7 child attached to the existing IPi
@@ -244,7 +244,7 @@ SSH URI:
244
244
  ```python
245
245
  create_kernel(cwd="/Users/me/local-project")
246
246
  create_kernel(cwd="ssh://gpu-box/opt/projects/model")
247
- create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model", venv=".venv")
247
+ create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model")
248
248
  ```
249
249
 
250
250
  The URI path is the absolute working directory on the target. Host aliases,
@@ -333,9 +333,9 @@ Fresh kernels make a small general-purpose module set available: `typing_extensi
333
333
 
334
334
  Use `publication.shared_dependencies` for packages that must be installed in both the kernel and the lazily created Notebook 7 environment, such as packages that ship a JupyterLab frontend extension. Shared entries are automatically included in every kernel, so do not duplicate them under `kernel.additional_dependencies`. The publisher uses the exact package versions and sources selected by its kernel. Ordinary kernel-only packages remain in `kernel.additional_dependencies`; installing one live with `%pip` requires no publication change. Put personal scientific and widget defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` to keep them out of project kernels.
335
335
 
336
- Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock. An explicit `venv` remains supported and is reused without synchronization; it cannot be combined with locked project preparation.
336
+ Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock. Strict-lock mode checks the base project lock; kernel overrides can still select different package versions.
337
337
 
338
- IPi captures the base environment selected by uv and constrains added kernel packages to that selection. Incompatible additions fail instead of shadowing project packages. Approved direnv settings apply to preparation and launch; IPi does not approve an `.envrc`.
338
+ IPi requires uv 0.12.3 or later. Its runtime and additional dependencies use native uv overlays and may override project package pins. Each kernel has a private writable environment: live `%pip` changes affect only that kernel. Select the project with `cwd` and request extra packages with `additional_dependencies`; explicit venv attachment is not supported. Approved direnv settings apply to preparation and launch; IPi does not approve an `.envrc`.
339
339
 
340
340
  When `tools.view_image` is omitted, the public tool is disabled on stdio and
341
341
  enabled on HTTP, SSE, and streamable HTTP. Set it explicitly to `true` or
@@ -61,7 +61,7 @@ Execution requires an explicit `kernel_id`. The default observation window is fi
61
61
 
62
62
  Managed kernels keep IPython history in memory because the notebook and registry are the durable execution record. Kernel creation is explicit, apart from optional discoverable startup prewarming. A closed or lost handle never creates a replacement.
63
63
 
64
- Handles are registry-local counters beginning with `k` for kernels and `c` for cells, such as `k0` and `c1`. Pass them unchanged to their originating registry; IDs are never reused within that registry. Tool responses provide one structured JSON payload with typed resource, handle, cell, inventory, or error envelopes. Output cursors belong to each reader. Large sources and events provide immutable MCP resource URIs and byte counts. Use `read_cell(..., include_artifact_metadata=True)` for artifact filesystem paths, SHA-256 checksums, and MIME types.
64
+ Handles combine a persisted nine-digit decimal registry prefix with `k` or `c` and a durable counter, such as `123456789k0` and `123456789c1`. Pass them unchanged to their originating registry; IDs are never reused within that registry. Tool responses provide one structured JSON payload with typed resource, handle, cell, inventory, or error envelopes. Output cursors belong to each reader. Large sources and events provide immutable MCP resource URIs and byte counts. Use `read_cell(..., include_artifact_metadata=True)` for artifact filesystem paths, SHA-256 checksums, and MIME types.
65
65
 
66
66
  `ipython` returns its bounded cell record plus any images the cell displayed as MCP
67
67
  image content, so `plt.show()` is one step instead of a path the model has to
@@ -74,10 +74,10 @@ Each notebook runtime gets a new session. Local sessions are stored under:
74
74
  ```text
75
75
  <initial-cwd>/.agents/sessions/<session-id>/
76
76
  session.jsonl
77
- kernels/k1/notebook.ipynb
78
- kernels/k1/outputs/manifest.json
79
- kernels/k1/outputs/c1/g-<sha256-prefix>/input.py
80
- kernels/k1/outputs/c1/g-<sha256-prefix>/o1-stdout.txt
77
+ kernels/123456789k1/notebook.ipynb
78
+ kernels/123456789k1/outputs/manifest.json
79
+ kernels/123456789k1/outputs/c1/g-<sha256-prefix>/input.py
80
+ kernels/123456789k1/outputs/c1/g-<sha256-prefix>/o1-stdout.txt
81
81
  ```
82
82
 
83
83
  Every input and every retained Jupyter MIME output has a durable local
@@ -115,7 +115,7 @@ Kernels always write an archival `notebook.ipynb`. Publish a specific live
115
115
  notebook only when someone needs to open it:
116
116
 
117
117
  ```text
118
- publish(kernel_id="k1", cells={"c1": "Result"})
118
+ publish(kernel_id=kernel_id, cells={cell_id: "Result"})
119
119
  ```
120
120
 
121
121
  The tool starts one private Notebook 7 child attached to the existing IPi
@@ -201,7 +201,7 @@ SSH URI:
201
201
  ```python
202
202
  create_kernel(cwd="/Users/me/local-project")
203
203
  create_kernel(cwd="ssh://gpu-box/opt/projects/model")
204
- create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model", venv=".venv")
204
+ create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model")
205
205
  ```
206
206
 
207
207
  The URI path is the absolute working directory on the target. Host aliases,
@@ -290,9 +290,9 @@ Fresh kernels make a small general-purpose module set available: `typing_extensi
290
290
 
291
291
  Use `publication.shared_dependencies` for packages that must be installed in both the kernel and the lazily created Notebook 7 environment, such as packages that ship a JupyterLab frontend extension. Shared entries are automatically included in every kernel, so do not duplicate them under `kernel.additional_dependencies`. The publisher uses the exact package versions and sources selected by its kernel. Ordinary kernel-only packages remain in `kernel.additional_dependencies`; installing one live with `%pip` requires no publication change. Put personal scientific and widget defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` to keep them out of project kernels.
292
292
 
293
- Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock. An explicit `venv` remains supported and is reused without synchronization; it cannot be combined with locked project preparation.
293
+ Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock. Strict-lock mode checks the base project lock; kernel overrides can still select different package versions.
294
294
 
295
- IPi captures the base environment selected by uv and constrains added kernel packages to that selection. Incompatible additions fail instead of shadowing project packages. Approved direnv settings apply to preparation and launch; IPi does not approve an `.envrc`.
295
+ IPi requires uv 0.12.3 or later. Its runtime and additional dependencies use native uv overlays and may override project package pins. Each kernel has a private writable environment: live `%pip` changes affect only that kernel. Select the project with `cwd` and request extra packages with `additional_dependencies`; explicit venv attachment is not supported. Approved direnv settings apply to preparation and launch; IPi does not approve an `.envrc`.
296
296
 
297
297
  When `tools.view_image` is omitted, the public tool is disabled on stdio and
298
298
  enabled on HTTP, SSE, and streamable HTTP. Set it explicitly to `true` or
@@ -2,6 +2,12 @@
2
2
 
3
3
  IPi is a notebook runtime exposed through MCP. Codex or Claude Code owns the agent loop; IPi owns persistent IPython kernels, execution records, notebooks, and artifacts. IPi installs and receives no host hooks.
4
4
 
5
+ ## Kernel environment ownership
6
+
7
+ Local and SSH kernels require uv 0.12.3 or later and use one `uv run --isolated` path. uv discovers the project/workspace, Python, local sources, and default groups. Runtime and task requirements use native overlays that can override project pins. IPi does not freeze the base package inventory, attach existing venvs, synchronize `.venv`, or write `uv.lock`. Missing or stale locks resolve temporarily unless `kernel.locked` requires a current base lock. Each launch has a private writable environment, including when uv can skip an already-satisfied overlay. Live package changes belong to that kernel.
8
+
9
+ Kernel setup remains asynchronous. Work accepted during setup receives its retained setup failure if provisioning fails. New Python submissions after failure return the same redacted cause, with the kernel ID. Replaying an accepted request ID still recovers the original cell.
10
+
5
11
  ## Distribution Boundary
6
12
 
7
13
  Installed archive plugins accept content hashes from PEP 610 `archive_info` or the URL hash fragment retained by uv for local wheels. Both forms produce the same source identity. Conflicting hashes, malformed fragments, and archives with no content hash remain errors.
@@ -115,7 +121,7 @@ Bash tool registration is process-wide and disabled by default. Set `IPI_MCP_ENA
115
121
 
116
122
  `ipython(kernel_id, code, yield_time_ms, request_id)` accepts work and optionally observes it. A zero observation window always returns a `handle`, including for a fast completed execution. Positive observations return a `cell` view with truthful queued, running, paused, succeeded, failed, interrupted, or lost state. Live debugger stops use `paused`; unhandled failures finish as `failed` while optionally retaining postmortem frames. An interruption request is independent of execution state.
117
123
 
118
- `read_cell(cell_id, yield_time_ms=0)` reads immediately by default and can wait up to 30 seconds for Python output or completion. Python replies omit source, origin, created_at, and request_id; Markdown remains immediate and includes its source and metadata. These fields remain stored internally, and Python source is available through `ipi://cells/{cell_id}/source`. It uses caller-owned output cursors such as `c1:17`. Kernel inventories use `k:<offset>` and cell inventories use `<kernel_id>:<offset>`; the identity binds each cursor to its resource and page type. Callers pass cursors unchanged. Terminal execution does not imply that the current page exhausts output. Repeated reads replay the same retained events; a later event increments the revision without reopening terminal execution. IOPub events are sequenced and persisted before response construction. Terminal completion waits for both the shell result and ordered idle message.
124
+ `read_cell(cell_id, yield_time_ms=0)` reads immediately by default and can wait up to 30 seconds for Python output or completion. Python replies omit source, origin, created_at, and request_id; Markdown remains immediate and includes its source and metadata. These fields remain stored internally, and Python source is available through `ipi://cells/{cell_id}/source`. It uses caller-owned output cursors such as `123456789c1:17`. Kernel inventories use `<registry-prefix>k:<offset>` and cell inventories use `<kernel_id>:<offset>`; the identity binds each cursor to its resource and page type. Callers pass cursors unchanged. Terminal execution does not imply that the current page exhausts output. Repeated reads replay the same retained events; a later event increments the revision without reopening terminal execution. IOPub events are sequenced and persisted before response construction. Terminal completion waits for both the shell result and ordered idle message.
119
125
 
120
126
  Model-facing output pages contain ordered `blocks`. Adjacent stream events from the same stream are concatenated into a stdout or stderr text block without changing the retained log. Other events preserve their identity and content, including immutable artifact references. The page cursor records the next underlying event, not the next block. Byte limits are applied after this projection; joining never crosses a different stream or a non-stream event.
121
127
 
@@ -139,7 +145,7 @@ The notebook mirrors retained records without re-executing them. Cross-cell disp
139
145
 
140
146
  `markdown` appends prose with optional retry deduplication and returns only `{"kind":"markdown","cell_id":"..."}`. An identical retry returns the same receipt. Use `read_cell(cell_id)` to retrieve the retained prose and metadata. `publish(kernel_id, cells={cell_id: caption})` optionally highlights terminal Python cells in the full notebook without adding executions or rows. Selection is validated before changing presentation: every selected cell must be accessible, terminal, and belong to the named kernel. Omitted cells keep their existing highlights. Publication reconciles retained records before exposing the live sidecar; a notebook synchronization failure cannot silently become a successful stale publication. Archival records survive live-kernel close under the configured retention policy, which has no automatic record expiry by default.
141
147
 
142
- Kernel handles use an unpadded, zero-based counter (`k0`, `k1`, ...); cell handles use a one-based counter (`c1`, `c2`, ...). Both are scoped to one registry. Python, Bash, markdown, and browser executions share the cell counter across kernels, so a cell ID alone resolves its record. Allocation state lives in the same SQLite database as retained resources and is committed before acceptance, so closed or expired records, failed acceptance, and owner restarts cannot recycle handles. Existing retained IDs are not renamed. Independent registries may both contain `k0` and `c1`; callers must retain the originating registry context. Execution replies contain one structured JSON payload, with an explicitly empty text-content list. Image blocks use MCP content while their complete MIME events remain retrievable as artifacts, avoiding a base64 copy in JSON.
148
+ Kernel handles combine a persisted nine-digit decimal registry prefix with an unpadded, zero-based counter (`123456789k0`, `123456789k1`, ...); cell handles use the same prefix and a one-based counter (`123456789c1`, `123456789c2`, ...). Each registry draws its prefix once, uniformly from 100000000 through 999999999, and stores it in SQLite metadata. Reopening the database preserves the prefix. Python, Bash, markdown, and browser executions share the cell counter across kernels, so a cell ID alone resolves its record. Allocation state lives in the same SQLite database as retained resources and is committed before acceptance, so closed or expired records, failed acceptance, and owner restarts cannot recycle handles. Registries with retained unqualified IDs are rejected at startup without discarding their records; use a fresh registry root after upgrading. Foreign handles fail local lookup unless two registries draw the same prefix. This is probabilistic separation: a particular independent pair collides with probability 1/900000000, independent of retained cell count. Prefix collisions across many registries follow the birthday bound, and matching counters can then alias. Prefixes are not authentication secrets. There is no cross-server routing; ask the owning agent to run follow-up work and return results. The prefix adds three tokens per ID under the measured o200k_base and cl100k_base encodings; counters keep growing without padding or wrapping. Execution replies contain one structured JSON payload, with an explicitly empty text-content list. Image blocks use MCP content while their complete MIME events remain retrievable as artifacts, avoiding a base64 copy in JSON.
143
149
 
144
150
  ## Plugin Lifecycle
145
151
 
@@ -3,8 +3,8 @@
3
3
  IPi exposes one optional-use debugger tool backed by IPython's Pdb engine:
4
4
 
5
5
  ```python
6
- ipdb(kernel_id="k0", cell_id="c7", command="where")
7
- ipdb(kernel_id="k0", command="break /absolute/path/model.py:42")
6
+ ipdb(kernel_id=kernel_id, cell_id=cell_id, command="where")
7
+ ipdb(kernel_id=kernel_id, command="break /absolute/path/model.py:42")
8
8
  ```
9
9
 
10
10
  Every call names a kernel. Add a cell ID for frame inspection, Python evaluation, or execution control. Omit the cell ID for kernel-wide breakpoint configuration and exception-stop policy. There is no active-target selector.
@@ -19,7 +19,7 @@ Use `where`, `up`, `down`, `args`, `p expression`, `pp expression`, `list`, or `
19
19
 
20
20
  Insert `breakpoint()` in a submitted cell or set a source breakpoint before running it. The cell reports `paused` and a source location. `step`, `next`, `return`, `until`, and `continue` use the real debugger engine. `quit` abandons that execution and unwinds its cleanup. Breakpoint configuration uses Pdb syntax, including `break`, `tbreak`, `condition`, `ignore`, `enable`, `disable`, and `clear`. Bare `clear` clears all breakpoints without prompting. Kernel-scoped source breakpoints should name a file and line; frame-relative expressions require a cell target.
21
21
 
22
- `ipdb(kernel_id="k0", command="exceptions raised")` stops when an exception is raised, before handling; this includes exceptions that user code may catch. `exceptions off` restores default postmortem-only behavior. Internal iteration termination is excluded from automatic raised-exception stops.
22
+ `ipdb(kernel_id=kernel_id, command="exceptions raised")` stops when an exception is raised, before handling; this includes exceptions that user code may catch. `exceptions off` restores default postmortem-only behavior. Internal iteration termination is excluded from automatic raised-exception stops.
23
23
 
24
24
  A live pause occupies its subshell. Other subshells and debugger commands remain usable. A pause also stops other tasks on that subshell's event loop; shared locks and mutable objects retain their normal Python behavior. There is no process-wide freeze or ability to step inside native C/CUDA operations. Interrupts and abandonment can be delayed by blocking native code or user code that suppresses cancellation.
25
25
 
@@ -1,12 +1,12 @@
1
1
  # Execution, results, and recovery
2
2
 
3
- Read this when work runs longer than one tool call, output is large, or a connection drops. For overlapping submissions, also read [Parallel execution](parallel-execution.md). Examples below are MCP tool calls; use returned IDs in place of illustrative IDs.
3
+ Read this when work runs longer than one tool call, output is large, or a connection drops. For overlapping submissions, also read [Parallel execution](parallel-execution.md). Examples below are MCP tool calls; `kernel_id` and `cell_id` stand for exact handles returned by this server.
4
4
 
5
5
  ## Submit and observe
6
6
 
7
7
  ```python
8
- ipython(kernel_id="k0", code="result = expensive_calculation()", yield_time_ms=0, request_id="calculate-1")
9
- read_cell(cell_id="c1", yield_time_ms=10000)
8
+ ipython(kernel_id=kernel_id, code="result = expensive_calculation()", yield_time_ms=0, request_id="calculate-1")
9
+ read_cell(cell_id=cell_id, yield_time_ms=10000)
10
10
  ```
11
11
 
12
12
  A `handle` means accepted, not completed. `yield_time_ms` limits how long the caller observes; it does not limit execution. A cancelled or disconnected observer leaves accepted work owned by IPi. Keep the kernel and cell IDs, then observe that same cell rather than resubmitting its code.
@@ -25,10 +25,10 @@ Large source or output bodies supply immutable MCP resource URIs. Read the retur
25
25
 
26
26
  Supply `request_id` when creating a kernel, submitting Python or Markdown, or issuing a debugger command if a lost response would make repetition risky. Reuse that key with the same operation and payload to recover its accepted record. Reusing a key for a different payload is rejected. A fresh key requests fresh work and can repeat side effects. Optional Bash also supports request IDs when enabled.
27
27
 
28
- HTTP and stdio expose the same execution API. HTTP reconnection does not require a new kernel: retain the kernel ID and use `list_cells(kernel_id=...)` when cell records need recovery. Closed or lost kernels do not contain usable live Python state. Identifiers do not replace authentication.
28
+ HTTP and stdio expose the same execution API. HTTP reconnection to the same server does not require a new kernel: retain the kernel ID and use `list_cells(kernel_id=...)` when cell records need recovery. Closed or lost kernels do not contain usable live Python state. Kernel and cell IDs belong to the IPi server that issued them; pass them unchanged. For another agent's notebook, ask that agent to run follow-up work and return results. Identifiers do not replace authentication.
29
29
 
30
30
  ## Cancellation and debugger state
31
31
 
32
- `interrupt(cell_id="c1")` requests cancellation of that execution only. Continue observing its outcome. Native blocking code, suppressed cancellation, and descendant activity can outlive the request. `close_kernel(kernel_id="k0")` discards the named kernel's live state; retained records remain available under the storage policy. Finishing an answer is not a reason to close a reusable kernel.
32
+ `interrupt(cell_id=cell_id)` requests cancellation of that execution only. Continue observing its outcome. Native blocking code, suppressed cancellation, and descendant activity can outlive the request. `close_kernel(kernel_id=kernel_id)` discards the named kernel's live state; retained records remain available under the storage policy. Finishing an answer is not a reason to close a reusable kernel.
33
33
 
34
34
  A new ordinary submission to a kernel abandons existing live debugger pauses and discards existing postmortem inspection before admission. Recovering an existing execution retry key does not repeat that cleanup. See [Debugging](debugging.md) before choosing to resume or move on.
@@ -4,17 +4,17 @@ Read this when choosing a working directory, Python environment, dependency over
4
4
 
5
5
  ## Choose the execution environment
6
6
 
7
- Reuse a suitable kernel ID already provided in context. When creating a kernel, pass an existing absolute working directory:
7
+ Reuse a suitable kernel ID returned by this IPi server. When creating a kernel, pass an existing absolute working directory:
8
8
 
9
9
  ```python
10
10
  create_kernel(cwd="/absolute/project", additional_dependencies=["numpy", "pandas"], request_id="analysis-kernel")
11
11
  ```
12
12
 
13
- The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup. Inspect failed setup through the kernel record rather than assuming an accepted creation is ready. Configuration and plugins resolve for that kernel's cwd; a kernel created for another project can have a different environment.
13
+ The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup. Submit work with the returned ID and observe its cell; setup failures are returned directly even if setup failed before submission. Configuration and plugins resolve for that kernel's cwd; a kernel created for another project can have a different environment.
14
14
 
15
15
  IPi uses ephemeral `uv run --isolated` environments. uv selects the project or workspace member for `cwd`, its Python version, sources, and normal default dependency groups. IPi does not create or synchronize `.venv`. It resolves missing or stale locks temporarily without writing `uv.lock`; set `kernel.locked = true` in IPi configuration when a current lock is required. Approved direnv settings apply before resolution. IPi does not approve an `.envrc`.
16
16
 
17
- Added kernel dependencies must remain compatible with every package selected by the project. Conflicts fail without replacing those packages. Prefer `additional_dependencies` at creation for task requirements. Personal defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` apply only outside a uv project. An explicit `venv` selects an existing base without synchronizing it; it cannot be combined with locked project preparation. Live `%pip` changes are disposable. The environment hint describes available modules and distribution/import aliases. Query the actual interpreter when needed:
17
+ IPi requires uv 0.12.3 or later. Initial startup includes a 180-second dependency preparation allowance in addition to the Jupyter readiness budget; `IPI_UV_BASE_ENVIRONMENT_TIMEOUT_S` changes that allowance for local and SSH launches. Select a project with `cwd` and use `additional_dependencies` for task requirements; explicit venv attachment is not supported. Native uv overlays let runtime and added packages override project pins inside the disposable kernel environment. Strict-lock mode checks the base lock, not the overlay versions. Personal defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` apply only outside a uv project. Live `%pip` changes are private to this kernel and do not change other kernels or later launches. The environment hint describes available modules and distribution/import aliases. Query the actual interpreter when needed:
18
18
 
19
19
  ```python
20
20
  import sys
@@ -42,7 +42,7 @@ Python imports, file access, and Bash commands run on the target. Do not substit
42
42
  The `bash` tool is disabled by default. Set `IPI_MCP_ENABLE_BASH_TOOL=1` before server startup to advertise it. When present:
43
43
 
44
44
  ```python
45
- bash(kernel_id="k0", cmd="pwd", yield_time_ms=0, request_id="inspect-directory")
45
+ bash(kernel_id=kernel_id, cmd="pwd", yield_time_ms=0, request_id="inspect-directory")
46
46
  ```
47
47
 
48
48
  Bash runs in the selected kernel environment and follows Python's cell handles, observation, retries, and targeted cancellation. Read its retained output with `read_cell`. Like a new `ipython` submission, it moves on from existing debugger pauses in that kernel. For calculations that should reuse Python variables, imports, or live objects, use `ipython`.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Contract
6
6
 
7
- - Use `publish(kernel_id="k0", cells={"c1": "Main result"})`. The optional mapping highlights terminal Python cells in the full live notebook. It never filters or reruns notebook content. All selected cells must belong to the named kernel.
7
+ - Use `publish(kernel_id=kernel_id, cells={cell_id: "Main result"})`. The optional mapping highlights terminal Python cells in the full live notebook. It never filters or reruns notebook content. All selected cells must belong to the named kernel.
8
8
  - Null captions highlight without text. Omitted cells retain their existing highlights; omit the map to publish without changing presentation. Empty notebooks can be published.
9
9
  - The complete mapping is validated before presentation changes. Valid highlights are retained even if endpoint creation subsequently fails.
10
10
  - A successful call returns the complete
@@ -9,8 +9,8 @@ Finish shared setup first: imports, function definitions, and any data both oper
9
9
  For example, suppose a completed setup cell defined `load_metadata()` and `summarize_metrics()`, and neither operation depends on or mutates the other's state. These are MCP tool calls, not Python to execute inside the kernel:
10
10
 
11
11
  ```python
12
- ipython(kernel_id="k0", code="metadata = load_metadata()", yield_time_ms=0)
13
- ipython(kernel_id="k0", code="metrics = summarize_metrics()", yield_time_ms=0)
12
+ ipython(kernel_id=kernel_id, code="metadata = load_metadata()", yield_time_ms=0)
13
+ ipython(kernel_id=kernel_id, code="metrics = summarize_metrics()", yield_time_ms=0)
14
14
  ```
15
15
 
16
16
  Use each returned cell ID to observe that operation:
@@ -22,13 +22,13 @@ figure.savefig("result.png", dpi=200, bbox_inches="tight")
22
22
  display(figure)
23
23
  ```
24
24
 
25
- Use the optional `view_image(kernel_id="k0", path="result.png")` tool to inspect an existing PNG, JPEG, GIF, or WebP on the kernel's filesystem. Relative paths resolve under that kernel's cwd, including for SSH kernels. This reads an image; it does not execute a cell. If that tool is absent from the catalog, inspect through an available image-capable host surface or an appropriate IPython display.
25
+ Use the optional `view_image(kernel_id=kernel_id, path="result.png")` tool to inspect an existing PNG, JPEG, GIF, or WebP on the kernel's filesystem. Relative paths resolve under that kernel's cwd, including for SSH kernels. This reads an image; it does not execute a cell. If that tool is absent from the catalog, inspect through an available image-capable host surface or an appropriate IPython display.
26
26
 
27
27
  ## Add prose and highlight results
28
28
 
29
29
  ```python
30
- markdown(kernel_id="k0", source="## Result\nThe experiment completed with the parameters below.")
31
- publish(kernel_id="k0", cells={"c7": "Main result", "c9": "Validation"})
30
+ markdown(kernel_id=kernel_id, source="## Result\nThe experiment completed with the parameters below.")
31
+ publish(kernel_id=kernel_id, cells={cell_id: "Main result", validation_cell_id: "Validation"})
32
32
  ```
33
33
 
34
34
  These are MCP calls. Markdown appends a notebook cell, including while Python runs. Publication highlights selected terminal Python cells in the full live notebook; it does not select a subset for export or rerun the code. Captions describe already-retained results. See [Notebook publishing](notebook-publishing.md) for URL lifetime and highlight updates.
@@ -3,7 +3,9 @@ name: notebook
3
3
  description: Use persistent Python kernels for multi-step investigation, analysis, prototyping, and verification with reusable state and rich output.
4
4
  ---
5
5
 
6
- Reuse a suitable kernel ID already provided in context, or create a kernel for the required cwd and environment. Retain its `kernel_id` and pass it to submissions and notebook mutations. A starting kernel queues Python until setup completes.
6
+ Reuse a suitable kernel ID returned by this IPi server, or create a kernel for the required cwd and environment. Retain its `kernel_id` and pass it to submissions and notebook mutations. A starting kernel queues Python until setup completes.
7
+
8
+ Kernel and cell IDs belong to the IPi server that issued them; pass them unchanged. For another agent's notebook, ask that agent to run follow-up work and return results.
7
9
 
8
10
  Use IPi's native parallel execution for independent work: submit separate cells to the same kernel with `yield_time_ms=0` before waiting on their results. IPi schedules them in bounded subshells that share Python globals; no manual subshell setup is needed. Use distinct names or synchronization, and wait for prerequisite cells to succeed before submitting dependent code. Create another kernel when work needs isolated globals or incompatible process state. See [Parallel execution](references/parallel-execution.md) for submission patterns, shared-state constraints, and capacity handling.
9
11
 
@@ -3,8 +3,8 @@
3
3
  IPi exposes one optional-use debugger tool backed by IPython's Pdb engine:
4
4
 
5
5
  ```python
6
- ipdb(kernel_id="k0", cell_id="c7", command="where")
7
- ipdb(kernel_id="k0", command="break /absolute/path/model.py:42")
6
+ ipdb(kernel_id=kernel_id, cell_id=cell_id, command="where")
7
+ ipdb(kernel_id=kernel_id, command="break /absolute/path/model.py:42")
8
8
  ```
9
9
 
10
10
  Every call names a kernel. Add a cell ID for frame inspection, Python evaluation, or execution control. Omit the cell ID for kernel-wide breakpoint configuration and exception-stop policy. There is no active-target selector.
@@ -19,7 +19,7 @@ Use `where`, `up`, `down`, `args`, `p expression`, `pp expression`, `list`, or `
19
19
 
20
20
  Insert `breakpoint()` in a submitted cell or set a source breakpoint before running it. The cell reports `paused` and a source location. `step`, `next`, `return`, `until`, and `continue` use the real debugger engine. `quit` abandons that execution and unwinds its cleanup. Breakpoint configuration uses Pdb syntax, including `break`, `tbreak`, `condition`, `ignore`, `enable`, `disable`, and `clear`. Bare `clear` clears all breakpoints without prompting. Kernel-scoped source breakpoints should name a file and line; frame-relative expressions require a cell target.
21
21
 
22
- `ipdb(kernel_id="k0", command="exceptions raised")` stops when an exception is raised, before handling; this includes exceptions that user code may catch. `exceptions off` restores default postmortem-only behavior. Internal iteration termination is excluded from automatic raised-exception stops.
22
+ `ipdb(kernel_id=kernel_id, command="exceptions raised")` stops when an exception is raised, before handling; this includes exceptions that user code may catch. `exceptions off` restores default postmortem-only behavior. Internal iteration termination is excluded from automatic raised-exception stops.
23
23
 
24
24
  A live pause occupies its subshell. Other subshells and debugger commands remain usable. A pause also stops other tasks on that subshell's event loop; shared locks and mutable objects retain their normal Python behavior. There is no process-wide freeze or ability to step inside native C/CUDA operations. Interrupts and abandonment can be delayed by blocking native code or user code that suppresses cancellation.
25
25
 
@@ -1,12 +1,12 @@
1
1
  # Execution, results, and recovery
2
2
 
3
- Read this when work runs longer than one tool call, output is large, or a connection drops. For overlapping submissions, also read [Parallel execution](parallel-execution.md). Examples below are MCP tool calls; use returned IDs in place of illustrative IDs.
3
+ Read this when work runs longer than one tool call, output is large, or a connection drops. For overlapping submissions, also read [Parallel execution](parallel-execution.md). Examples below are MCP tool calls; `kernel_id` and `cell_id` stand for exact handles returned by this server.
4
4
 
5
5
  ## Submit and observe
6
6
 
7
7
  ```python
8
- ipython(kernel_id="k0", code="result = expensive_calculation()", yield_time_ms=0, request_id="calculate-1")
9
- read_cell(cell_id="c1", yield_time_ms=10000)
8
+ ipython(kernel_id=kernel_id, code="result = expensive_calculation()", yield_time_ms=0, request_id="calculate-1")
9
+ read_cell(cell_id=cell_id, yield_time_ms=10000)
10
10
  ```
11
11
 
12
12
  A `handle` means accepted, not completed. `yield_time_ms` limits how long the caller observes; it does not limit execution. A cancelled or disconnected observer leaves accepted work owned by IPi. Keep the kernel and cell IDs, then observe that same cell rather than resubmitting its code.
@@ -25,10 +25,10 @@ Large source or output bodies supply immutable MCP resource URIs. Read the retur
25
25
 
26
26
  Supply `request_id` when creating a kernel, submitting Python or Markdown, or issuing a debugger command if a lost response would make repetition risky. Reuse that key with the same operation and payload to recover its accepted record. Reusing a key for a different payload is rejected. A fresh key requests fresh work and can repeat side effects. Optional Bash also supports request IDs when enabled.
27
27
 
28
- HTTP and stdio expose the same execution API. HTTP reconnection does not require a new kernel: retain the kernel ID and use `list_cells(kernel_id=...)` when cell records need recovery. Closed or lost kernels do not contain usable live Python state. Identifiers do not replace authentication.
28
+ HTTP and stdio expose the same execution API. HTTP reconnection to the same server does not require a new kernel: retain the kernel ID and use `list_cells(kernel_id=...)` when cell records need recovery. Closed or lost kernels do not contain usable live Python state. Kernel and cell IDs belong to the IPi server that issued them; pass them unchanged. For another agent's notebook, ask that agent to run follow-up work and return results. Identifiers do not replace authentication.
29
29
 
30
30
  ## Cancellation and debugger state
31
31
 
32
- `interrupt(cell_id="c1")` requests cancellation of that execution only. Continue observing its outcome. Native blocking code, suppressed cancellation, and descendant activity can outlive the request. `close_kernel(kernel_id="k0")` discards the named kernel's live state; retained records remain available under the storage policy. Finishing an answer is not a reason to close a reusable kernel.
32
+ `interrupt(cell_id=cell_id)` requests cancellation of that execution only. Continue observing its outcome. Native blocking code, suppressed cancellation, and descendant activity can outlive the request. `close_kernel(kernel_id=kernel_id)` discards the named kernel's live state; retained records remain available under the storage policy. Finishing an answer is not a reason to close a reusable kernel.
33
33
 
34
34
  A new ordinary submission to a kernel abandons existing live debugger pauses and discards existing postmortem inspection before admission. Recovering an existing execution retry key does not repeat that cleanup. See [Debugging](debugging.md) before choosing to resume or move on.
@@ -4,17 +4,17 @@ Read this when choosing a working directory, Python environment, dependency over
4
4
 
5
5
  ## Choose the execution environment
6
6
 
7
- Reuse a suitable kernel ID already provided in context. When creating a kernel, pass an existing absolute working directory:
7
+ Reuse a suitable kernel ID returned by this IPi server. When creating a kernel, pass an existing absolute working directory:
8
8
 
9
9
  ```python
10
10
  create_kernel(cwd="/absolute/project", additional_dependencies=["numpy", "pandas"], request_id="analysis-kernel")
11
11
  ```
12
12
 
13
- The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup. Inspect failed setup through the kernel record rather than assuming an accepted creation is ready. Configuration and plugins resolve for that kernel's cwd; a kernel created for another project can have a different environment.
13
+ The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup. Submit work with the returned ID and observe its cell; setup failures are returned directly even if setup failed before submission. Configuration and plugins resolve for that kernel's cwd; a kernel created for another project can have a different environment.
14
14
 
15
15
  IPi uses ephemeral `uv run --isolated` environments. uv selects the project or workspace member for `cwd`, its Python version, sources, and normal default dependency groups. IPi does not create or synchronize `.venv`. It resolves missing or stale locks temporarily without writing `uv.lock`; set `kernel.locked = true` in IPi configuration when a current lock is required. Approved direnv settings apply before resolution. IPi does not approve an `.envrc`.
16
16
 
17
- Added kernel dependencies must remain compatible with every package selected by the project. Conflicts fail without replacing those packages. Prefer `additional_dependencies` at creation for task requirements. Personal defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` apply only outside a uv project. An explicit `venv` selects an existing base without synchronizing it; it cannot be combined with locked project preparation. Live `%pip` changes are disposable. The environment hint describes available modules and distribution/import aliases. Query the actual interpreter when needed:
17
+ IPi requires uv 0.12.3 or later. Initial startup includes a 180-second dependency preparation allowance in addition to the Jupyter readiness budget; `IPI_UV_BASE_ENVIRONMENT_TIMEOUT_S` changes that allowance for local and SSH launches. Select a project with `cwd` and use `additional_dependencies` for task requirements; explicit venv attachment is not supported. Native uv overlays let runtime and added packages override project pins inside the disposable kernel environment. Strict-lock mode checks the base lock, not the overlay versions. Personal defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` apply only outside a uv project. Live `%pip` changes are private to this kernel and do not change other kernels or later launches. The environment hint describes available modules and distribution/import aliases. Query the actual interpreter when needed:
18
18
 
19
19
  ```python
20
20
  import sys
@@ -42,7 +42,7 @@ Python imports, file access, and Bash commands run on the target. Do not substit
42
42
  The `bash` tool is disabled by default. Set `IPI_MCP_ENABLE_BASH_TOOL=1` before server startup to advertise it. When present:
43
43
 
44
44
  ```python
45
- bash(kernel_id="k0", cmd="pwd", yield_time_ms=0, request_id="inspect-directory")
45
+ bash(kernel_id=kernel_id, cmd="pwd", yield_time_ms=0, request_id="inspect-directory")
46
46
  ```
47
47
 
48
48
  Bash runs in the selected kernel environment and follows Python's cell handles, observation, retries, and targeted cancellation. Read its retained output with `read_cell`. Like a new `ipython` submission, it moves on from existing debugger pauses in that kernel. For calculations that should reuse Python variables, imports, or live objects, use `ipython`.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Contract
6
6
 
7
- - Use `publish(kernel_id="k0", cells={"c1": "Main result"})`. The optional mapping highlights terminal Python cells in the full live notebook. It never filters or reruns notebook content. All selected cells must belong to the named kernel.
7
+ - Use `publish(kernel_id=kernel_id, cells={cell_id: "Main result"})`. The optional mapping highlights terminal Python cells in the full live notebook. It never filters or reruns notebook content. All selected cells must belong to the named kernel.
8
8
  - Null captions highlight without text. Omitted cells retain their existing highlights; omit the map to publish without changing presentation. Empty notebooks can be published.
9
9
  - The complete mapping is validated before presentation changes. Valid highlights are retained even if endpoint creation subsequently fails.
10
10
  - A successful call returns the complete
@@ -9,8 +9,8 @@ Finish shared setup first: imports, function definitions, and any data both oper
9
9
  For example, suppose a completed setup cell defined `load_metadata()` and `summarize_metrics()`, and neither operation depends on or mutates the other's state. These are MCP tool calls, not Python to execute inside the kernel:
10
10
 
11
11
  ```python
12
- ipython(kernel_id="k0", code="metadata = load_metadata()", yield_time_ms=0)
13
- ipython(kernel_id="k0", code="metrics = summarize_metrics()", yield_time_ms=0)
12
+ ipython(kernel_id=kernel_id, code="metadata = load_metadata()", yield_time_ms=0)
13
+ ipython(kernel_id=kernel_id, code="metrics = summarize_metrics()", yield_time_ms=0)
14
14
  ```
15
15
 
16
16
  Use each returned cell ID to observe that operation:
@@ -22,13 +22,13 @@ figure.savefig("result.png", dpi=200, bbox_inches="tight")
22
22
  display(figure)
23
23
  ```
24
24
 
25
- Use the optional `view_image(kernel_id="k0", path="result.png")` tool to inspect an existing PNG, JPEG, GIF, or WebP on the kernel's filesystem. Relative paths resolve under that kernel's cwd, including for SSH kernels. This reads an image; it does not execute a cell. If that tool is absent from the catalog, inspect through an available image-capable host surface or an appropriate IPython display.
25
+ Use the optional `view_image(kernel_id=kernel_id, path="result.png")` tool to inspect an existing PNG, JPEG, GIF, or WebP on the kernel's filesystem. Relative paths resolve under that kernel's cwd, including for SSH kernels. This reads an image; it does not execute a cell. If that tool is absent from the catalog, inspect through an available image-capable host surface or an appropriate IPython display.
26
26
 
27
27
  ## Add prose and highlight results
28
28
 
29
29
  ```python
30
- markdown(kernel_id="k0", source="## Result\nThe experiment completed with the parameters below.")
31
- publish(kernel_id="k0", cells={"c7": "Main result", "c9": "Validation"})
30
+ markdown(kernel_id=kernel_id, source="## Result\nThe experiment completed with the parameters below.")
31
+ publish(kernel_id=kernel_id, cells={cell_id: "Main result", validation_cell_id: "Validation"})
32
32
  ```
33
33
 
34
34
  These are MCP calls. Markdown appends a notebook cell, including while Python runs. Publication highlights selected terminal Python cells in the full live notebook; it does not select a subset for export or rerun the code. Captions describe already-retained results. See [Notebook publishing](notebook-publishing.md) for URL lifetime and highlight updates.
@@ -3,6 +3,6 @@ name: notebook-http
3
3
  description: Use IPi's explicit persistent kernels across reconnecting HTTP MCP clients.
4
4
  ---
5
5
 
6
- HTTP and stdio expose the same IPi tools and resource IDs. Follow the notebook skill. Retain `kernel_id` for submissions and notebook mutations, and `cell_id` for observation and interruption. HTTP transport sessions do not own Python state; reconnecting does not require a new kernel. Resource IDs identify records and do not replace authentication.
6
+ HTTP and stdio expose the same IPi tools and resource IDs. Follow the notebook skill. Retain `kernel_id` for submissions and notebook mutations, and `cell_id` for observation and interruption. HTTP transport sessions do not own Python state; reconnecting to the same server does not require a new kernel. IDs from another server are not portable; ask the owning agent to return results. Resource IDs identify records and do not replace authentication.
7
7
 
8
8
  There is no separate logical `session_id`, active-kernel selector, or whole-service kill tool. `close_kernel` ends only its named kernel. Published notebooks remain live until that kernel or the owning IPi process ends; retained records survive under the advertised storage policy.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ipinb"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Persistent IPython notebooks for MCP agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -23,7 +23,6 @@ class KernelLaunchRequest:
23
23
  """Validated target-side launch data, independent of one transport."""
24
24
 
25
25
  cwd: PurePath
26
- venv: PurePath | None
27
26
  requirements: tuple[str, ...]
28
27
  environment: Mapping[str, str]
29
28
  locked: bool = False
@@ -25,6 +25,7 @@ from typing import Any, cast
25
25
  from jupyter_client import KernelManager
26
26
 
27
27
  from ipi._exception_groups import BaseExceptionGroup
28
+ from ipi._uv_environment import preparation_timeout_s
28
29
  from ipi.redaction import redact_exception_message, redact_url_secrets, redact_url_userinfo
29
30
 
30
31
  from .address import SshKernelLocation
@@ -587,7 +588,6 @@ class SshPreparedTransport:
587
588
  response = supervisor.request(
588
589
  "launch",
589
590
  cwd=request.cwd.as_posix(),
590
- venv=(request.venv.as_posix() if request.venv is not None else None),
591
591
  locked=request.locked,
592
592
  requirements=list(request.requirements),
593
593
  environment=dict(request.environment),
@@ -1487,14 +1487,13 @@ class _SupervisorClient:
1487
1487
  with self._request_lock:
1488
1488
  if self._closed:
1489
1489
  raise SshTransportError("SSH supervisor control channel is closed")
1490
+ timeout = _SUPERVISOR_RESPONSE_SECONDS
1491
+ if operation == "launch":
1492
+ preparation_timeout = preparation_timeout_s()
1493
+ fields["preparation_timeout_s"] = preparation_timeout
1494
+ timeout = _SUPERVISOR_LAUNCH_RESPONSE_SECONDS + preparation_timeout
1490
1495
  request_id = self._write_request(operation, fields=fields, one_way=False)
1491
- response = self._read_message(
1492
- timeout_s=(
1493
- _SUPERVISOR_LAUNCH_RESPONSE_SECONDS
1494
- if operation == "launch"
1495
- else _SUPERVISOR_RESPONSE_SECONDS
1496
- )
1497
- )
1496
+ response = self._read_message(timeout_s=timeout)
1498
1497
  if response.get("id") != request_id or response.get("version") != _PROTOCOL_VERSION:
1499
1498
  raise SshTransportError("SSH supervisor returned a mismatched control response")
1500
1499
  if response.get("ok") is not True: