@kruntime/komputer
v0.1.15
Published
Production TypeScript SDK/runtime for K Komputer, AgentSession, KState, authority, events, receipts, and recovery.
Downloads
3,005
Readme
@kruntime/komputer
Runtime SDK and kernel surface for K.
This package owns the durable runtime truth:
KomputerAgentSession.kstate- VFS
- Runtime Authority
- events
- messages
- receipts
- vProcess recovery
It intentionally does not scan image source trees. Source layout and active files belong to @kruntime/kimage.
Agent Computer Surface
agent.exec(...) always enters K's local shell layer first. If a command cannot
run inside the virtual computer, K may delegate it to a configured remote shell.
The following work without any remote shell:
pwd cd ls cat chmod install stat du base64 echo printf grep wc head jq tail sort uniq sed sha256sum awk tr cut diff tee xargs
find basename dirname realpath readlink mkdir mktemp touch rm cp mv ln
command type which help env printenv read source . set sh bash export unset id whoami hostname uname date true false test [ [[
sleep curl wget jobs ps kill fg bg wait
registered image commandsOutput from K-local commands and remote commands is projected through the same
process event family. Streaming stdout/stderr uses
{ type: 'process', status: 'output' }.
K-local builtins support ls -la/ls -F, quote-aware variables, $?,
${VAR:-fallback}-style parameter defaults, ${#VAR} length probes,
${path##*/}/${path%/*} trimming, $((...)), KFS glob expansion,
unquoted field splitting, bounded brace expansion, stdin/stdout/stderr
redirection, heredoc stdin, $() and backtick command substitution, pipes, top-level
if branches, top-level for ... in loops, and bounded while/until loops
through the VFS, so runtimes with no attached remote shell can still run familiar shell-shaped work
without a remote shell. chmod stores real mode bits in Drive/KState metadata;
stat -c %a/%A, ls -l, test -x, and PATH executable lookup all read those
same bits.
such as echo note > ~/work/note.txt, grep alpha < notes.txt,
cat > file <<'EOF', if test -f notes.txt; then grep alpha notes.txt; fi,
mkdir -p app/{src,test}, for file in *.ts; do basename "$file"; done, and
i=0; while test "$i" -lt 3; do echo "$i"; i=$((i + 1)); done.
printf "%s\n" one two repeats the format for remaining args, with basic
%s/%d, width, left alignment, zero padding, and %% support.
find ... -maxdepth 2 -print0 | xargs -0 -n1 ... works inside the K shell, so
common Agent scripts can compose file discovery and text tools without a remote
shell. find ... -exec ... {} \; is also supported for familiar
one-file-at-a-time workflows.
mkdir follows normal Unix shell semantics: without -p, the parent must
already exist and the target must not; mkdir -p is the recursive, idempotent
form.
touch -c/touch --no-create follows Unix no-create behavior for existence
probes.
rm -f is safe as a cleanup no-op when no paths are passed, and -- separates
options from paths that begin with -.
cp -- and mv -- follow the same path separator rule, while cp -r keeps
directory copies inside KFS.
sed -n '1,20p' file, sed -n '$p' file, and sed -n '/pattern/p' file
cover the common file-slice and match-print probes Agents use before editing.
awk -F, '{print $1,$2,$NF,NF}' covers the usual field-splitting and simple
projection probes without a remote shell.
tail -n +2 file follows Unix start-at-line semantics for header-skipping
pipelines. head -c and tail -c select UTF-8 bytes for common file sniffing
and log-tail probes.
find -type l, test -L, and ls -F also understand K symbolic links.
grep -R -n pattern ~/work searches recursively through KFS directories;
grep -F, grep -E, and repeated grep -e patterns cover the usual fixed
string, regex, and multiple-pattern probes.
ln -s, readlink, realpath, and readlink -f are KState-backed, so
symbolic links work on runtimes with no attached remote shell and survive recovery.
curl ... | jq -r .field also works for common API JSON responses.
read supports common stdin parsing patterns, including read NAME,
read FIRST REST, and read -r RAW.
source file and . file execute KFS shell files in the current shell state,
so profile-style files can export variables, change cwd, or adjust shell
options for later commands.
sh file / bash file and bash -c '...' run through the same K shell in a
child shell state: positional parameters work, but cwd, exported variables, and
shell options do not leak back to the Agent's parent shell.
env NAME=value cmd runs a command with scoped temporary environment entries,
env -u NAME cmd temporarily removes a variable, and env -i NAME=value cmd
starts that command from an empty environment. These child environments do not
pollute the Agent's persistent shell state.
Network follows the same Unix-shaped rule. Runtime code can bind virtual hosts:
const computer = await Komputer.boot(image, {
hosts: {
api: {
'/hello': 'hello\n',
'/data.json': { ok: true },
},
},
})The Agent sees those names in /sys/hosts.md and can use normal shell commands:
curl http://api/hello
curl -I http://api/data.json
curl -i http://api/data.json
curl -fsSL -D ~/work/headers.txt http://api/data.json
curl -O http://api/data.json
wget -qO- http://api/data.jsonIf a host is not bound and the current runtime profile has real fetch, K can use the
real network. If neither exists, the command fails clearly. K-local curl
supports common script options including compact flags like -fsSL, header
dumps with -D, stdout output with -o -, and remote-name downloads with
-O; wget follows redirects and supports Agent-friendly -qO- downloads.
Background remote commands are recorded as virtual processes. Agents can inspect
them with jobs and ps, move live handles with fg/bg, wait on them with
wait, and signal them with kill when the active remote implements process
signaling.
The VFS intentionally keeps a Node-like shape:
await agent.fs.readFile('~/work/plan.md')
await agent.fs.writeFile('~/work/report.md', '...')
await agent.fs.mkdir('~/work/archive')
await agent.fs.rename('~/work/a.md', '~/work/archive/a.md')
await agent.fs.rm('~/work/archive', { recursive: true })
const tree = await agent.fs.walk('~/work')Mounted Drives are the only bridge between K paths and backing resources.
Use memoryDrive(...) for Agent-visible in-memory files. Use
memoryState(...) for K-owned runtime state. They are intentionally different:
memoryState({ maxBytes }) limits sessions/messages/events/owned state, while
memoryDrive(..., { maxBytes }) limits one mounted file tree.
Browser and worker persistence is explicit:
import { Komputer } from '@kruntime/komputer'
import { indexedDbState } from '@kruntime/komputer/browser'
const computer = await Komputer.boot(image, {
state: indexedDbState('assistant-state'),
models,
})Custom Drives may implement lifecycle hooks:
const drive = {
kind: 'drive',
name: 'crm-docs',
async mount(ctx) {
// connect, validate credentials, warm indexes
},
async unmount() {
// release backend handles
},
// readText/writeText/readdir/stat...
}When K delegates a script to a remote shell, it rewrites K paths only through the
mounted Drive's realPath(...). If a Drive has no real backing path, that script
is rejected instead of pretending the remote can see the virtual resource. K-local
builtins and image commands can still work with that Drive through the VFS.
Command handlers receive submitted input as ctx.args/ctx.input, and can run
shell work through ctx.exec(...), which follows the same K shell authority and
path rules as agent.exec(...).
agent.events() follows the ordered event log until the caller aborts or the
local session handle closes. agent.close() explicitly ends the durable
session, emits a session.closed signal, and lets event tails finish after
draining already-written events. agent.detach() only releases the current
worker lease and preserves the durable session for another worker.
Forked sessions inherit image hooks by default:
const reviewer = await agent.fork({ mode: 'readonly' })
const maintenance = await agent.fork({ mode: 'limited', hooks: false })hooks: false is for deliberate internal maintenance sessions created from a
hook. Normal application forks should keep hooks enabled.
disk: 'overlay' gives the fork an isolated upper layer for K-owned state:
const experiment = await agent.fork({ disk: 'overlay' })Writes to KState-backed paths stay in the fork overlay. External custom drives are readonly in overlay forks so experiments cannot accidentally write through to real services.
fork({ mode: 'limited', scope }) carries the scope into the child session.
Forked sessions are registered with the parent Komputer, so computer.close()
releases their leases together with normal logged-in sessions.
Each active session holds a durable worker lease in .kstate. A second worker
cannot drive the same session until the lease is released or expires. Long
running sessions renew the lease with a heartbeat. computer.close() detaches
local workers by default, while computer.close({ endSessions: true }) ends
them. computer.recoverSessions() can take over detached or expired sessions
after process shutdown or crash.
Long-running workers can call computer.detachIdleSessions({ idleForMs }) to
release idle AgentSession handles without ending durable sessions. Running
background jobs are skipped by default. A later login(agent, { id }) restores
the same Agent from KState.
For fleets, use KomputerPool as a thin lifecycle cache around the same boot
and login primitives:
import { Komputer, KomputerPool } from '@kruntime/komputer'
const pool = new KomputerPool({
maxWarm: 100,
idleForMs: 60_000,
boot: key => Komputer.boot(image, {
state: stateFor(key),
remote,
models,
}),
})
const agent = await pool.login('tenant:alice:assistant', 'assistant', {
id: 'chat:inbox',
})
await pool.withLogin('tenant:alice:assistant', { id: 'chat:inbox' }, async agent => {
await agent.exec('cat ~/task.md')
})
await pool.sweep()
console.log(pool.stats())The pool is not a task system and not another storage abstraction. A key maps
to one durable Computer. KomputerPool only coalesces concurrent boots, keeps a
bounded set of warm JavaScript handles, detaches idle sessions, and closes idle
Computers. Use withLogin(...) with maxWarm: 0 for browser, extension,
embedded, or serverless profiles that should keep no warm Computers between
requests. Plain get(...) and login(...) keep a warm handle until
sweep(...) or release(...).
For operations dashboards and leak checks, use pool.stats(),
computer.stats(), and agent.stats(). These are local-worker counters for
warm Computers, booting entries, local session handles, active virtual
processes, commands, hooks, crons, mounts, hosts, handlers, and shell
functions. They do not scan durable JSONL history and do not hold extra
references.
computer.close() releases local active process handles as well as session
handlers. Durable background process rows stay in KState for recovery instead
of keeping worker timers or remote stream readers alive after shutdown.
Custom state and remote adapters can expose close(). computer.close()
releases them after sessions and mounts are settled, which keeps browser,
plugin, cloud, and DB-backed runtimes from leaking bridge handles or caches.
On recovery, K seals unfinished tool calls with an uncertain tool result and
seals any running turn with a terminal turn event. That keeps the provider
message stream, event stream, and input queue from containing dangling state
after a worker takeover.
K also separates provider-message sealing from event-stream sealing. If a
durable assistant message contains tool_use, recovery appends an uncertain
tool_result message. If only a streamed public tool event was durable before
the crash, recovery emits a sealed/uncertain tool event without inventing a
provider message.
Background process receipts are also sealed append-only. Starting a background
remote or K-local process writes an uncertain receipt; normal completion appends
the same receipt id again as committed or failed. If a worker dies before
that final row exists, the receipt remains uncertain for reconciliation.
VFS effects also write receipts for vfs.write, vfs.append, vfs.mkdir,
vfs.rm, vfs.rename, vfs.symlink, vfs.chmod, and vfs.utime. Those
receipts carry caller provenance through by and record operation metadata such
as path and byte count, not file contents.
Deterministic K-owned virtual processes can do better when their state is fully
recoverable: for example, a crashed sleep 10 & is resumed from its durable
start time or completed immediately if its deadline already passed. Remote
processes and non-replayable K scripts stay conservative and reconcile through
uncertain.
Approvals use one simple public pair: hooks call ctx.ask(...), and external
code calls agent.decide(id, 'allow' | 'reject'). K stores the paused action in
.kstate; allowing it resumes the same shell, command, filesystem, model, or
turn action, including after a worker detach and later login recovery.
Restricted modes do not trust arbitrary remote shell execution. A remote must
declare hard shell sandbox support before readonly, plan, or limited can
delegate external shell commands to it. K hardens every remote sandbox request
with mandatory denyWrite entries for sensitive startup/config paths such as
shell rc files, .mcp.json, editor config directories, .git/config,
.git/hooks, and _shared, then validates paths and host allow-lists before a
Remote adapter sees the request.
limited is an explicit allow-list mode. It applies to filesystem writes,
remote shell commands, K shell commands, and direct agent.command(...) calls.
Filesystem scope entries may use ~/...; K normalizes them against the Agent's
home directory when the session or fork is created.
Markdown commands can request invocation-local mode changes, such as
readonly: true; K applies that only for the command turn and restores the
AgentSession mode afterward.
Model Providers
K models are LLM adapters with one stable surface:
model.complete({ messages })The package includes cross-platform fetch-based adapters for the primary LLM protocol families:
import {
anthropicMessagesModel,
deepseekMessagesModel,
deepseekOpenAIModel,
minimaxOpenAIModel,
openaiChatCompletionsModel,
openaiResponsesModel,
} from '@kruntime/komputer'Provider-specific continuation data is stored as adapter-owned sidecar state on
the message. K core only sees the standard content blocks and an optional
generic provider.anchor bit that asks the message window to retain a cache
anchor. OpenAI Responses encrypted reasoning, DeepSeek reasoning_content,
Anthropic signatures, and MiniMax reasoning_details are interpreted only by
their own adapters.
Other services should be exposed as commands, mounts, MCP tools, or application APIs unless they are actually LLM-like.
Secrets
Secrets are bound at runtime, not baked into images. Agents can inspect
/sys/secrets.md to know which handles exist, but the raw values are only
available to trusted command/hook/cron code through:
ctx.secret('github')Entrypoints
import { boot, memoryState } from '@kruntime/komputer'The root entrypoint is cross-platform and must be importable anywhere JavaScript can run.
import { Komputer } from '@kruntime/komputer'
import { localState, localRemote, sshRemote } from '@kruntime/komputer/node'
const computer = await Komputer.boot(image, {
state: localState('.kstate'),
remotes: {
local: localRemote(),
devbox: sshRemote({ target: 'agent@devbox', args: ['-T'] }),
},
})Node-only helpers live in the /node subpath so browser, mini program,
extension, and embedded bundles do not inherit Node filesystem/process modules.
localRemote({ sandbox: 'bubblewrap' }) may declare hard shell sandbox support
on Linux hosts where Bubblewrap is installed and usable. If it is not usable,
the adapter reports unsupported sandboxing and restricted-mode real shell
execution fails closed.
sshRemote(...) delegates through the local OpenSSH client. It is a real
remote shell adapter, but SSH alone is not a sandbox attestation, so restricted
modes that require a hard sandbox fail closed.
