Skip to content

Executors and Debug Exporters

An executor owns embedded-Python execution. Replacing it must preserve environment, return, and error contracts; an interface stub is not a secure sandbox.

Custom Executors

Implement the Executor protocol from kedi. This interface sketch is not an implementation and must not be used as a sandbox:

from typing import Any, Callable
from kedi import Executor, ExecutorDebugExporter, KediRuntime


class RestrictedExecutor:
    def set_debug_exporter(
        self, exporter: ExecutorDebugExporter | None
    ) -> None: ...

    def evaluate_inline(
        self, rt: KediRuntime, code: str, local_env: dict[str, Any]
    ) -> Any: ...

    def execute_block(
        self, rt: KediRuntime, code: str, local_env: dict[str, Any]
    ) -> Any: ...

    def execute_side_effects(
        self,
        rt: KediRuntime,
        code: str,
        env_map: dict[str, Any],
        *,
        kedi_line_offset: int = 0,
    ) -> None: ...

    def execute_prelude(
        self, rt: KediRuntime, code: str
    ) -> dict[str, Any]: ...

    def create_dynamic_function(
        self,
        name: str,
        params: list[str],
        body: Callable[..., Any],
        defaults: dict[str, Any] | None = None,
    ) -> Callable[..., Any]: ...

    def evaluate_type_expression(
        self, rt: KediRuntime, code: str, env: dict[str, Any]
    ) -> Any: ...

The protocol is runtime-checkable. A custom executor must preserve Kedi's environment and return semantics, not merely evaluate isolated strings.

Incremental sessions additionally need execute_prelude_incremental(rt, code, env) to extend an existing Python environment and return its updated mapping. The env argument is an existing mapping or None. An executor that only implements Executor cannot run incremental preludes; the runtime raises an error rather than silently restarting the environment. DefaultExecutor supports this operation.

Default Executor

DefaultExecutor uses Python eval and exec:

import asyncio
from kedi import DefaultExecutor
from kedi.lang import compile_program, parse_program

program = parse_program("= `2 + 3`")
runtime = compile_program(program, executor=DefaultExecutor())
try:
    assert runtime.run_main() == 5
finally:
    asyncio.run(runtime.aclose())

It is not sandboxed. Embedded Python has the host process's authority, imports, filesystem access, network access, and credentials. Use a specialized executor and operating-system isolation for untrusted Kedi source.

Debug Exporters

Attach a Markdown event exporter when compiling the program above:

from kedi import DefaultExecutor, MarkdownDebugExporter

executor = DefaultExecutor(
    debug_exporter=MarkdownDebugExporter("runtime-debug.md")
)
runtime = compile_program(program, executor=executor)

Events include executor step, code, inputs, local environment, outputs, and errors. Sanitization makes values printable; it does not redact secrets. Debug exports can contain prompts, credentials, user data, and tool results. Store and share them accordingly.

default_debug_export_path("program.kedi") creates a timestamped path in the current working directory.