clikernel 0.2.4__tar.gz → 0.2.6__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.
- {clikernel-0.2.4 → clikernel-0.2.6}/CHANGELOG.md +17 -0
- {clikernel-0.2.4/clikernel.egg-info → clikernel-0.2.6}/PKG-INFO +3 -2
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel/__init__.py +2 -2
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel/_modidx.py +1 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel/core.py +40 -19
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel/mcp.py +1 -1
- clikernel-0.2.6/clikernel/skill.py +23 -0
- {clikernel-0.2.4 → clikernel-0.2.6/clikernel.egg-info}/PKG-INFO +3 -2
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel.egg-info/requires.txt +2 -1
- {clikernel-0.2.4 → clikernel-0.2.6}/pyproject.toml +2 -1
- clikernel-0.2.4/clikernel/skill.py +0 -55
- {clikernel-0.2.4 → clikernel-0.2.6}/LICENSE +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/MANIFEST.in +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/README.md +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel/cli.py +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel/stream.py +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel.egg-info/SOURCES.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel.egg-info/dependency_links.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel.egg-info/entry_points.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/clikernel.egg-info/top_level.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/setup.cfg +0 -0
- {clikernel-0.2.4 → clikernel-0.2.6}/tests/test_stream.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- do not remove -->
|
|
4
4
|
|
|
5
|
+
## 0.2.6
|
|
6
|
+
|
|
7
|
+
### New Features
|
|
8
|
+
|
|
9
|
+
- Replace httpx2 exception handling with fastspec APIError in kernel restart error paths ([#42](https://github.com/AnswerDotAI/clikernel/issues/42))
|
|
10
|
+
- Stop closing kernel managers on disconnect or kernel switch ([#40](https://github.com/AnswerDotAI/clikernel/issues/40))
|
|
11
|
+
- Rename httpx import to httpx2 ([#39](https://github.com/AnswerDotAI/clikernel/issues/39))
|
|
12
|
+
- restart now recovers when the kernel is gone by creating a fresh kernel on the same gateway, with startup files reapplied ([#38](https://github.com/AnswerDotAI/clikernel/issues/38))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## 0.2.5
|
|
16
|
+
|
|
17
|
+
### New Features
|
|
18
|
+
|
|
19
|
+
- restart now re-runs startup.py and reinstalls inspectors on client-created kernels; attached kernels still restart bare ([#37](https://github.com/AnswerDotAI/clikernel/issues/37))
|
|
20
|
+
|
|
21
|
+
|
|
5
22
|
## 0.2.4
|
|
6
23
|
|
|
7
24
|
### New Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clikernel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
|
|
5
5
|
Author: clikernel contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,7 +12,8 @@ Requires-Python: >=3.11
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: fastcore>=2.2.2
|
|
15
|
-
Requires-Dist: jupyasyncclient>=0.2.
|
|
15
|
+
Requires-Dist: jupyasyncclient>=0.2.8
|
|
16
|
+
Requires-Dist: fastspec>=0.2.2
|
|
16
17
|
Requires-Dist: mcpmini>=0.0.1
|
|
17
18
|
Requires-Dist: aidialog>=0.0.7
|
|
18
19
|
Requires-Dist: pillow
|
|
@@ -5,7 +5,7 @@ Modules:
|
|
|
5
5
|
- `clikernel.cli`: The stream-protocol frontend: the service on stdin/stdout for token-reading clients
|
|
6
6
|
- `clikernel.core`: Connect to gateway-hosted kernels and turn execution into concise text
|
|
7
7
|
- `clikernel.mcp`: The MCP frontend: `Client` as tools on stdio
|
|
8
|
-
- `clikernel.skill`: Use the
|
|
8
|
+
- `clikernel.skill`: Use the `clikernel` MCP session as the default workspace for Python work: reading and changing files, notebook work, trying things out, checking how a library behaves, and reshaping data. One session stays open, so imports and variables carry between calls. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
|
|
9
9
|
- `clikernel.stream`: Streaming JSON-lines worker protocol: nbformat-shaped output events, and a supervisor for select-based UIs."""
|
|
10
10
|
|
|
11
|
-
__version__ = "0.2.
|
|
11
|
+
__version__ = "0.2.6"
|
|
@@ -16,6 +16,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
16
16
|
'clikernel.cli.serve_stream': ('cli.html#serve_stream', 'clikernel/cli.py')},
|
|
17
17
|
'clikernel.core': { 'clikernel.core.Client': ('core.html#client', 'clikernel/core.py'),
|
|
18
18
|
'clikernel.core.Client.__init__': ('core.html#client.__init__', 'clikernel/core.py'),
|
|
19
|
+
'clikernel.core.Client._setup': ('core.html#client._setup', 'clikernel/core.py'),
|
|
19
20
|
'clikernel.core.Client._use': ('core.html#client._use', 'clikernel/core.py'),
|
|
20
21
|
'clikernel.core.Client.aclose': ('core.html#client.aclose', 'clikernel/core.py'),
|
|
21
22
|
'clikernel.core.Client.connect': ('core.html#client.connect', 'clikernel/core.py'),
|
|
@@ -7,10 +7,11 @@ Docs: https://AnswerDotAI.github.io/clikernel/core.html.md"""
|
|
|
7
7
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
8
8
|
|
|
9
9
|
# %% auto #0
|
|
10
|
-
__all__ = ['DEFAULT_URL', 'MAXLEN', 'STATE_LOST', 'cfg_dir', 'gateways', 'resolve', 'Client']
|
|
10
|
+
__all__ = ['DEFAULT_URL', 'MAXLEN', 'STATE_LOST', 'STATE_LOST_SETUP', 'cfg_dir', 'gateways', 'resolve', 'Client']
|
|
11
11
|
|
|
12
12
|
# %% ../nbs/00_core.ipynb #2b3b7f4a
|
|
13
13
|
import os, tomllib
|
|
14
|
+
from fastspec.errors import APIError
|
|
14
15
|
from fastcore.utils import *
|
|
15
16
|
from fastcore.nbio import render_text
|
|
16
17
|
from fastcore.xdg import xdg_config_home
|
|
@@ -92,20 +93,34 @@ def _inspector_setup(src):
|
|
|
92
93
|
|
|
93
94
|
# %% ../nbs/00_core.ipynb #33b7bc28
|
|
94
95
|
STATE_LOST = 'NOTE: the kernel restarted with a fresh interpreter: all session state is lost (imports, variables, monkeypatches). Redo any setup the task still needs.'
|
|
96
|
+
STATE_LOST_SETUP = 'NOTE: the kernel restarted with a fresh interpreter: startup.py has been re-run (its imports are back), but all other session state is lost (variables, monkeypatches, other imports). Redo what the task still needs.'
|
|
95
97
|
|
|
96
98
|
class Client:
|
|
97
99
|
"One conversation's connection: a gateway manager, and the current kernel"
|
|
98
|
-
def __init__(self, cfgdir=None): self.cfgdir,self.mgr,self.kc,self.kid,self.auto = cfgdir,None,None,None,False
|
|
100
|
+
def __init__(self, cfgdir=None): self.cfgdir,self.mgr,self.kc,self.kid,self.auto,self.made = cfgdir,None,None,None,False,False
|
|
99
101
|
|
|
100
102
|
async def _use(self, mgr, kid):
|
|
101
103
|
"Point at `kid` on `mgr`, dropping any previous ws (never the kernel)"
|
|
102
104
|
if self.kc: await self.kc.aclose()
|
|
103
|
-
if self.mgr is not None and self.mgr is not mgr: await self.mgr.aclose()
|
|
104
105
|
self.mgr,self.kid,self.kc = mgr,kid,mgr.client(kid)
|
|
105
106
|
self.kc.start_channels()
|
|
106
107
|
await self.kc.wait_for_ready(timeout=30)
|
|
107
108
|
|
|
108
109
|
# %% ../nbs/00_core.ipynb #94497e4a
|
|
110
|
+
@patch
|
|
111
|
+
async def _setup(self:Client):
|
|
112
|
+
"Deliver `startup.py` and `inspectors.py` to the current kernel, returning startup's output"
|
|
113
|
+
out = ''
|
|
114
|
+
d = Path(self.cfgdir) if self.cfgdir else cfg_dir()
|
|
115
|
+
if (p := d/'startup.py').exists(): out = await self.execute(_startup_src(p.read_text(), p))
|
|
116
|
+
if (p := d/'inspectors.py').exists():
|
|
117
|
+
res = await self.execute(_inspector_setup(p.read_text()))
|
|
118
|
+
if res: # a load failure is fatal: refusing to run beats running uninspected
|
|
119
|
+
await self.mgr.shutdown_kernel(self.kid)
|
|
120
|
+
self.kc,self.kid,self.auto,self.made = None,None,False,False
|
|
121
|
+
raise RuntimeError(f'inspectors.py failed to load; kernel stopped:\n{res}')
|
|
122
|
+
return out
|
|
123
|
+
|
|
109
124
|
@patch
|
|
110
125
|
async def connect(self:Client, host='', kernel='', auto=False):
|
|
111
126
|
"Connect to a gateway (create a kernel, or attach to `kernel` by id prefix); the pointer aims at it afterwards; `auto` marks a created kernel as client-scoped"
|
|
@@ -113,7 +128,7 @@ async def connect(self:Client, host='', kernel='', auto=False):
|
|
|
113
128
|
if self.auto: # scoped to this client: any new connect ends it, even one already dead
|
|
114
129
|
try: await self.stop()
|
|
115
130
|
except Exception as e:
|
|
116
|
-
self.kc,self.kid,self.auto = None,None,False
|
|
131
|
+
self.kc,self.kid,self.auto,self.made = None,None,False,False
|
|
117
132
|
note = f'\nnote: stopping the auto kernel failed ({e})'
|
|
118
133
|
url,tok,ver = resolve(host, self.cfgdir)
|
|
119
134
|
mgr = JupyAsyncMultiKernelManager(url, token=tok, verify=ver)
|
|
@@ -122,19 +137,13 @@ async def connect(self:Client, host='', kernel='', auto=False):
|
|
|
122
137
|
kid = first(k['id'] for k in ks if k['id'].startswith(kernel))
|
|
123
138
|
if not kid: raise ValueError(f'no kernel matching {kernel!r} on {url}: {[k["id"][:8] for k in ks]}')
|
|
124
139
|
await self._use(mgr, kid)
|
|
140
|
+
self.made = False
|
|
125
141
|
return f'connected to existing kernel {kid} on {url}' + note
|
|
126
142
|
kw = dict(cwd=os.getcwd(), env=dict(os.environ)) if not host else {} # the default gateway is local by definition: kernels start where, and as, the conversation lives (cwd and environment - so kernel-side tools that key state to the conversation, like llmdojo's doc-state, resolve it correctly)
|
|
127
143
|
kid = await mgr.start_kernel(**kw)
|
|
128
144
|
await self._use(mgr, kid)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (p := d/'startup.py').exists(): out = await self.execute(_startup_src(p.read_text(), p))
|
|
132
|
-
if (p := d/'inspectors.py').exists():
|
|
133
|
-
res = await self.execute(_inspector_setup(p.read_text()))
|
|
134
|
-
if res: # a load failure is fatal: refusing to start beats running uninspected
|
|
135
|
-
await self.mgr.shutdown_kernel(kid)
|
|
136
|
-
self.kc,self.kid = None,None
|
|
137
|
-
raise RuntimeError(f'inspectors.py failed to load; kernel stopped:\n{res}')
|
|
145
|
+
self.made = True
|
|
146
|
+
out = await self._setup()
|
|
138
147
|
self.auto = auto
|
|
139
148
|
return f'created kernel {kid} on {url}' + (f'\n{out}' if out.strip() else '') + note
|
|
140
149
|
|
|
@@ -159,7 +168,6 @@ async def list_kernels(self:Client, host=''):
|
|
|
159
168
|
url,tok,ver = resolve(host, self.cfgdir)
|
|
160
169
|
mgr = JupyAsyncMultiKernelManager(url, token=tok, verify=ver)
|
|
161
170
|
ks = await mgr.list_kernels()
|
|
162
|
-
await mgr.aclose()
|
|
163
171
|
else: ks = await self.mgr.list_kernels()
|
|
164
172
|
if not ks: return 'no kernels'
|
|
165
173
|
def _l(k): return f"{k['id']} {k.get('execution_state','?')} connections={k.get('connections','?')}" + (' <- current' if k['id']==self.kid else '')
|
|
@@ -175,16 +183,30 @@ async def stop(self:Client, kernel=''):
|
|
|
175
183
|
await self.mgr.shutdown_kernel(kid)
|
|
176
184
|
if kid == self.kid:
|
|
177
185
|
await self.kc.aclose()
|
|
178
|
-
self.kc,self.kid,self.auto = None,None,False
|
|
186
|
+
self.kc,self.kid,self.auto,self.made = None,None,False,False
|
|
179
187
|
return f'stopped kernel {kid}'
|
|
180
188
|
|
|
181
189
|
@patch
|
|
182
190
|
async def restart(self:Client):
|
|
183
|
-
"Restart the current kernel: same id, fresh interpreter"
|
|
191
|
+
"Restart the current kernel: same id, fresh interpreter; a kernel this client created gets `startup.py` and `inspectors.py` again"
|
|
184
192
|
if not self.kc: return 'no kernel: call `connect` first'
|
|
185
|
-
await self.mgr.restart_kernel(self.kid)
|
|
193
|
+
try: await self.mgr.restart_kernel(self.kid)
|
|
194
|
+
except APIError as e:
|
|
195
|
+
if e.status_code is None:
|
|
196
|
+
return f'restart did not complete ({e.error_type}): retry, or `connect` for a fresh kernel; check the gateway if this persists'
|
|
197
|
+
if e.status_code != 404:
|
|
198
|
+
return f'restart failed ({e.message}); the kernel is still listed: retry, `stop` it, or `connect` for a fresh one'
|
|
199
|
+
old,kw = self.kid,{}
|
|
200
|
+
await self.kc.aclose()
|
|
201
|
+
if self.mgr.base_url == resolve('', self.cfgdir)[0]: kw = dict(cwd=os.getcwd(), env=dict(os.environ))
|
|
202
|
+
await self._use(self.mgr, await self.mgr.start_kernel(**kw))
|
|
203
|
+
self.made = True
|
|
204
|
+
out = await self._setup()
|
|
205
|
+
return f'kernel {old} was gone; created fresh kernel {self.kid}.\n' + STATE_LOST_SETUP + (f'\n{out}' if out.strip() else '')
|
|
186
206
|
await self.kc.wait_for_ready(timeout=30)
|
|
187
|
-
return STATE_LOST
|
|
207
|
+
if not self.made: return STATE_LOST
|
|
208
|
+
out = await self._setup()
|
|
209
|
+
return STATE_LOST_SETUP + (f'\n{out}' if out.strip() else '')
|
|
188
210
|
|
|
189
211
|
@patch
|
|
190
212
|
async def interrupt(self:Client):
|
|
@@ -197,5 +219,4 @@ async def interrupt(self:Client):
|
|
|
197
219
|
async def aclose(self:Client):
|
|
198
220
|
"Drop connections; kernels are left exactly as they are"
|
|
199
221
|
if self.kc: await self.kc.aclose()
|
|
200
|
-
if self.mgr: await self.mgr.aclose()
|
|
201
222
|
self.mgr,self.kc,self.kid = None,None,None
|
|
@@ -67,7 +67,7 @@ def mk_server(c:Client):
|
|
|
67
67
|
return await c.stop(kernel)
|
|
68
68
|
|
|
69
69
|
async def restart()->str:
|
|
70
|
-
"Kill the current kernel's process and start a fresh one under the same id: `sys.modules` genuinely reset, all session state discarded. Use for a clean slate, after rebuilding a native extension, or after a reload left stale classes behind (symptoms: `isinstance` mysteriously failing, a class missing a method you know it has). Also works when `execute` is stuck.
|
|
70
|
+
"Kill the current kernel's process and start a fresh one under the same id: `sys.modules` genuinely reset, all session state discarded. Use for a clean slate, after rebuilding a native extension, or after a reload left stale classes behind (symptoms: `isinstance` mysteriously failing, a class missing a method you know it has). Also works when `execute` is stuck. A kernel this conversation created gets startup.py re-run and inspectors reinstalled (the reply says so); an attached kernel restarts bare, as found."
|
|
71
71
|
return await c.restart()
|
|
72
72
|
|
|
73
73
|
async def interrupt()->str:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Use the `clikernel` MCP session as the default workspace for Python work: reading and changing files, notebook work, trying things out, checking how a library behaves, and reshaping data. One session stays open, so imports and variables carry between calls. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
|
|
2
|
+
|
|
3
|
+
Prefer it over one-off Python scripts (`python -c`, shell heredocs). Prefer in-kernel tools over shell equivalents when they exist: file search and directory listing go through the `rgapi` pyskill (`rg()`/`fd()`/`ls()`), and GitHub and local git work through the `ghapi` pyskill, when those are installed. Shell commands remain the right tool for project test/build commands and non-Python tools.
|
|
4
|
+
|
|
5
|
+
# Starting and stopping
|
|
6
|
+
|
|
7
|
+
The first `execute` creates a kernel and reports what it imported: read that banner, it says what to do next. That kernel stops when the conversation ends. Use a bare `connect` instead when the kernel should stay running afterwards, and tell the user its id.
|
|
8
|
+
|
|
9
|
+
To continue earlier work, or to use the user's solveit kernel, `list_kernels` then `connect` with the id. Attaching runs no setup: the kernel's live state is the point.
|
|
10
|
+
|
|
11
|
+
`restart` gives a fresh interpreter under the same id, so redo any setup. `interrupt` stops a long `execute` and keeps state. If a reply says the kernel has stopped, `connect` again. If `connect` fails, the gateway server is not running: tell the user rather than working around it.
|
|
12
|
+
|
|
13
|
+
Remote gateways are the same verbs with a `host`, named in `~/.config/clikernel/gateways.toml`.
|
|
14
|
+
|
|
15
|
+
# Working in it
|
|
16
|
+
|
|
17
|
+
- Magics work as written. `%cd` expands `~` and is the way to change directory: prefer it over `os.chdir`.
|
|
18
|
+
- Only the last expression in a cell displays. `print(...)` any earlier value you need to see.
|
|
19
|
+
- Everything a cell outputs lands in the conversation. Be selective: `len(v)` first, then decide what to show.
|
|
20
|
+
- Don't re-run an `import` already run this session. If a name raises `NameError`, the kernel restarted or is newly attached: redo setup.
|
|
21
|
+
- After an `nbdev-export` (or any edit to a module already imported), `importlib.reload` that module and re-import any names you hold from it: a name bound by `from x import y` keeps the old object, while a `@patch`ed method refreshes with the reload because the patch writes onto the shared class. Restart only when a class you hold instances of was itself redefined. Check what is loaded by calling it, never with `inspect.getsource`, which reads the file on disk.
|
|
22
|
+
- Try the simple import or API call first, before changing the environment, monkeypatching, or adding setup.
|
|
23
|
+
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clikernel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
|
|
5
5
|
Author: clikernel contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,7 +12,8 @@ Requires-Python: >=3.11
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: fastcore>=2.2.2
|
|
15
|
-
Requires-Dist: jupyasyncclient>=0.2.
|
|
15
|
+
Requires-Dist: jupyasyncclient>=0.2.8
|
|
16
|
+
Requires-Dist: fastspec>=0.2.2
|
|
16
17
|
Requires-Dist: mcpmini>=0.0.1
|
|
17
18
|
Requires-Dist: aidialog>=0.0.7
|
|
18
19
|
Requires-Dist: pillow
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""Use the persistent `clikernel` MCP session as the default workspace for any task advanced through live Python execution -- stateful inspection, file-editing workflows, debugging, experiments, API probes, data transforms, or notebook-style work. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
|
|
2
|
-
|
|
3
|
-
# Core idea
|
|
4
|
-
|
|
5
|
-
clikernel connects this conversation to Jupyter kernels hosted by a gateway server (rustygate) that runs all the time, independently of any conversation. State lasts the whole conversation -- imports, live objects, monkeypatches, cached results -- and a kernel you `connect` explicitly persists across conversations too. Treat it as a notebook-style workbench, not a one-shot script runner.
|
|
6
|
-
|
|
7
|
-
Prefer it over one-off Python scripts (`python -c`, shell heredocs) whenever you need to inspect runtime behavior, test an idea, call a Python API, examine package state, run a live probe, or iterate on an implementation detail. Prefer in-kernel tools over shell equivalents when they exist: file search and directory listing go through the `rgapi` pyskill (`rg()`/`fd()`/`ls()`), and GitHub and local git work through the `ghapi` pyskill, when those are installed. Shell commands remain the right tool for project test/build commands and non-Python tools. Run them through the harness's shell tool, never `subprocess`/`os.system` from the kernel, which would bypass the harness's permission hooks.
|
|
8
|
-
|
|
9
|
-
# The lifecycle contract
|
|
10
|
-
|
|
11
|
-
The lifecycle has one implicit convenience and no implicit destruction beyond it. An `execute` with no kernel connected auto-creates one (default gateway, `startup.py` and inspectors as usual), and the connect banner arrives prepended to that first reply. That auto kernel is scoped to the conversation: it stops when the conversation ends, or the moment `connect` moves anywhere else. Kernels you `connect` explicitly -- created bare or attached by id -- are never stopped except by `stop_kernel`. The tools are self-documenting -- read each tool's MCP description -- and the shape of a session is:
|
|
12
|
-
|
|
13
|
-
- Start of work: just `execute` (the first call auto-connects; read the banner: it says what is imported and what to do next), or a bare `connect` when the kernel should outlive the conversation.
|
|
14
|
-
- Returning to earlier work (the user asks to continue where a previous conversation left off, or to use their solveit kernel): `list_kernels` to see what's running, then `connect` with the kernel id (or unique prefix). Attach runs nothing -- the kernel's live state is the point.
|
|
15
|
-
- End of work: an auto kernel stops itself with the conversation. `stop_kernel` an explicitly created kernel when it was for this task only; leave it running if the user wants to return to it, and tell the user its id so they can.
|
|
16
|
-
|
|
17
|
-
`restart` gives the current kernel a genuinely fresh interpreter under the same id (redo imports after it); `interrupt` stops a too-long `execute` while keeping state. If a reply says the kernel died, `connect` again. If `connect` fails because the gateway is unreachable, the gateway server is not running -- report that to the user rather than working around it.
|
|
18
|
-
|
|
19
|
-
Remote gateways (a rustygate or solveit instance elsewhere) are the same verbs with a `host`: a name from `~/.config/clikernel/gateways.toml` (`[gateways.<name>]` tables with `url`, `token` or `token_env`, and optional `verify = false` for self-signed TLS) or a URL. Tokens live in the config file, never in tool arguments.
|
|
20
|
-
|
|
21
|
-
# Notebook magics
|
|
22
|
-
|
|
23
|
-
`execute` runs IPython, so magics work as written. The `%nbrun` line magic (registered by aidialog, which the standard startup imports) runs cells from a `.ipynb` file by cell id prefix -- see `doc(dsk)` after startup for its options. It runs *in the kernel*, so its cells share session state and are checked by the session's inspectors.
|
|
24
|
-
|
|
25
|
-
`%cd` expands `~` and is the idiomatic way to change the kernel's directory: prefer it over `os.chdir`.
|
|
26
|
-
|
|
27
|
-
# Session setup
|
|
28
|
-
|
|
29
|
-
`connect` (creating) first runs `~/.config/clikernel/startup.py` (with `__file__` bound, so the file can locate its neighbors), then installs inspectors from `~/.config/clikernel/inspectors.py`. Both travel as source, so remote kernels get the same setup as local ones. Inspectors see each cell before it runs (1-arg: the AST; 2-arg: AST and raw source): a returned string prints as a note ahead of the cell's output, raising `RuleBlock` (provided in the file's namespace) blocks the cell, and inspector bugs warn and fail open. Attached kernels are taken as found: no startup, no inspectors.
|
|
30
|
-
|
|
31
|
-
# Output shape
|
|
32
|
-
|
|
33
|
-
Outputs are rendered with `fastcore.nbio.render_text`. A single non-empty output comes back as its preferred text form, e.g. `42`; multiple outputs use readable XML-ish tags (`<stdout>`, `<execute_result>`, ...) with raw, unescaped body text. Exceptions come back as one clean traceback: ANSI-stripped, over-long lines capped at 180 characters with `File `/`Cell ` locations and the exception message always whole. `input()` fails fast in-band (`allow_stdin=False`) rather than hanging. Image outputs (plots etc.) arrive as MCP image blocks, resized to a token-friendly budget, each preceded by a `<media id=...>` text tag naming it; when the image cannot be delivered, a `<media-unavailable>` note appears in the text instead.
|
|
34
|
-
|
|
35
|
-
# Interaction rules
|
|
36
|
-
|
|
37
|
-
- Try the simple import or API call first, before mutating environment, monkeypatching, or adding setup.
|
|
38
|
-
- Like Jupyter, only the *last* expression in a cell displays. `print(...)` any earlier value you need to see.
|
|
39
|
-
- Don't re-run an `import` already run this session. If a previously-imported name raises `NameError`, the kernel restarted or is newly attached -- redo setup.
|
|
40
|
-
- `importlib.reload` is not always enough: `from x import *` consumers and `@patch`-decorated classes hold stale references. On stale-class symptoms, use the `restart` tool.
|
|
41
|
-
- Everything a cell outputs lands in the conversation. Be surgical: `print(len(v))` first, then decide what to show.
|
|
42
|
-
- A kernel is shared by anything connected to it: subagents in this session, or the user's own client. Assume shared state is a feature, not a surprise.
|
|
43
|
-
|
|
44
|
-
# Pyskills
|
|
45
|
-
|
|
46
|
-
This environment commonly has `pyskills` installed. When present, check it first and prefer a relevant pyskill over ad hoc code:
|
|
47
|
-
|
|
48
|
-
from pyskills import list_pyskills, doc
|
|
49
|
-
import pyskills.skill
|
|
50
|
-
doc(pyskills.skill)
|
|
51
|
-
|
|
52
|
-
# The stream protocol
|
|
53
|
-
|
|
54
|
-
Driven as a plain CLI process (`clikernel [--host H] [--kernel K]`), the same client speaks a delimiter-framed stdin/stdout protocol, documented in its own startup banner and the README. Run bare it creates a kernel and stops it on exit; `--kernel` attaches and leaves it running.
|
|
55
|
-
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|