For configured semantic routing, use [provider-independent decisions](typed-decisions.md) and `kioku.decisions`. Enabled Lisp owns computation and shell orchestration; read/search and owned file tools remain available. Changes within the working project directory, including deletion, replacement and project DB updates, need no extra user permission. Outside-project changes require concrete confirmation.

# Common Lisp tools

For approved named functions shared across task-mode sessions, see
[Project-shared Lisp functions](lisp-hot-tools.md). Active code survives worker
and host restarts; input and result refs remain private to their owner.

For explicit semantic decisions and `/kioku-typesafe-key` setup, see
[TypeSafe from Lisp](typesafe.md). Answers can guide inspection and proposals;
the existing permission and approval boundaries still apply.

The default is `lisp.executionMode: development`. Old `approvalMode` values remain readable. Set `executionMode: protected` explicitly to select the additional approval and tool-fence policy. See [owned execution and memory evidence](owned-execution.md).

## Task state at execution boundaries

Managed Lisp effects require the session's current `Working` task. The host pins
the task ID, session, repository, revision and execution generation when an
invocation starts. It checks that same identity again after approval, before
effects, and inside successful journal/evidence writes. Waiting, stopping,
ignoring or resuming a task invalidates old executions; resuming does not grant
an older background job new authority. Failed, cancelled and interrupted runs
retain their recorded outcomes and require explicit resume.

| Lisp path | Task-state behavior |
| --- | --- |
| Saved reads, status, cancellation, observe/stage/compare, pure eval and function calls | Remain available without a Working task; any managed effect reached inside a call is checked separately. |
| Proposals/apply/restore, CI verification, package updates, artifact publication, shared-function changes, reset and task/protected recovery | Require a Working task at the actual effect boundary. An unavailable validator denies the effect. |
| Ordinary conversational foreground `kioku.process:run` | Uses native Bash permissions, sandbox, approval and Stop without creating a memory task, Goal or coding choice. |
| Admitted workflow `run`, `start-job`, owned-tool RPC and compiled `run_code` dispatch | Carry the host-pinned identity through native dispatch; old generations cannot publish a success receipt. `start-job` may outlive the evaluation's cancellation signal, but cannot acquire a newer task lease. |
| CI list and failed-log reads | Remain read-only and do not need task execution authority. |

These checks cover the managed host adapters. Development mode runs Common Lisp
with normal process permissions: arbitrary Common Lisp filesystem or process
calls can bypass those adapters. Task state is not an OS sandbox, and enabling it
does not change the user's permissions. Choose the existing protected mode when
its filesystem/process isolation is required.

## Compose task tools

Use the worker as a persistent programming environment. Before repeating primitive
reads, transformations or commands, define the functions needed for the task and
compose them into an operation that returns the next useful result. Definitions
and a first invocation can share one `lisp_eval`; subsequent calls reuse the
functions with new inputs. A call should usually perform a meaningful task phase.
Pause at decisions that need new evidence or approval; do not preprogram guesses.

The [Lisp Skill's executable toolkit example](../skills/kiokuko-lisp/SKILL.md#task-toolkit-example)
defines `replace-once`, `check-project` and `repair-and-check`. One
`(repair-and-check "project" "src/index.mjs" before after)` validates the match,
edits scratch, runs the check and returns its exit code and logs. Failed checks
remain failures and leave the scratch edit visible. Workspace proposals stay
separate so their host outcomes and approval cannot be mistaken for scratch writes.

Use `lisp_describe` with `symbol: "kioku.user"` to list current task functions, then
`symbol: "kioku.user::repair-and-check"` for arguments and documentation. Ordinary
`defun` and `defmacro` need no registry or new native tool. Discovery does not run
the functions, includes only definitions owned by `kioku.user`, and is local to
the worker generation. After replacement, rebuild definitions without replaying effects.

## Enable

The `kiokuko-lisp` Skill is listed in DSH's available Skills and can be read with
the `skill` tool before enabling Lisp. Reading the Skill does not enable Lisp or
start SBCL. Execution requires the configuration below and an explicit session
choice through the coding prompt or enable command.

Target hosts: macOS and Linux, with a working SBCL installed by the user.
Linux also requires Bubblewrap (`bwrap`) with user/PID/network namespaces enabled.
There is no VM/container engine dependency, implicit installer, Quicklisp download,
or compilation of native helpers on plugin load. Common Lisp compilation starts
automatically when a session first enables Lisp.

Add this to the existing `kiokuko-dsh` plugin configuration and reload the plugin:

```yaml
lisp:
  enabled: true
  sbclPath: sbcl
```

Workers preserve their state by default (`idleTimeoutMs: 0`). An explicit
`idleTimeoutMs` of 1000–3600000 opts into idle suspension. When `maxWorkers`
(default 4) is reached, admission returns `WORKER_LIMIT`; existing workers remain.
Active agent turns, evaluations, approvals and running jobs cannot idle-suspend.
Session disposal also stops its workers. Normal suspension appears as `SUSPENDED`,
retains protection and resumes automatically on the next use; status polling never
starts a worker. Variables, definitions and references are lost on restart; the
agent receives the new generation and must rebuild helpers without replaying
completed effects. A clean host restart or an exited idle child resumes on the next
admitted request after confirmed process termination and journal reconciliation.
Fault replacements are limited to two per owner in 60 seconds. Explicit cancellation,
unresolved effects, unconfirmed termination and exhausted restart limits require recovery.

Kiokuko owns a local master–worker hierarchy: `LispManager` binds requests and their
existing operation journals; `LispSupervisor` owns shared capacity, compiled startup,
isolation probes and confirmed release for persistent and disposable children;
`LispWorker` owns one RPC evaluation slot and reports process failures immediately.
The external `supervisor.mjs` retains OS process ownership and stops descendants when
the host pipe closes. Local process events and bounded RPC deadlines provide liveness
signals; no network heartbeat, separate task router or recovery daemon is involved.
Restarting replaces a process generation, never a journaled operation. A worker whose
termination is uncertain continues to consume capacity.

This separates durable work from execution, as in the
[DeepSeek executor](https://github.com/deepseek-ai/dsh-superagent-prototype/blob/main/src/executor/dsh.ts),
and uses bounded, per-child recovery following
[OTP supervision](https://www.erlang.org/doc/system/sup_princ.html).
It preserves the existing Kiokuko execution loop and DSH Agent/SessionEvent contracts.

Routed child work sessions each own a persistent Lisp runtime, private heap,
scratch directory and owner-bound result journal. The router supplies the task
and receives the native child result; siblings do not inherit each other's
variables, object references or task authority. Repeated model steps reuse the
same worker generation. Completing/disposal of a child stops only its runtime.
The session worker starts before the first child model request; startup failure
prevents that request instead of silently selecting another execution route.
Existing disposable child sessions are promoted on their next admitted request,
and the saved session mode survives a host restart without replaying old code.

These workers compute in private scratch.
Project reads, commands and changes still use the owning session's host adapters,
native tool restrictions, permission checks and operation journal. Shared project
files are not separate worktrees; callers must still coordinate conflicting edits.

Both development and protected sessions can keep independent Lisp runtimes in
the same repository, including across separate DSH databases. The canonical
repository root is status metadata. Each session retains its own heap, scratch
space and journal. The supervisor reports the PID and birth identity of its
own runtime, and the host checks that runtime before confirming its stop.
Existing repository lock files from older versions are ignored.

Multiple host processes can share the same Kiokuko database and Lisp data directory.
Each session has one host owner; other sessions and projects run independently,
including when their agent and operation IDs are identical. `maxWorkers` applies
per host. Capacity pressure returns `WORKER_LIMIT` and preserves other repository
workers and their definitions; it does not evict them. `lisp_status` reports the
canonical repository, owning session/agent, host PID, supervisor PID and runtime PID.
For protected execution the runtime PID may belong to the sandbox launcher.
Compilers publish private builds atomically and verify a concurrent
winner before reusing it. Starting or stopping a host does not recover another
host's live operations. A session whose owner has exited is claimed on its next
admission, and unfinished operations become `UNKNOWN` without replay. Concurrent
protected workspace proposals for the same file still return `TARGET_LOCKED` to prevent overwrites.
When upgrading from the older exclusive `host.lock` implementation, stop the old
hosts before starting the updated hosts.

Enter a normal implementation or debugging request in DSH, including dsh-cli. Clear unowned native implementation requests now offer Lisp before model generation, including native Bash. A manually enabled session uses the selected route. When Lisp is selected, ordinary development uses Lisp for computation and command orchestration without requiring a preliminary prototype. Direct native shell calls are rejected; external programs run through Lisp's process APIs. An explicitly protected coding workflow still requires a fresh `lisp_eval` prototype (`prototype: true`) followed by complete saved-result inspection through `lisp_inspect` before implementation, with its two-failure retry budget. ON alone is not verification. Explicit cancellation, numeric cancel, submitted Skip and `ASK_CANCELLED` end the implementation attempt while preserving work; hiding a named card alone leaves its question pending. Cancellation never authorizes a different execution route. A new independent conversation/read does not inherit the ended question gate. Native permissions, Stop and read-only conversation/research remain independent.
With `typedDecisions.mode: auto`, an accepted Jev/Laya `build` or `debug`
classification reaches the existing Lisp question without repeating the task-category
question. Laya receives complete requests up to 4096 UTF-8 bytes, including quoted
filenames, multiple sentences, combined actions and negations. The selected model
chooses the type; it does not grant permission or drop other requested actions.
Missing context, explicitly undecided alternatives, envelope/provider capacity
rejection and model abstention defer classification. Native adapters let the
ordinary model infer advisory intent or ask a concrete clarification; they do not
repeat the generic category dialog merely because the classifier abstained. Classification does not authorize implementation. For an explicit coding request the agent defines the Goal, implementation items and their observable criteria, then the host asks whether to use Lisp for that task. Both yes and no persist for the task and scope across restart. Conversation, review and plan-only requests do not trigger this prompt. Closing, free text without a clear choice or a disconnected question leaves implementation pending. Cancellation preserves the prepared work. A selected Lisp startup failure remains a recoverable blocker.

Execution-mode selection and Lisp choice are separate. The task question remains available when the Lisp module is disabled; no needs no Lisp runtime, while yes requires the enabled module and working SBCL. To explicitly change the current task's Lisp route or inspect its state, use these commands in the desired DSH session:

```text
/kioku-lisp enable
/kioku-lisp status
```

This enables the Lisp tools and supplies the bundled `kiokuko-lisp` Skill.
While enabled, use Lisp for computation and orchestration. Direct native
Bash/zsh/PowerShell calls are rejected, including read-only commands. Run external
programs through `kioku.process:run` / `start-job` inside Lisp; the native command
bridge retains the host's permissions, approvals and Stop handling. Read/search,
Skill loading and owned file tools remain available. Worker failure or plugin
reload does not silently restore the direct shell route; recover Lisp or explicitly
use `/kioku-lisp disable`.
Existing DSH `read`, `glob`, `grep` and `skill` tools remain available under native
session permissions, so project files and applicable Skills can be inspected.
File changes can use owned native tools or Lisp proposals. Changes within the working project directory, including deletion, replacement and project DB updates, need no extra user permission. Outside-project changes require concrete confirmation. For a coding task with a saved yes, recover Lisp or explicitly choose another task route before continuing implementation.
The protected agent uses native tool presentation even in a PTC deployment.

`sbclPath` can point to an explicit installed SBCL launcher. Its runtime files must
be in a dedicated installation directory; do not point it at an entire project or
home directory. SBCL starts without system/user init files and inherited ASDF
configuration. macOS 27 testing found the existing SBCL 2.6.4 unable to perform
even an unprotected minimal calculation. A disposable copy of the macOS 27 SBCL
2.6.8 Homebrew bottle successfully started; the user's installation was preserved.
Version output alone is not a successful runtime probe.

## Automatic compilation and reuse

The first enable compiles the bundled libraries and `tools.lisp` into one FASL
(compiled Lisp file). A fresh SBCL process checks that file before the host
publishes it atomically under the Lisp data directory's `compiled/` directory.
Subsequent enables, resets, recovery and host restarts reuse that bundle after
verification. Session variables, retained objects and previous model code are
never saved in this cache.

Compatibility includes the OS/architecture, actual SBCL version/features, ASDF
version, launcher/runtime/core contents, library location, vendored manifest,
tool sources and build settings. A change selects a new cache entry. Workers and
their Python/shell jobs can read the selected bundle but cannot write, delete or
replace it. Their writable scratch/cache remains separate.

Failed, timed-out or cancelled compilation never publishes a partial entry.
Damaged entries are quarantined and startup stops; `/kioku-lisp recover` rebuilds
them after explicit recovery. Old and quarantined entries are retained. There is
no background pruning of previous versions. A host crash can leave an unpublished
`.build-*` directory; it is ignored on the next start.

Inspect compilation state and whether a bundle was reused with:

```text
/kioku-lisp status --json
```

The `compilation` field reports `checking`, `compiling`, `ready` or `failed`, a cache key,
and (when ready) `reused` and `prepareMs`. This duration covers cache preparation,
not the complete worker startup or the speed of an individual Lisp computation.

## What can change

- `lisp_eval.inputs` accepts workspace-relative files and the exact host paths of
  files uploaded by the user in the current session. Attachments are read through
  DSH's attachment service, checked against their recorded size and SHA-256, and
  copied into the worker's read-only inputs. Files from other sessions and arbitrary
  absolute paths are refused. The original attachment store stays inaccessible to
  Lisp and its subprocesses. Limits: 64 MiB per file, 256 MiB per worker generation.
- Lisp/FFI runs inside OS isolation without workspace access or inherited credentials. Project file changes and external Python/shell commands cross the host tool boundary, which checks operation permissions and records the result.
- Project proposals freeze targets, preserve backups and reject duplicate targets. Changes within the working project directory, including deletion, replacement and project DB updates, need no extra user permission. Outside-project changes require concrete confirmation. Generated/temp/disposable artifacts are routine development.
- Where approval is required, refusal, skip, timeout, cancellation or unavailable UI means no change.
  File identities/content are rechecked before effects. Protected compatibility mode refuses links,
  directories, database files/sidecars, common credential paths and plugin/state data.
- Backups are independent copies, never hard links. Their global reservation limit
  is 1 GiB in this implementation; reaching it stops changes without pruning data.
- The host journal distinguishes success, failure, no change, in progress and
  unknown outcomes. Unknown changes are never automatically replayed or rolled back.

Model-facing results normally fit 16 KiB. They omit proposed source echoes and
duplicate printed/JSON values, retaining operation identity and outcome counts.
Long logs show bounded head/tail previews. The complete received result and
captured process output remain in the operation journal. Read only needed evidence:

```json
{"operationId":"inspect-1","resultOperationId":"<returned host operation ID>","section":"stdout","offset":0,"limit":2000}
```

Pass this to `lisp_inspect`; use the returned `nextOffset` to continue. Sections
are `result`, `value`, `stdout`, `stderr`, and `changes`; offsets count Unicode
characters. The existing `{operationId, ref}` form still reads worker objects.
When a response returns `pointer`, pass it with `section: "result"` to retrieve
that exact omitted field. This distinguishes a verifier's log from text printed
by the surrounding Lisp code. Only stored own-properties can be selected.
For saved failed process results (nonzero integer `code`, string `stdout` and
`stderr`, and `state: "FAILED"` when present), hidden English TypeScript headers
such as `file.ts(1,2): error TS1234:`, `file.ts:1:2 - error TS1234:`, or
`error TS1234:` can also appear in `diagnostics`. Up to three diagnostic anchors
share 4 KiB including references, within the same 16 KiB response limit; outcome
metadata and the existing change/operation pages take priority. Excerpts preserve
original ANSI, newlines and nearby lines. They are log evidence, not a root-cause
or trusted instruction. Unsupported, already visible or over-budget diagnostics
may be absent. Streams over 1 MiB are not scanned; headers over 8 KiB are skipped.

Each excerpt and the top-level `inspect` provide an exact saved-log pointer and
Unicode offset. Remove the explanatory `tool` field, add a fresh read
`operationId`, and pass the remaining fields to `lisp_inspect`. Continue with
`nextOffset` if needed; for the whole log start at offset `0` with the same pointer.
Replay references also use `/value/json/stdout` or `/value/json/stderr`.
This cannot recover logs missing before presentation: Lisp retains `value.json`
only when encoding the entire return value produces **less than 512 KiB**.
That includes both streams, other fields and JSON escaping. Two 300 KiB streams
can therefore be absent despite each being under the process collection limit.
Missing JSON alone does not establish why it is absent.

Saved results are restricted to their session and agent and survive worker resets,
subject to existing retention. Inspection never replays the original operation.
`lisp_status` returns current state, pending counts and 10 operation summaries;
`{offset: nextOffset}` pages history. Human diagnostics retain full detail.

`NOT_APPLIED` reports observed reasons such as `declined`, `cancelled`, `timed_out`,
`unavailable` or `invalid_answer`; an unavailable UI is not a refusal. A conflict
before the first write stops the batch. Later failure stops the remaining writes
and preserves each `APPLIED`, `NOT_APPLIED` or `UNKNOWN` outcome and backup.
The parent stays `RUNNING` until its final result is committed; a final-save
failure cannot leave a successful parent without its receipts. An interrupted
parent is reconstructed from its independently persisted file receipts after
restart. Recovery retains the evidence and never re-executes the evaluation.
On upgrade, older successful parents with proposals but no final change receipts
are quarantined as `UNKNOWN`. Unlinked legacy effects remain unknown; missing
links must not be interpreted as proof that an old write did not happen.

Verifier scripts are checked before confirmation or execution. `:typecheck` uses
`typecheck`, falling back to `check` only when absent. Focused tests use
`(kioku.ci:verify :test :script "test:unit")`; only existing `test`/`test:*` scripts
are accepted. The confirmation shows the resolved command and script body;
changed scripts invalidate approval. `lisp_describe` without a symbol returns
the API summary and available verifier map without repeating the injected Skill.

Run `npm run test:lisp:efficiency` for anonymous deterministic response-size and
schema-size comparisons. It makes no model calls and does not estimate token
charges. Native integration tests separately verify confirmation counts, tool
delivery, output validation and recovery.

The host serializes conflicting target proposals and checks their captured hashes before applying them. This is not a transaction against arbitrary Lisp or unrelated programs; concurrent external changes can still race filesystem calls. Unknown outcomes require inspection and must not be replayed.

## Stop and recover

The control beside the session input shows the Lisp state. Selecting it opens the
recovery view; failures do not repeatedly open a modal. It offers stop/recovery without needing a model response. The commands
remain available when the AI loop is stopped:

```text
/kioku-lisp status
/kioku-lisp diagnostics
/kioku-lisp cancel
/kioku-lisp recover
```

The default `status` view shows counts and up to three unresolved Lisp operations
and three unresolved external operations, each with its exact `diagnostics` command.
Successful operation IDs stay in `/kioku-lisp status --json`; ordinary status does
not print the full history. Reading either view never starts or recovers a worker.

An owned file proposal rejected by the host before dispatch is `NOT_APPLIED`,
not an unknown mutation. The Lisp generation and its definitions remain usable;
the host's preparation and permission checks still apply to any later attempt.
An error after dispatch, even with identical text, remains `UNKNOWN`.

`RECONCILIATION_REQUIRED` means external effects still lack a verifiable receipt.
Inspect `/kioku-lisp diagnostics OPERATION_ID` and the original host evidence.
A SIGTERM or job settlement message alone is not proof that the process and its
resources stopped. No interrupted command is automatically retried.

Restarting Lisp cannot clear an unknown external operation, and neither can
`recover` or `abandon`. While one remains, every later effect is refused. After
inspecting the original receipt, end only the operations you have confirmed are
harmless; the recorded text is the human evidence, not an observed result:

```text
/kioku-lisp abandon-managed OPERATION_ID
/kioku-lisp recover
```

`abandon-managed` accepts only `unknown` operations, requires the inspection
evidence, and records no success or failure outcome. It never re-executes the
command and never reverts an effect, so operations whose effects you cannot
verify must stay open until you resolve them yourself. The Lisp control beside
the session input offers the same action for the listed operations, and a scope
containing an abandoned operation cannot later be used as protocol test evidence.

A host that supplies versioned Bash termination receipts can reconcile an exact
original native child as `cancelled-confirmed` after observing its managed range
empty. The plugin checks the child/root call, agent, session and exact dispatched
working directory. The Lisp timeout or cancellation stays an error, and prior
command effects remain. Older hosts, missing receipts and mismatched identities
remain unresolved; text, SIGTERM and job settlement cannot substitute for proof.

In ordinary development conversations, `recover` starts a fresh worker after a
timeout, output limit or Stop, including between turns. It needs no task setup;
old turn permissions remain expired, and definitions/heap values are discarded.
Task mode, protected mode and a defined coding workflow retain their task
authority. Recovery never replays the interrupted expression.

Inspect every UNKNOWN file operation and its backup before abandoning it:

```text
/kioku-lisp abandon OPERATION_ID
/kioku-lisp recover
```

Abandonment records a human decision; it does not replay or restore data. Backups
remain at the reported paths. Inspect or restore a particular operation with:

```text
/kioku-lisp diagnostics OPERATION_ID
/kioku-lisp restore OPERATION_ID
```

Restore checks the backup digest and asks for fresh confirmation against the
current target. To return to normal tools after confirmed stop and
resolution of unknown operations:

```text
/kioku-lisp disable
```

Plugin unload retains a host-owned execution fence. Reloading never silently
restarts previous Lisp code. A live second host using the same Lisp data directory
is refused. A dead host's unfinished operations become UNKNOWN at startup.

## Limits

Defaults: 120-second evaluations (maximum 600 seconds), 30 seconds per setup
subprocess and worker startup,
4 workers, 4 active jobs per worker, 8 MiB process output, 1 MiB frames,
1000 retained references, 100 proposals per evaluation, 64 MiB input files,
256 MiB total input copies per worker generation.
Set `startupTimeoutMs: 60000` if first-use compilation needs more time. Setup
subprocesses use the same output limit and parent-liveness supervisor as workers.
Published bundles are limited to 256 MiB each. Concurrent first enables share one
serialized build under the host's existing exclusive data-directory lease.

Lisp workers remain OS-isolated. Host processes launched through kioku.process run/start-job use the normal user environment and supervised process groups after the host permission check; directory defaults to the admitted workspace and may select a real subdirectory. Collect background results rather than rerunning commands.
`python`, `shell`, `run-lines`, `python-stdout` and `shell-stdout` also accept
`:directory`. The output-only helpers signal an error on process failure, so a
composed operation stops before its next step. Use `run` when the function needs
to inspect a nonzero exit and handle it explicitly. `result-ok?` is true only for
exit code zero; missing or nonnumeric exit codes return false.

For selected tests, use the bundled `kioku.test` functions inside `lisp_eval`:

```lisp
(kioku.test:node "test/example.test.ts" :tsx t)
(kioku.test:vitest "test/example.spec.ts" :config-loader :native)
```

Both accept one file or a list, a `:directory`, a `:name-pattern` regex and
`:timeout-ms` for the subprocess. Use a Lisp evaluation timeout long enough to
collect the result. Vitest uses the development project's installed
`./node_modules/.bin/vitest` and its JSON reporter. Node uses TAP and can run in
protected scratch with `:isolation :none`:

```lisp
(kioku.test:node "test/public.test.mjs" :directory "extract/project" :isolation :none)
```

These functions use the existing process permissions and cancellation. The
result keeps the process `code` and includes `testStatus`, a count summary
when the reporter is readable, and up to eight failures with available
locations. A zero exit without observed passing cases yields `unknown`.
For Vitest, each test failure's `file`, `line`, and `column` identify the test
definition when available. When the assertion stack contains a usable local
source frame, `assertionLocation` gives the assertion's own `file`, `line`, and
`column`. That file can be a helper called by the test.
For missing or truncated reports, check the returned diagnostic and inspect
the saved `/value/json/stdout` or `/value/json/stderr` with `lisp_inspect`.
The saved logs are bounded and `logsTruncated` indicates when capture was
shortened. Project-specific commands remain available through `kioku.process:run`.

The experimental flag spelling works with Node 22.8+ and remains accepted by
Node 24/26. The shorter `--test-isolation` spelling requires Node 23.6+.
The broker resolves programs through its fixed system PATH, so its Node can differ
from the host's Node or the version selected by a version manager/CI setup step.

Check `result-code`; evaluation success alone does not establish process success.
This alternative invocation does not establish that `npm test` passed.

Fork-free scratch helpers include `glob-scratch`, `head-lines`, `tail-lines`,
`count-lines`, `grep-scratch`, `copy-scratch` and `delete-scratch`. Mutating
helpers reject absolute paths, parent traversal, links, directories and self-copy.
Counts, bytes, returned lines and matches are bounded. `tail-lines` uses a fixed
ring buffer, file copies use a 64 KiB buffer, each grep compiles its scanner once,
and `join-lines`, `sort-lines`, `uniq-lines`, `take-lines` and `drop-lines` scan
lists sequentially rather than using indexed list access.

Shell pipelines and Python multiprocessing may be rejected; single-process Python,
shell builtins and `exec` remain available. Linux uses a PID namespace to stop
descendants and a seccomp filter to deny socket creation
(including io_uring). Neither platform promises aggregate hard
limits for every memory/thread/scratch allocation. Timeouts/output limits and
confirmed-stop handling are separate controls.

## CI investigation from Lisp

`kioku.ci:list-runs` and `kioku.ci:failed-log` invoke the host's `gh` executable
from the exact repository root bound to the protected session. The worker receives
only bounded results, never host credentials or arbitrary network access. Run IDs
must be numeric and are resolved by `gh` against that repository, so a run from a
different repository is rejected.

`kioku.ci:verify` accepts only `typecheck`, `lisp`, `test`, `build`, `package`, or
`vendor`. These map to fixed `npm` commands and timeouts; arbitrary executable,
arguments, URL or shell input is not accepted. `:directory` selects a relative
project directory under the bound workspace, or under the current worker's scratch
with `:location :scratch`. Absolute paths, traversal and links are refused. Routine verifier execution needs no approval. The host records actual cwd, process outcome and full-output test summaries before display shortening. Register completion checks and source paths before execution. Nonzero exit is FAILED; zero exit alone is not test coverage. The enclosing
`lisp_eval` operation ID provides replay and conflict handling.

Typical flow:

```lisp
(kioku.ci:list-runs :limit 5)
(kioku.ci:failed-log 123456789)
;; inspect project inputs, then propose a bounded file change
(kioku.files:propose-write "path/to/file" new-content)
(kioku.ci:verify :lisp)
;; Run the actual npm test in the extracted project, after human confirmation.
(kioku.ci:verify :test :location :scratch :directory "extract/project")
;; Or verify an authorized project already applied beneath the workspace.
(kioku.ci:verify :test :directory "project")
```

## Development verification

```sh
npm run verify:lisp:vendor
npm run typecheck
KIOKUKO_REQUIRE_LISP_RUNTIME=1 \
KIOKUKO_DSH_PACKAGE_ROOT="$PWD/tests/fixtures/dsh-runtime/node_modules" \
npm run test:lisp
npm run build
npm run pack:check
```

Set `KIOKUKO_LISP_SBCL` to a test launcher when necessary. A skipped native test is
not platform verification. The original P0 report is historical evidence only;
it does not grant production admission. The runtime probes its own protected SBCL.

Results retain their operation identity permanently within the retained session.
Successful/failed result bodies older than 30 days are expired at startup; replay
returns RESULT_EXPIRED and never re-evaluates. The journal stops accepting writes
at 10000 records or 1 GiB per session, or 4 GiB globally. Backup retention is
independent: no automatic deletion. Call bindings count toward the record limit.

For API examples see [the bundled Skill](../skills/kiokuko-lisp/SKILL.md).

Verification scope and installed Web reproduction: [verification record](lisp-verification.md).

Native `ask_user_question` and `exit_plan_mode` remain available in both Lisp modes. Plan approval uses the DSH review UI. Explicit human implementation instructions also request exit at the next accepted boundary. Protected compatibility mode retains its Lisp sandbox; development mode allows ordinary owned tools after Plan exits. Run `npm run test:lisp:plan:web` for a disposable packed-package Web check without model credentials.

<!-- kiokuko:runtime approval-policy -->
## Development approval policy

The legacy lisp.approvalMode ask/auto configuration and profile commands remain readable. Both authorize routine project edits, tests, helper registration and Kioku saving. Changes within the working project directory, including deletion, replacement and project DB updates, need no extra user permission. Outside-project changes require concrete confirmation. A worker failure leaves normal tools available. Refusal, cancellation, changed targets and unknown outcomes cannot authorize effects.

Use kioku_read/write/edit/remove/exec/result for observed work. Host receipts distinguish actual process outcome, source-bound verification and saving status. Lisp/model passed fields are advisory. Saving can be retried without repeating development effects; after restart an interrupted process remains unknown. See [owned execution](owned-execution.md).
<!-- /kiokuko:runtime -->

Pinned DSH stores the approval setting in the native `kiokuko-lisp` namespace, keyed by
the host-owned profile identity. Current DSH exposes `lisp.approvalMode`, the
`startupDefaults` pair and `typedDecisions.provider` together through the native
`kiokuko-dsh` configuration form, and saves the profile patch. This
compatibility difference does not change the controls or scope. No separate
preference file or database migration is used.


## Read-only code intelligence

Persistent `lisp_eval` can use an optional `CodeIntelligenceServiceV1` from
`@askdkc/dsh-lsp-server`. Kiokuko resolves it in the calling agent's context.
The provider owns Tree-sitter, grammars, source snapshots, coordinates, and LSP
processes. Kiokuko does not install them or fall back to generic LSP tools.
Core alone has no code-intelligence runtime dependency.

The released `@askdkc/dsh-lsp-server` v0.1.14 provides V1. Follow the
[installation guide](code-intelligence.md); the local patch is only a historical
test fixture. `kiokuko-dsh` checks the service contract and capabilities at runtime,
not just the package version. Missing providers, grammars and unready language
servers return explicit outcomes without blocking ordinary Lisp APIs.
Node must satisfy `kiokuko-dsh`'s `>=24.16.0` requirement.

Start with `(kioku.code:capabilities)`. Its response includes `status`, `reason`
when relevant, `data.version`, `data.capabilities`, `data.languages`, distributed
`data.queryIds`, and `data.semanticReady`. The last field stays false until this lease completes a semantic query: a
server executable's presence does not establish a synchronized semantic result.

```lisp
;; One evaluation: aggregate locally, return selected source and metadata.
(kioku.code:with-snapshot (snapshot "src/example.ts")
  (let ((outline (kioku.code:outline snapshot :limit 20)))
    (if (member (gethash "status" outline) '("ok" "partial") :test #'equal)
        (let ((items (gethash "items" (gethash "data" outline))))
          (kioku.internal:object
            "status" (gethash "status" outline)
            "omitted" (gethash "omitted" outline)
            "declarationCount" (length items)
            "selected" (if (plusp (length items))
                           (kioku.code:span (gethash "handle" (aref items 0))
                                            :max-chars 1000)
                           #())))
        outline)))
```

Check `status` before accessing `data`. `ok` with an empty array means the query
completed with no results. `partial` preserves parse-error/truncation information.
`unsupported`, `unavailable`, `stale`, `cancelled`, `timeout`, and `limit_exceeded`
are separate states. Missing diagnostics publication never becomes a clean file.
`with-snapshot` returns the opening failure unchanged and always releases an
accepted snapshot, including on a Lisp error. For other queries, branch explicitly
on `ok`/`partial` before using their `data`.

| Lisp function | Arguments | Purpose |
| --- | --- | --- |
| `capabilities` | none | Negotiate V1, per-language readiness, disk sources and limits |
| `open` | path | Pin a bounded disk source snapshot |
| `outline` | handle, `:range`, `:limit` | Bounded declaration map |
| `enclosing` | handle, position, `:kinds` | Nearest matching node and ancestors |
| `query` | handle, query ID, `:range`, `:limit` | Distributed `declarations`, `calls`, `imports` captures |
| `span` | node handle, `:max-chars` | Bounded original source span |
| `semantic` | handle, kind, `:position`, `:limit` | `definition`, `references`, `implementation`, `hover`, `diagnostics`, `completion` |
| `release` | handle | Idempotent release |

Positions are JSON objects such as `(kioku.internal:object "line" 1 "character" 2)`:
zero-based lines and UTF-16 character offsets. Ranges have `start` and `end` and
are half-open. The provider performs conversion. There is no unsaved-editor
synchronization claim: `sourceKind` is `disk`, with freshness and a snapshot version
on every accepted source result. File/checkout changes require an explicit reopen.
Handles and node handles expire at evaluation end; journal replay and saved
`lisp_inspect` output are historical evidence, never live capabilities. Task workers
retain their host-RPC prohibition. Neither `lsp` nor `lsp_extra` is added to the
protected tool allowlist; child sessions cannot borrow admission.

Each evaluation has 100 charged calls (including capabilities and failed attempts),
100 files, 16 MiB cumulative source input, 64 KiB cumulative code responses, and a
30-second code budget. Individual documents are at most 2 MiB; captures 200,
outline entries 100, source spans 8,000 UTF-16 units, responses 32 KiB. Structural
calls have a 2-second deadline and semantic calls 10 seconds, capped by the batch
and caller deadlines. Release/disposal remain available outside exhausted budgets.
Model presentation remains capped at 16 KiB and retains code outcome metadata;
use bounded `lisp_inspect` pages for saved detail. Aggregate counts and chosen spans
inside Lisp; returning full outlines can increase output instead of reducing it.

The local prerequisite currently supports JavaScript/JSX/TypeScript/TSX. Its
checkout metadata read must be allowed by the scoped host filesystem. Linked Git
worktrees whose metadata is outside that scope return
`unavailable` rather than read through another filesystem. This condition is
reported independently from grammar and language-server readiness.

Development verification is `npm run test:code-intelligence` after preparing the
provider fixture. The source/native and packed paths are separate checks. The CI
matrix configures development checks on macOS, Ubuntu 26.04 and Arch, plus
protected checks on macOS. Linux protected acceptance is separate. A configured
job is not evidence that the current package passed; see the setup guide for
verification limits.

Clean installation and platform status: [code intelligence setup](code-intelligence.md).

## Coding task choice and recovery

For a new coding task, `coding_workflow` first defines and displays the Goal, implementation items and observable conditions. The host then asks whether to use Lisp. Configuration alone never implies yes; an explicit manual session ON is reused as the selected route.
`startupDefaults.lisp` (default `ask`) only preselects the Lisp answer on the task-startup card, which stays editable; a saved answer still wins. The default never enables the module or satisfies a yes: a missing runtime or failed startup remains the same recoverable blocker. Closing the question leaves it pending and `coding_workflow next` reopens the same question. The combined startup card reuses the chat choice after restart and on ordinary follow-ups. A different chat or repository asks separately; explicit Lisp commands still override the inherited choice. A saved yes followed by startup failure remains a visible blocker; recover the runtime or explicitly change the task route with `/kioku-lisp disable`. No uses ordinary tools and still requires fresh item checks followed by a final user-entry Goal check. Lisp evaluation success alone is insufficient: bind `task_completion` and collect actual host process evidence. See [coding workflow](coding-workflow.md).

Live opt-in condition/restart recovery: see [bounded recovery](lisp-recovery.md).
