Kernels
Overview
The kernels feature provides a runtime execution layer for code entities.
It supports:
- A built-in immutable kernel with
id = 0for direct Fennel evaluation. - User-defined process kernels that are launched as subprocesses.
- ZMQ request/reply execution for subprocess kernels.
- Multiple live instances per process kernel.
- Graph-native management (list/detail/instance nodes + views).
The runtime is exposed as app.kernels.
Current Status
As of February 20, 2026, the kernels feature is implemented and integrated.
Validated:
- Kernel CRUD semantics (including immutable kernel
0and generated UUID ids). - Async
run-codepath for internal and process kernels. - Subprocess integration against
assets/python/subprocess_kernel_launcher.py. - Kernel selection behavior with multiple running instances.
- Graph integration (
kernels/kernel/kernel-instancenodes and views).
Latest test outcome:
assets/lua/tests/test-kernels.fnl: all tests passing, including subprocess launcher integration.- Full suite (
make test) passing in the current workspace.
Goals
- Let code entities execute asynchronously through a selected kernel.
- Keep kernel definitions persistent and editable.
- Support multiple running instances of the same kernel.
- Keep integration graph-first, so kernels are visible and operable through node views.
Non-goals
- Notebook execution (notebooks currently do not define execution semantics).
- Streaming incremental output in the code-entity UI (current UX shows final async result/error text).
Runtime Module
Implementation lives in assets/lua/kernels.fnl.
Main responsibilities:
- Kernel definition persistence/load.
- Process instance lifecycle management.
- ZMQ socket creation and polling.
- Async code dispatch and callback delivery.
- Per-kernel log writing.
Kernel Definition Model
Definition fields:
id: string UUID for user kernels, numeric0for internal kernel.name: optional display/lookup name.cmd: process launch command (process kernels only).cwd: optional process working directory.internal: runtime-only flag for kernel0.
Kernel 0 is immutable:
- Cannot be edited.
- Cannot be deleted.
- Cannot spawn process instances.
User kernel IDs are always generated by the runtime:
- Callers cannot provide custom IDs when creating kernels.
Persistence
Kernel definition directory:
user-data-dir/space/kernels
File format:
- One JSON file per kernel.
- Filename:
<id>.json - Stored fields:
id,name,cmd,cwd
Runtime sidecar directories:
user-data-dir/space/kernels/runtimefor connection files.user-data-dir/space/kernels/logsfor kernel log files.
Per-kernel log path:
user-data-dir/space/kernels/logs/<kernel-id>.log
Instance Model
Each process-kernel run creates a distinct instance with its own:
- Instance id (UUID).
- Process id (spawned via
process.spawn). - Connection file path.
- ZMQ context/socket.
- Request queue.
- Status/error metadata.
Statuses:
startingrunningstoppingstoppederror
Process + ZMQ Protocol
Process launch:
- Uses
process.spawnwith args["/bin/bash", "-lc", <cmd>]. - Injects
KERNEL_CONNECTION_FILEin env. - Optional
cwdis honored.
Connection bootstrap:
- The launched kernel writes an endpoint JSON to the connection file.
- Runtime polls until endpoint appears (startup timeout enforced).
- Runtime opens ZMQ
REQand connects to endpoint.
Request payload (JSON string sent over ZMQ):
code: source string.registers: JSON-encoded register table.catch_errors: boolean.
Response payload (JSON string from subprocess kernel):
output: stdout payload.error: error payload.registers: JSON-encoded register table.
Stop handshake:
- Runtime sends JSON string
"STOP"to subprocess kernel. - Process is terminated if it does not exit within the stop deadline.
Instance Selection Policy
When executing code against a process kernel, runtime picks:
- The most recently created running instance for that kernel.
Algorithm:
- Filter instances for matching kernel id and
status == "running". - Select the last one in creation order.
- If no running instance exists, execution fails with an error.
This policy is intentional and currently documented behavior.
Code Entity Integration
Code entity store (assets/lua/entities/code.fnl) now persists:
kernelfield, defaulting to0.
kernel is a single selector and can be:
- A kernel id (
numberor id string), or - A kernel name (
string).
Code entity node (assets/lua/graph/nodes/code-entity.fnl) supports:
- Updating kernel selector.
- Setting kernel by graph selection (exactly one selected kernel node).
- Async run via
app.kernels:run-code. - Emitting run-result signal with a single display string.
Code entity view and preview:
- Both expose a Run action.
- Both expose a single result/error label.
Graph Integration
New nodes:
kernelslist node.kernel:<id>detail node.kernel-instance:<instance-id>instance node.
New views:
assets/lua/graph/view/views/kernels.fnlassets/lua/graph/view/views/kernel.fnlassets/lua/graph/view/views/kernel-instance.fnl
Key loader registrations in assets/lua/graph/key-loaders.fnl:
- Exact key:
kernels - Prefix:
kernel: - Prefix:
kernel-instance:
Start node now includes kernels target.
Launchable added:
assets/lua/launchables/kernels.fnl
Internal Kernel (id=0)
Kernel 0 evaluates Fennel source directly using fennel-evaluator.
Behavior:
- Runs asynchronously via callback scheduling.
- Returns formatted output on success.
- Returns formatted error text on failure.
- No process spawn.
- No ZMQ/socket usage.
Error Handling
Representative error cases:
- Unknown kernel id/name.
- Ambiguous kernel name.
- Attempted kernel delete while active instances exist.
- No running instance for selected process kernel.
- Spawn failures.
- Startup timeout waiting for connection file.
- ZMQ send/recv parse errors.
- Unexpected process exit during startup/run.
Errors are surfaced back to callers as callback payloads and mirrored into kernel logs.
Logging
Per-kernel log includes:
- Timestamped lifecycle events (spawn/start/connect/exit/stop).
- Captured subprocess stdout.
- Captured subprocess stderr.
- Selected request lifecycle events.
The code-entity UI itself displays only final run text; detailed traces remain in per-kernel log files.
Current Limitations
- Process-kernel command parsing is shell-based via
/bin/bash -lc. - No UI-level instance pinning per code entity yet.
- No round-robin or load-balancing policy across instances.
- No partial-output streaming in code-entity views.
Testing
Added test module:
assets/lua/tests/test-kernels.fnl
Covers:
- Create/update/delete definitions.
- Internal kernel immutability.
- Internal kernel execution callback behavior.
- Duplicate-name ambiguity errors.
Updated tests:
assets/lua/tests/test-code-entities.fnlforkernelpersistence/default.assets/lua/tests/test-graph-loaders.fnlfor kernels loader coverage.
Suggested Next Validation
Completed:
- End-to-end subprocess execution validation with
assets/python/subprocess_kernel_launcher.py.
Recommended ongoing validation:
- Stress run with multiple concurrent instances of the same kernel.
- Longer-running request queues to verify fairness and stability over time.
- UI-driven regression passes for kernel management views after layout changes.
Possible Improvements
Refactor
assets/lua/kernels.fnlinto smaller modules. Reason: lifecycle, protocol, persistence, and dispatch logic are currently dense in one file.Add multi-instance stress and soak tests. Reason: current integration test validates correctness, but not sustained high-load behavior.
Add explicit observability counters for queue depth and per-instance latency. Reason: simplifies diagnosing production/runtime stalls beyond logfile inspection.
Add optional code-entity instance pinning. Reason: current policy intentionally uses the latest running instance; pinning would allow deterministic routing when desired.
