Build a Host in TypeScript
Prepare real runtime inputs, verify the Host lifecycle, and identify current Bun Plugin blockers.
Lenso’s TypeScript/Bun Host uses @lenso/cli/host for composition, generated
host.js for lifecycle control, and Rust lenso-host-runtime for the Kernel and
Bun/Process Adapters. The Host API exposes start, inspect, and stop;
Plugins own business entrypoints.
Verification status and prerequisites
The following results were obtained from a fresh directory on macOS ARM64 on 2026-09-07. This is not yet a working end-to-end Bun Plugin Host quickstart. First verify the real lifecycle with an empty Host, then check the Plugin blocker below.
| Path | Observed result |
|---|---|
| Bun Plugin scaffold, check, pack | Passed; produced Bundle 4 |
| Add that Bundle to a TypeScript Host | Runtime profile admission rejected it |
| Empty Host build, check, show, prepare | Passed |
| Generated entrypoint start, inspect, stop, start again | Passed; recovered the same Generation |
| Physical termination | termination: confirmed, but forced: true |
Business invocation through CLI plugin dev |
Failed with a nested runtime error |
You need Git, the Rust toolchain, Bun, and macOS ARM64. Run these commands in a
fresh directory. Validation used Bun 1.4.1-canary.1; it does not establish support
for every Bun version or another platform.
mkdir host-walkthrough
cd host-walkthrough
bun add --dev @lenso/cli@0.16.1
Obtain the runtime inputs
The npm CLI ships a platform resolver and generated control library, not the complete Host runtime. Build the two binaries from the pinned sources and lockfiles used in this verification, rather than relying on a global CLI:
git clone https://github.com/LioRael/lenso-bun-adapter.git runtime-source
git -C runtime-source checkout 3c1f2f011f4f9d496db337f296ad686ab0a985e0
cargo build --locked --manifest-path runtime-source/Cargo.toml -p lenso-host-runtime
git clone https://github.com/LioRael/lenso-runtime-rust.git owner-source
git -C owner-source checkout 1e3915a
cargo build --locked --manifest-path owner-source/Cargo.toml \
-p lenso-runner --bin lenso-process-owner --features process-owner
| Preparation input | Source |
|---|---|
--runtime |
runtime-source/target/debug/lenso-host-runtime, version 0.1.5 |
--owner |
owner-source/target/debug/lenso-process-owner, from lenso-runner 0.2.13 |
--resolver |
node_modules/@lenso/cli/vendor/darwin-arm64/lenso |
| Control library | Passed by the npm launcher; do not substitute an older global lenso |
--bun |
Required when selecting Bun implementations; supply a real target executable |
--notices |
Nonempty third-party notices applicable to the files you distribute |
If CARGO_TARGET_DIR is set, use that output directory instead of the default
paths above. For this local empty-Host demonstration, use the source license as
the notices input:
cp runtime-source/LICENSE THIRD_PARTY_NOTICES.txt
This is a local demonstration input, not a complete redistribution notice for the runtime. Before shipping to others, collect applicable notices for the actual binaries, dependencies, and Bun, and use release builds for the intended target.
Build an empty Host
This empty-host.ts needs no hypothetical example Bundle. It verifies the control
path and intentionally runs no business Plugin.
import { defineHost } from "@lenso/cli/host";
export default defineHost({ id: "example.empty-host", plugins: [] });
bun --bun run lenso app build --source empty-host.ts --target aarch64-apple-darwin --out build/empty
bun --bun run lenso app check --root build/empty
bun --bun run lenso app show --root build/empty --json
Expect zero Instances and bindings. Each build/prepare needs a fresh output
directory. Build output only contains authority and the Bundle inventory;
lenso run cannot start it.
Prepare and start
bun --bun run lenso app prepare \
--build build/empty --target aarch64-apple-darwin \
--runtime runtime-source/target/debug/lenso-host-runtime \
--owner owner-source/target/debug/lenso-process-owner \
--resolver node_modules/@lenso/cli/vendor/darwin-arm64/lenso \
--notices THIRD_PARTY_NOTICES.txt --out dist/empty
mkdir -p state/app
The empty Host does not need --bun. A distribution selecting Bun implementations
must also supply its exact Bun executable. The distribution lock verifies every
immutable file; do not rewrite hashes to bypass verification failures.
Use state/app as the mutable Root. Do not run against build/empty or copy a
competing Host authority into the Root.
Create this script to wait for readiness, inspect the App, and stop through the generated entrypoint:
import { start } from "./dist/empty/host.js";
const app = await start({ root: `${import.meta.dir}/state/app` });
try {
console.log(JSON.stringify(await app.inspect()));
} finally {
const outcome = await app.stop();
console.log(JSON.stringify(outcome));
if (outcome.shutdown !== "suspended" ||
outcome.ownership.termination !== "confirmed") {
process.exitCode = 1;
}
}
bun lifecycle.ts
bun lifecycle.ts
start returns after the Ready Gate opens. Inspection includes revision,
generation, instances, and diagnostics. Restart should recover the same
Generation; revision can change. The observed outcome was shutdown: suspended,
termination: confirmed, and forced: true. The first two establish durable
suspension and confirmed process termination. forced means cleanup escalated;
this is not fully graceful exit. The script preserves that field in its output.
For a long-running session, use bun dist/empty/host.js --root "$PWD/state/app";
Ctrl+C/SIGTERM requests shutdown. The default ownership registry is .lenso-owners
next to the Root. Repeated starts must share that registry; do not change it or
delete locks to bypass ownership conflicts. start also accepts registry,
startupMs, stopMs, and confirmationMs. These bound waiting, not business
shutdown guarantees.
Add a real Bun Plugin: current blocker
These commands generate and pack a real Bundle instead of referencing a
nonexistent company.notes artifact:
bun --bun run lenso plugin new example.host-echo --runtime bun
bun --bun run lenso plugin check --repo-root example.host-echo --json
bun --bun run lenso plugin pack --repo-root example.host-echo --output echo.lenso-plugin --json
import { defineHost, pluginBundle } from "@lenso/cli/host";
export default defineHost({
id: "example.echo-host",
plugins: [pluginBundle("./echo.lenso-plugin")],
});
bun --bun run lenso app build --source app.ts --target aarch64-apple-darwin --out build/echo
On the verified CLI version, the final command reports
V4 Bundle has no implementation admitted by Host policy. The Bundle declares
lenso.bun-authoring@2, while the current builder’s V2 admission uses
lenso.plugin-authoring@2. Selecting --target javascript-bun does not fix this
profile rejection. Do not edit the manifest, forge digests, or use empty-Host
success as a substitute for Plugin acceptance.
Business calls belong to the Plugin’s public entrypoint or development-time
plugin dev; app.inspect() only reads structure and there is no app.invoke().
The verified plugin dev --operation execute attempt failed with
Cannot start a runtime from within a runtime, so no successful business return
was obtained. The separate document-sync test Host is not proof
that this distribution path works.
bun --bun run lenso plugin dev --repo-root example.host-echo \
--operation execute \
--request-json '{"name":"example.host-echo","arguments_json":"{\"text\":\"hello\"}"}'
Multiple Instances, extensions, and configuration
This is a declaration example requiring your own admitted Store/Copy Bundles;
it is not an executable continuation of the echo example. Assume Store offers
the store Slot and Copy declares both named dependencies.
import { defineHost, pluginBundle } from "@lenso/cli/host";
const store = pluginBundle("./store.lenso-plugin");
export default defineHost({
id: "company.copy-host",
plugins: [
{ plugin: store, instance: "source", configuration: { namespace: "source" } },
{ plugin: store, instance: "destination", configuration: { namespace: "dest" } },
pluginBundle("./copy.lenso-plugin"),
],
slots: [{ id: "store", cardinality: "many" }],
dependencies: [
{
consumer: { plugin: "company.copy" }, requirement: "source",
allow: [{ plugin: "company.store", instance: "source" }],
default: { plugin: "company.store", instance: "source" },
},
{
consumer: { plugin: "company.copy" }, requirement: "destination",
allow: [{ plugin: "company.store", instance: "destination" }],
default: { plugin: "company.store", instance: "destination" },
},
],
});
Hosts are closed by default. Existing configuration must pass the Plugin Schema;
new Instances and replacement Releases need explicit authorization. Extensible
Slots use allow: [pluginBundle(...)] and maxInstances (1–256). Replacing a
one Slot also needs replaceable: true. configurationSchema constrains the
merged configuration in addition to the Plugin Schema; it is not an OS sandbox.
See the Host declaration reference
for full authorization examples and supported Schema keywords.
Paths resolve relative to the declaration file. Declarations support static objects, arrays, constants, and relative default imports. They do not evaluate environment reads, dynamic imports, spreads, arbitrary calls, or business modules. The Host declaration profile accepts Request and at most 256 Instances/Slots; Bun SDK Stream/Event support does not expand it.
Recovery, upgrades, and troubleshooting
After stopping, retain the external Root and shared registry and restart the unchanged distribution. The evidence establishes exact-Generation recovery, not arbitrary version upgrades. Build/prepare an upgrade into a new directory, confirm the old process has terminated, and verify compatibility with durable state; back up the Root and persistent data. There is no public generic hot-upgrade command. Do not overwrite a running distribution or edit its lock.
| Symptom | Check and action |
|---|---|
Host Catalog is unavailable |
Confirm build succeeded; do not inspect output that was never produced |
V4 Bundle has no implementation admitted by Host policy |
Compare execution class, runtime profile, and target; echo has the reproduced blocker above |
Cannot start a runtime from within a runtime |
Reproduced in CLI dev; longer timeouts cannot fix the runtime integration |
prepared Host needs the generated npm control library |
Invoke prepare through the project’s npm launcher |
distribution target ... does not support ... |
Supply artifacts matching the actual platform/architecture |
failed integrity / not a regular file |
Restore original files and check symlinks/content; do not bypass verification |
| Ownership conflict | Inspect the original process and shared registry; do not delete locks or switch registries to force concurrency |
| Startup timeout | Ready was not confirmed; inspect profiles, Bundles, and Root, not just process existence |
shutdown: failed, unconfirmed termination, or forced: true |
Preserve business shutdown and physical termination separately; do not report graceful success |
This verification covers declaration checks, real empty-Host lifecycle, and the reported failures. It does not establish a complete Bun Plugin distribution, crash injection, or cross-version upgrades. Rerun the same workflow and update the status table after the integration is corrected.