Skip to content

Debugger and Inspector

The optional kedi-debugger package serves both editor extensions through DAP. It stops at Kedi statements and procedure boundaries while leaving the existing runtime, adapters, approval policy, and concurrency in charge of execution.

Source-checkout feature

Install the matching local runtime, debugger, and editor extension. These changes are not an automatic Marketplace, Zed registry, or PyPI release. Older Kedi builds do not contain the required kedi.debugging hooks.

One Interpreter

In managed mode, the updated extensions install the debugger automatically into the shared ~/.kedi/editor-venv, including upgrades of older managed environments. The installer validates both the debugger package and the required runtime hooks. This path requires matching published packages; the source-checkout warning above still applies until release.

For a selected host interpreter or unpublished local development, install explicitly:

python -m pip install -e /path/to/kedi
python -m pip install --no-deps -e /path/to/kedi/debugger
python -c 'import kedi_debugger; from kedi.debugging import DebugEvent, observe_execution'

The shared managed environment is ~/.kedi/editor-venv. Existing VS Code and Zed host-Python overrides also govern debugging. Host interpreters are not modified automatically. Project dependencies must be installed in that same environment.

Start Without a Model

Save this as debug.kedi, put a breakpoint on the assignment inside double, and launch it through the editor's debugger:

@double(value: int) -> int:
  [result: int] = `value * 2`
  = `result`

[number: int] = `21`
[answer: int] = `double(number)`
> show: The computed answer is <answer>.

Inspect value and result in Locals, navigate the Kedi stack, step out, and continue. The example makes no model calls. The debug console displays normal program output; it is not a Python REPL.

VS Code

Use Run and Debug with the Kedi launch configuration:

{
  "version": "0.2.0",
  "configurations": [{
    "type": "kedi",
    "request": "launch",
    "name": "Debug Kedi",
    "program": "${file}",
    "cwd": "${workspaceFolder}",
    "stopOnEntry": true
  }]
}

The extension requires workspace trust. Select the interpreter using the existing Kedi command or settings described in VS Code.

Zed

Use a Kedi debugger task with adapter kedi and the same launch fields. The updated extension delegates to python -m kedi_debugger --stdio using the same Python selection as its language server. See Zed for host setup. Zed reserves the task's adapter field for the debugger name, so use kediAdapter for an optional model-framework override. For example, in .zed/debug.json:

[
  {
    "label": "Debug Kedi",
    "adapter": "kedi",
    "request": "launch",
    "program": "$ZED_FILE",
    "cwd": "$ZED_WORKTREE_ROOT",
    "stopOnEntry": true
  }
]

Save the file and trust the project before starting. The pinned Zed extension API does not expose the VS Code-style trust or unsaved-buffer callbacks.

Launch Environment

Both editors load the nearest .env from the launch cwd or its parents before the worker imports Kedi or selects a model. Without cwd, discovery starts at the program's directory. Only the nearest file is loaded. This includes API keys, KEDI_ADAPTER, KEDI_ADAPTER_MODEL, and MODEL_NAME, even if the editor was started outside a terminal.

Launch env entries override the inherited process environment, which overrides .env values. null removes a variable from both sources; "" keeps an empty value. Dotenv quoting and ${NAME} interpolation are supported. Explicit launch model and adapter settings override environment defaults. These rules apply to both VS Code and Zed. Keep credentials out of committed launch configurations.

To skip dotenv discovery, set PYTHON_DOTENV_DISABLED=1 in launch env or the inherited environment. The debugger sets this flag in the worker after loading its environment so later automatic dotenv loads cannot restore removed keys or load unrelated credentials. Restart the debug session after editing .env. Known loaded credentials are masked in debug output and inspection. An invalid or unreadable .env fails launch without exposing its contents; a missing file is allowed.

Model Boundaries

A model-backed program uses the usual Kedi directives:

> model: openai:gpt-5.6-luna
[country] = Sweden
>> The capital of <country> is [capital].
> show: The capital is <capital>.

Enable the Before model dispatch and After model result event filters in the debugger's breakpoint controls. Their IDs are model_input and model_result. The first stop shows the prepared input; the second shows the result and available request/usage observations. A request-transforming hook can change the assembled request, so inspect request for the actual Kedi request rather than treating the earlier input as a provider wire transcript.

Waiting on a capture does not stop its independent producer. Continue and step commands resume paused peer lanes by default, so a capture can receive its result. With explicit single-thread stepping, a paused producer needs its own continue command. The debugger does not serialize parallel work or resend a request after resume.

Request assembly and nested tool execution inside a model transaction are observation-only: they cannot pause while holding that transaction's resources. A propagated tool error can stop after those resources are released. Exceptions from parallel model jobs also reach the exception filter.

Inspection and Privacy

Source snapshots are frozen for the session. If a source changes before starting or resuming, restart the debugger to run the edited program. Known environment secrets are masked in source responses without shifting their line positions.

Snapshots are bounded and detached from live objects. Completed captures expose their existing values and typed fields without resolving or calling the model again. Pending promises are not forced; unknown Python objects remain opaque. Large values do not consume the slots reserved for later sibling bindings. If a scope exceeds its snapshot limits, a <snapshot> notice explains the partial view. A resolved promise shows the memoized value actually read by the program, even if the shared model-result mapping subsequently changes. Names and captured paths such as answer, record.name, or items[0] can be watched. Calls, assignments, operator evaluation and dynamic property access are rejected. Resuming invalidates that lane's inspection handles. Recent changes compare captured values only, not all possible mutations of arbitrary Python objects.

Distinct dictionary keys such as 1 and "1" can share a displayed name. Watch evaluation rejects those ambiguous paths instead of selecting an arbitrary value; inspect their entries in the variables view.

Token usage counters, cache/reasoning token details and token limits remain visible; the word token in a metric name does not make it a credential. Access/refresh tokens, API keys and sensitive fields inside usage containers remain redacted. Known environment secret values are masked even under otherwise public field names.

Known environment secrets and sensitive fields are redacted, but unknown secrets inside prose cannot be guaranteed absent. No persistent transcript or Logfire exporter is configured. Treat screenshots and console output as potentially sensitive. The older executor debug exporter has a different privacy contract and is not this inspector.

Limits and Lifecycle

Pause is cooperative. Already-running provider calls and their deadlines continue. Embedded Python blocks are opaque; blocking Python may require Terminate. The supervisor remains responsive and owns only its worker/process descendants. Disconnect terminates the launched worker, not unrelated Python processes.

Use Restart/Rerun after saving source or .env changes. The editor ends the old adapter session and launches a fresh one; the DAP restart shutdown flag is accepted. This is a fresh execution, not hot reload. Source edits detected while paused produce an explanatory console message instead of resuming stale code.

Thread names stay stable; DAP events carry running, stopped and exited states. Capture waits use progress events when the editor supports them and end when the wait or run finishes. Already-completed captures do not announce a wait. Normal completion reports exit code 0, ends all thread/progress states, and then terminates the session. A closed session is not by itself a program failure.

Stepping stops before the next eligible Kedi statement, so assignments become visible at the following stop. Internal model observations do not create extra same-line steps unless their event filters are enabled. Editors may clear Variables after completion; stop before the final return to inspect values.

Source edits require restart. There is no remote attach, notebook/REPL debugging, reverse stepping, variable mutation, arbitrary Python evaluation, terminal input, or async subagent-internal stepping. Tools still obey approval policy; DAP does not provide an interactive terminal for approval prompts.