pub struct Kernel { /* private fields */ }Expand description
The Kernel (核) — executes kaish code.
This is the primary interface for running kaish commands. It owns all the runtime state: variables, tools, VFS, jobs, and persistence.
Implementations§
Source§impl Kernel
impl Kernel
Sourcepub fn new(config: KernelConfig) -> Result<Self>
pub fn new(config: KernelConfig) -> Result<Self>
Create a new kernel with the given configuration.
Sourcepub fn with_backend(
backend: Arc<dyn KernelBackend>,
config: KernelConfig,
configure_vfs: impl FnOnce(&mut VfsRouter),
configure_tools: impl FnOnce(&mut ToolRegistry),
) -> Result<Self>
pub fn with_backend( backend: Arc<dyn KernelBackend>, config: KernelConfig, configure_vfs: impl FnOnce(&mut VfsRouter), configure_tools: impl FnOnce(&mut ToolRegistry), ) -> Result<Self>
Create a kernel with a custom backend and /v/* virtual path support.
This is the constructor for embedding kaish in other systems that provide their own storage backend (e.g., CRDT-backed storage in kaijutsu).
A VirtualOverlayBackend routes paths automatically:
/v/*→ Internal VFS (JobFs at/v/jobs, MemoryFs at/v/blobs)/dev→ DevFs (synthetic/dev/null,/dev/zero,/dev/random,/dev/urandom) — kernel-owned so it works even when your backend is read-only- Everything else → Your custom backend
The optional configure_vfs closure lets you add additional virtual mounts
(e.g., /v/docs for CRDT blocks) after the built-in mounts are set up.
Note: The config’s vfs_mode is ignored — all non-/v/* path routing
is handled by your custom backend. The config is only used for name, cwd,
skip_validation, and interactive.
§Example
// Simple: default /v/* mounts only
let kernel = Kernel::with_backend(backend, config, |_| {}, |_| {})?;
// With custom mounts
let kernel = Kernel::with_backend(backend, config, |vfs| {
vfs.mount_arc("/v/docs", docs_fs);
vfs.mount_arc("/v/g", git_fs);
}, |_| {})?;
// With custom tools
let kernel = Kernel::with_backend(backend, config, |_| {}, |tools| {
tools.register(MyCustomTool::new());
})?;Sourcepub fn into_arc(self) -> Arc<Self>
pub fn into_arc(self) -> Arc<Self>
Wrap this Kernel in an Arc and initialize its self-reference.
This enables the Kernel to hand out Arc<dyn CommandDispatcher> references
to child contexts, allowing builtins like timeout to dispatch inner
commands through the full resolution chain (user tools → builtins →
.kai scripts → external commands).
Sourcepub async fn fork(&self) -> Arc<Self>
pub async fn fork(&self) -> Arc<Self>
Fork a subsidiary kernel for concurrent execution.
The fork is a fully-functional Kernel that:
- Snapshots per-session state from the parent: scope (COW — cheap), user-defined tools, cwd, aliases, ignore config, etc. Mutations on the fork do NOT propagate back to the parent — matching bash subshell / background-job semantics.
- Shares read-mostly resources with the parent via
Arc: the tool registry, the VFS router, and the job manager. A job registered by the fork is visible to the parent’sjobsbuiltin, and the fork sees the same VFS mounts. - Owns its own
stderr_receiver,cancel_token, andexecute_lock. It is never the TTY owner, sointeractiveisfalseandterminal_stateisNone.
The returned Arc has its self_weak populated (via into_arc), so
nested dispatch through ctx.dispatcher (e.g. the timeout builtin)
routes through the fork itself, not the parent — which is essential
for concurrency safety.
Use this for detached background concurrency where the fork should
survive parent cancellation: the & background-job operator and any
other “fire and forget” worker. The fork gets a fresh, independent
cancellation token.
For foreground concurrency (scatter workers, concurrent pipeline
stages, $(...) cmdsubs) where parent timeout/cancel must cascade
into the fork’s external children, use Self::fork_attached.
Sourcepub async fn fork_attached(&self) -> Arc<Self>
pub async fn fork_attached(&self) -> Arc<Self>
Fork attached to the parent’s cancellation.
Same as Self::fork but the fork’s cancel_token is a child of
the parent’s. When the parent cancels (request timeout, embedder
Kernel::cancel, etc.), the fork’s token also cancels, which in
turn kills any external children spawned in the fork via the
wait_or_kill / SIGTERM-grace-SIGKILL path.
Sourcepub async fn fork_for_background(
&self,
cancel: CancellationToken,
job_id: JobId,
) -> Arc<Self>
pub async fn fork_for_background( &self, cancel: CancellationToken, job_id: JobId, ) -> Arc<Self>
Fork for a background job, stamping the job id so external commands
spawned anywhere beneath it record their process groups on that job
(for kill -<sig> %N). The caller owns cancel so it can also drive
JobManager::cancel.
Sourcepub fn dispatcher(&self) -> Option<Arc<dyn CommandDispatcher>>
pub fn dispatcher(&self) -> Option<Arc<dyn CommandDispatcher>>
Get an Arc<dyn CommandDispatcher> to this Kernel, if wrapped via into_arc().
Returns None if the Kernel was not wrapped, or if all strong references
have been dropped (the Weak can no longer upgrade).
Sourcepub fn set_trash_backend(&mut self, backend: Option<Arc<dyn TrashBackend>>)
pub fn set_trash_backend(&mut self, backend: Option<Arc<dyn TrashBackend>>)
Replace or remove the trash backend used by rm and kaish-trash.
The kernel installs the OS trash (SystemTrash) automatically when
built with the os-integration feature. Embedders and tests can swap
in a custom crate::trash::TrashBackend, or pass None to remove
it — with trash enabled but no backend present, rm fails loud
rather than falling through to permanent delete.
Sourcepub fn cancel(&self)
pub fn cancel(&self)
Cancel the current execution.
This cancels the current cancellation token, causing any execution
loop to exit at the next checkpoint with exit code 130 (SIGINT).
A fresh token is installed for the next execute() call.
Sourcepub fn is_cancelled(&self) -> bool
pub fn is_cancelled(&self) -> bool
Check if the current execution has been cancelled.
Sourcepub async fn execute(&self, input: &str) -> Result<ExecResult>
pub async fn execute(&self, input: &str) -> Result<ExecResult>
Execute kaish source code with default options.
Equivalent to execute_with_options(input, ExecuteOptions::default()).
Returns the result of the last statement executed.
Sourcepub async fn execute_argv(
&self,
name: &str,
argv: &[Value],
) -> Result<ExecResult>
pub async fn execute_argv( &self, name: &str, argv: &[Value], ) -> Result<ExecResult>
Argv-native peer of Self::execute — run one command whose arguments
are already tokenized.
execute(&str) is string-native: it lexes and parses its input. A caller
that already holds OS/structured argv (a busybox-style multicall binary, a
structured embedder like kaijutsu) would otherwise have to re-quote argv
into a string just to have the lexer split it apart again — a round-trip
that is lossy for typed values, since to_argv() stringifies
Value::Bytes/Value::Json. execute_argv skips it.
Tokens are literal. No glob expansion, no $VAR interpolation, no
command substitution, no word splitting — the “single-quoted word”
semantics taken to its end. execute_argv("echo", &[Value::String("*.txt" .into())]) emits *.txt; it does not glob. (One shared-binder expansion
does still apply, for consistency with the string door: a leading ~ is
expanded against the session HOME — kaish expands ~ uniformly, so the
two doors agree. Pass a pre-resolved path if you need it byte-literal.) A
non-string Value
(Bytes/Json/Int) lands directly in ToolArgs.positional, so typed
data survives without a to_argv() round-trip. (Caveat: the two-layer
clap arg model means a builtin that re-parses its own to_argv() still
sees a stringified value; the typed-passthrough win fully lands only for
builtins that read args.positional directly — the documented pattern.)
This is a peer, not a subset: a command string can carry pipelines,
&&/||, control flow and $() that have no argv encoding, so the two
doors converge late (at the shared dispatch chain) rather than one
wrapping the other. From argv classification onward execute_argv reuses
the exact path a Stmt::Command takes — command resolution (aliases, user
tools, .kai scripts, externals, backend tools), arg binding, the --json
transform, and the confirmation latch — so a latched rm still emits a
nonce and an ls --json still applies output formatting. The kernel’s
pre-execution syntax validator does not run: argv has no shell syntax to
validate (a tool’s own validate()/clap parse still runs at dispatch).
Concurrent callers serialize on the same execute lock as Self::execute,
and the kernel’s configured request_timeout applies (a hung builtin or
external is interrupted at the deadline with exit code 124, the same as the
string door). There is no per-call options surface yet — a future
execute_argv_with_options would carry per-call timeout/cancel/vars/cwd.
Sourcepub async fn confirm(&self, latch: &LatchRequest) -> Result<ExecResult>
pub async fn confirm(&self, latch: &LatchRequest) -> Result<ExecResult>
Fulfill a confirmation latch by replaying its exact captured invocation with the nonce — the highest-fidelity approval path.
Inspect a gated result with ExecResult::latch_request; apply whatever
policy (allowlist, model review) over req.command/req.paths; then call
this to approve. It replays execute_argv(req.tool, req.argv) with
--confirm=<nonce> prepended — no re-parsing of the human hint, so a
path with spaces or glob characters round-trips exactly. Share the nonce
store (KernelConfig::with_nonce_store) to confirm from a later
kernel call than the one that produced the latch.
Errors (exit 2) if the latch carries no captured invocation — a latch
produced outside a dispatch seam (a direct tool.execute in a unit
test). Those are confirmable only by re-running with --confirm=<nonce>.
Sourcepub async fn execute_with_options(
&self,
input: &str,
opts: ExecuteOptions,
) -> Result<ExecResult>
pub async fn execute_with_options( &self, input: &str, opts: ExecuteOptions, ) -> Result<ExecResult>
Execute with per-call options. The primary entry point for embedders that don’t need per-statement output streaming.
opts carries timeout, transient vars overlay, optional cwd override,
and optional embedder-owned cancellation token. See ExecuteOptions
for semantics. For streaming, use Self::execute_with_options_streaming.
Cancellation: if opts.cancel_token is Some, it is raced
against the kernel’s internal token. Either firing cancels and kills
external children. The embedder’s token is read-only — kernel
timeouts do NOT propagate into it. Distinguish via the returned
code: 124 = timeout, 130 = cancellation.
Timeout: opts.timeout overrides KernelConfig::request_timeout.
Some(Duration::ZERO) returns 124 immediately without spawning.
Concurrent callers on the same Kernel serialize on the kernel-wide
execute lock. For true parallelism, call Kernel::fork (detached)
or Kernel::fork_attached (cancellation cascades from this kernel).
Sourcepub async fn execute_with_options_streaming(
&self,
input: &str,
opts: ExecuteOptions,
on_output: &mut (dyn FnMut(&ExecResult) + Send),
) -> Result<ExecResult>
pub async fn execute_with_options_streaming( &self, input: &str, opts: ExecuteOptions, on_output: &mut (dyn FnMut(&ExecResult) + Send), ) -> Result<ExecResult>
Same as Self::execute_with_options but with a per-statement output
callback. The callback fires after each top-level statement so the
embedder (REPL, MCP streaming) can flush output incrementally.
Sourcepub async fn execute_with_pipe_stdin(
&self,
input: &str,
opts: ExecuteOptions,
pipe_stdin: PipeReader,
) -> Result<ExecResult>
pub async fn execute_with_pipe_stdin( &self, input: &str, opts: ExecuteOptions, pipe_stdin: PipeReader, ) -> Result<ExecResult>
Execute with a lazy standard input fed as a [PipeReader].
Unlike ExecuteOptions::with_stdin (a pre-read String), this never
forces the input to be drained before execution: the reader seeds the
first top-level command’s pipe_stdin, and a command that does not read
stdin (echo) returns without touching it. This is the seam a
non-interactive frontend uses to forward an open process stdin without
hanging on a pipe that never sends EOF (sleep 10 | kaish -c 'echo hi').
Embedders that already hold a complete buffer should prefer the simpler
ExecuteOptions::with_stdin String path.
Sourcepub async fn execute_with_pipe_stdin_streaming(
&self,
input: &str,
opts: ExecuteOptions,
pipe_stdin: PipeReader,
on_output: &mut (dyn FnMut(&ExecResult) + Send),
) -> Result<ExecResult>
pub async fn execute_with_pipe_stdin_streaming( &self, input: &str, opts: ExecuteOptions, pipe_stdin: PipeReader, on_output: &mut (dyn FnMut(&ExecResult) + Send), ) -> Result<ExecResult>
Streaming counterpart to Self::execute_with_pipe_stdin — the REPL
-c/script frontend uses this to print output incrementally while
feeding a lazy process-stdin pipe.
Sourcepub async fn execute_with_vars(
&self,
input: &str,
vars: HashMap<String, Value>,
) -> Result<ExecResult>
👎Deprecated: use Kernel::execute_with_options with ExecuteOptions::with_vars
pub async fn execute_with_vars( &self, input: &str, vars: HashMap<String, Value>, ) -> Result<ExecResult>
use Kernel::execute_with_options with ExecuteOptions::with_vars
Execute kaish source code with a transient overlay of exported variables.
Deprecated thin wrapper over Self::execute_with_options. New code
should use that method directly:
execute_with_options(input, ExecuteOptions::new().with_vars(vars)).
Sourcepub async fn execute_streaming(
&self,
input: &str,
on_output: &mut (dyn FnMut(&ExecResult) + Send),
) -> Result<ExecResult>
👎Deprecated: use Kernel::execute_with_options_streaming
pub async fn execute_streaming( &self, input: &str, on_output: &mut (dyn FnMut(&ExecResult) + Send), ) -> Result<ExecResult>
use Kernel::execute_with_options_streaming
Execute kaish source code with a per-statement callback.
Deprecated thin wrapper. New code should use
Self::execute_with_options_streaming.
Sourcepub async fn set_positional(
&self,
script_name: impl Into<String>,
args: Vec<String>,
)
pub async fn set_positional( &self, script_name: impl Into<String>, args: Vec<String>, )
Set positional parameters ($0 script name and $1-$9 args).
Sourcepub async fn exported_vars(&self) -> Vec<(String, Value)>
pub async fn exported_vars(&self) -> Vec<(String, Value)>
List exported variables (name, value), sorted by name. These are the
vars a child process would see (see dispatch’s hermetic env build).
Sourcepub async fn try_set_cwd(&self, path: PathBuf) -> bool
pub async fn try_set_cwd(&self, path: PathBuf) -> bool
Set the working directory only if path resolves to a directory in the
kernel’s backend — the same namespace cd validates against. Unlike a
raw host-FS is_dir() check, this correctly accepts virtual mounts
(/v/docs, in-memory scratch, …) and rejects real paths that have since
disappeared. Returns whether the cwd was changed.
Sourcepub async fn last_result(&self) -> ExecResult
pub async fn last_result(&self) -> ExecResult
Get the last result ($?).
Sourcepub async fn has_function(&self, name: &str) -> bool
pub async fn has_function(&self, name: &str) -> bool
Check if a user-defined function exists.
Sourcepub fn tool_schemas(&self) -> Vec<ToolSchema>
pub fn tool_schemas(&self) -> Vec<ToolSchema>
Get available tool schemas.
Sourcepub async fn classify_command(&self, name: &str) -> CommandKind
pub async fn classify_command(&self, name: &str) -> CommandKind
Classify how the kernel will resolve a command name.
This is the supported, single source of truth for command resolution that
embedders should call instead of re-deriving the rules. Walk a parsed
script (kaish_kernel::parser::parse → Stmt::Command nodes) and call
this per command name to bucket each into builtin / user-function /
special-form / dynamic / external — for example a consent gate that blocks
a script until external commands are approved.
The classification mirrors the interpreter’s real resolution order
(execute_command_depth): special-forms (true/false/source/.)
short-circuit first, then aliases are expanded (bounded recursion,
re-checking special-forms each step, exactly as execution does), then user
functions (which shadow builtins), then builtins, then a PATH lookup. A
name that is a variable or command-substitution expansion ($cmd,
$(pick), ${x}) classifies as CommandKind::Dynamic because it can’t
be resolved statically.
Aliases are resolved against the kernel’s current alias table, so an
alias cat=/bin/something makes cat classify as External — the same
thing it would actually run. The safe direction of any residual imprecision
is External/Dynamic, never a false “internal”: the /v/bin/ prefix and
.kai/backend-tool resolution are reported External even though some of
those resolve in-process, so a consent gate over-gates rather than letting
a PATH escape slip through.
Sourcepub fn jobs(&self) -> Arc<JobManager>
pub fn jobs(&self) -> Arc<JobManager>
Get job manager.
Sourcepub async fn reset(&self) -> Result<()>
pub async fn reset(&self) -> Result<()>
Reset kernel to initial state.
Clears in-memory variables and resets cwd to root. History is not
cleared (it persists across resets). The kernel’s $$ identity, the
confirmation latch / trash-on-delete configuration, and any
frontend-seeded initial_vars (HOME/PATH/etc, from KernelConfig)
are re-applied to the fresh scope rather than silently reverting to
defaults — an embedder that opted into with_latch(true) must not
find the gate quietly disabled after a reset() between requests.
Trait Implementations§
Source§impl CommandDispatcher for Kernel
impl CommandDispatcher for Kernel
Source§fn dispatch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
cmd: &'life1 Command,
ctx: &'life2 mut ExecContext,
) -> Pin<Box<dyn Future<Output = Result<ExecResult>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn dispatch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
cmd: &'life1 Command,
ctx: &'life2 mut ExecContext,
) -> Pin<Box<dyn Future<Output = Result<ExecResult>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Dispatch a command through the Kernel’s full resolution chain.
This is the single path for all command execution when called from the pipeline runner. It provides the full dispatch chain: user tools → builtins → .kai scripts → external commands → backend tools.
Source§fn eval_expr<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
expr: &'life1 Expr,
_ctx: &'life2 ExecContext,
) -> Pin<Box<dyn Future<Output = Result<Value>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn eval_expr<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
expr: &'life1 Expr,
_ctx: &'life2 ExecContext,
) -> Pin<Box<dyn Future<Output = Result<Value>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Evaluate an expression through the kernel’s async chain, including
command substitution. Delegates to eval_expr_async, which snapshots
the kernel’s scope/cwd and restores them after any $(...) runs, so
only command output escapes. The ctx is unused here because the
kernel evaluates against its own session state (a fork carries the
pipeline stage’s snapshot); var refs resolve against that scope.
Source§fn fork<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Arc<dyn CommandDispatcher>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn fork<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Arc<dyn CommandDispatcher>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Produce a forked dispatcher with independent mutable state (detached).
Calls the inherent Kernel::fork method (note the UFCS to avoid
recursing into the trait method we’re defining) and coerces the
returned Arc<Kernel> to Arc<dyn CommandDispatcher>.
Source§fn fork_attached<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Arc<dyn CommandDispatcher>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn fork_attached<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Arc<dyn CommandDispatcher>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Produce a forked dispatcher with cancellation cascading from this kernel.
Auto Trait Implementations§
impl !Freeze for Kernel
impl !RefUnwindSafe for Kernel
impl !UnwindSafe for Kernel
impl Send for Kernel
impl Sync for Kernel
impl Unpin for Kernel
impl UnsafeUnpin for Kernel
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
Source§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T, S> SpanWrap<S> for Twhere
S: WrappingSpan<T>,
impl<T, S> SpanWrap<S> for Twhere
S: WrappingSpan<T>,
Source§fn with_span(self, span: S) -> <S as WrappingSpan<Self>>::Spanned
fn with_span(self, span: S) -> <S as WrappingSpan<Self>>::Spanned
WrappingSpan::make_wrapped to wrap an AST node in a span.