Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions .github/workflows/llrt-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: LLRT Native Packages

on:
workflow_dispatch:
pull_request:
branches: [main]
paths:
- ".github/workflows/llrt-native.yml"
- "packages/llrt/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "Taskfile.yml"
push:
branches: [main]
paths:
- ".github/workflows/llrt-native.yml"
- "packages/llrt/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "Taskfile.yml"
release:
types: [published]

permissions:
contents: read
id-token: write

jobs:
build-native:
name: Build ${{ matrix.platform_arch_abi }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
platform_arch_abi: darwin-arm64
runner: macos-15
- target: x86_64-apple-darwin
platform_arch_abi: darwin-x64
runner: macos-15-intel
- target: x86_64-unknown-linux-gnu
platform_arch_abi: linux-x64-gnu
runner: ubuntu-24.04
- target: aarch64-unknown-linux-gnu
platform_arch_abi: linux-arm64-gnu
runner: ubuntu-24.04-arm

steps:
- uses: actions/checkout@v6

- uses: jdx/mise-action@v4

- run: task install

- run: rustup target add ${{ matrix.target }}

- run: pnpm --filter @robinbraemer/llrt run create:native-packages

- run: pnpm --filter @robinbraemer/llrt run verify:native-artifacts

- run: pnpm --filter @robinbraemer/llrt run prepare:llrt-source

- run: pnpm --filter @robinbraemer/llrt run build:native:target
env:
LLRT_TARGET: ${{ matrix.target }}

- run: pnpm --filter @robinbraemer/llrt run collect:native-artifacts

- name: Verify optional native package
working-directory: packages/llrt/npm/${{ matrix.platform_arch_abi }}
run: npm pack --dry-run

- run: pnpm --filter @robinbraemer/llrt run build

- name: Smoke-test packed install on native runner
run: pnpm --filter @robinbraemer/llrt run smoke:packed-install

- uses: actions/upload-artifact@v4
with:
name: llrt-native-${{ matrix.platform_arch_abi }}
path: packages/llrt/npm/${{ matrix.platform_arch_abi }}/llrt_node.${{ matrix.platform_arch_abi }}.node
if-no-files-found: error

package:
name: Package LLRT
runs-on: ubuntu-24.04
needs: build-native
steps:
- uses: actions/checkout@v6

- uses: jdx/mise-action@v4

- run: task install

- run: pnpm --filter @robinbraemer/llrt run create:native-packages

- run: pnpm --filter @robinbraemer/llrt run verify:native-artifacts

- uses: actions/download-artifact@v4
with:
pattern: llrt-native-*
path: artifacts/llrt-native

- name: Place native artifacts into npm package directories
run: |
for artifact_dir in artifacts/llrt-native/llrt-native-*; do
platform_arch_abi="${artifact_dir##*/llrt-native-}"
mkdir -p "packages/llrt/npm/${platform_arch_abi}"
cp "${artifact_dir}/llrt_node.${platform_arch_abi}.node" "packages/llrt/npm/${platform_arch_abi}/"
done

- run: pnpm --filter @robinbraemer/llrt run verify:native-artifacts:strict

- run: pnpm --filter @robinbraemer/llrt run build

- name: Verify main package
working-directory: packages/llrt
run: npm pack --dry-run

- name: Verify optional native packages
run: |
for package_dir in packages/llrt/npm/*; do
(cd "${package_dir}" && npm pack --dry-run)
done

- run: pnpm --filter @robinbraemer/llrt run smoke:packed-install

- name: Prepare native package metadata
if: github.event_name == 'release'
env:
GITHUB_REPOSITORY: ""
run: pnpm --filter @robinbraemer/llrt run prepare:native-publish

- name: Publish optional native packages
if: github.event_name == 'release'
run: |
for package_dir in packages/llrt/npm/*; do
(cd "${package_dir}" && npm publish --access public --no-git-checks)
done

- name: Publish main LLRT package
if: github.event_name == 'release'
run: pnpm --filter @robinbraemer/llrt publish --access public --no-git-checks
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
node_modules/
dist/
target/
*.node
*.tgz
.DS_Store
packages/llrt/native/index.d.ts
packages/llrt/native/index.js
packages/llrt/vendor/
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ tasks:
test:
desc: Run tests
cmds:
- pnpm --filter @robinbraemer/codemode test {{.CLI_ARGS}}
- pnpm test {{.CLI_ARGS}}

test:watch:
desc: Run tests in watch mode
Expand Down
180 changes: 180 additions & 0 deletions internal/superpowers/plans/2026-06-10-standalone-llrt-calljson.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Standalone LLRT Runtime Adoption Plan

**Date:** 2026-06-10

**Status:** Prototype implemented; release and promotion gates remain.

**Working directory:** `/Users/robin/Developer/cnap-tech/codemode`

## Objective

Move codemode toward LLRT as the preferred JavaScript execution engine by building a standalone `@robinbraemer/llrt` package, wiring codemode to use it experimentally, and proving the safety/performance properties needed before CNAP adopts it as the default.

## Completed Prototype Work

- [x] Created `packages/llrt` as a standalone TypeScript package.
- [x] Added a Rust `napi-rs` native crate in `packages/llrt/native`.
- [x] Built a native smoke export.
- [x] Implemented `LlrtRuntime.callJson<TInput, TOutput>()`.
- [x] Added structured result/error types.
- [x] Added JSON input serialization and output parsing.
- [x] Added fresh LLRT VM per call.
- [x] Added wall-time timeout with rquickjs interrupt handler plus outer Tokio timeout.
- [x] Added memory limit via LLRT/rquickjs runtime memory limit.
- [x] Added async host callbacks through a JSON-safe N-API dispatcher.
- [x] Added standalone tests for execution, errors, isolation, timeout, memory pressure, and host callbacks.
- [x] Added LLRT stress tests for per-call memory overrides, repeated isolation, concurrent host callbacks, and concurrent stalled callback timeouts.
- [x] Made root `pnpm test` run both `@robinbraemer/llrt` and `@robinbraemer/codemode`.
- [x] Added `LlrtNativeExecutor` in codemode.
- [x] Ran the shared codemode executor contract against `LlrtNativeExecutor`.
- [x] Made `createExecutor()` prefer LLRT when `@robinbraemer/llrt` is installed.
- [x] Kept fallback executors available.
- [x] Tightened fallback policy so broken installed LLRT does not silently fall back.
- [x] Added `benchmark:executors` to collect local LLRT vs `isolated-vm` vs QuickJS WASM evidence.
- [x] Replaced absolute local LLRT Rust paths with a package-local `prepare:llrt-source` step and relative Cargo paths.
- [x] Added napi-rs optional platform package manifests for darwin arm64/x64 and linux glibc arm64/x64.
- [x] Taught the native loader to try the current platform's optional native package.
- [x] Verified `npm pack --dry-run` keeps the main package JS/types-only and puts the current platform native artifact in its optional package.
- [x] Added `.github/workflows/llrt-native.yml` to build every declared native target, dry-run-pack optional packages, assemble artifacts, and publish the LLRT package family on releases.
- [x] Added a workflow/manifest guard test so LLRT tests fail if the native target matrix or main package prepublish hook regresses.
- [x] Added an explicit `LLRT_TARGET` native build script so CI passes native targets directly to napi-rs instead of relying on package-manager argument forwarding.
- [x] Added `smoke:packed-install` to pack the main and current optional native package, install them into a temporary consumer project, and execute LLRT from that install.

## Current Known Rough Edges

- [ ] Source builds still need network access, `yarn install`, and upstream `make js` until native prebuilds or upstream crates exist.
- [ ] Native prebuild CI is implemented but not yet proven by a GitHub Actions run across all target runners.
- [ ] Cross-platform package artifact strategy is specified and locally smoke-tested for the current platform, but full target tarballs still need GitHub matrix evidence.
- [ ] Stress coverage exists for core LLRT safety properties; representative CNAP/codemode workload stress still needs a larger report.
- [ ] The standalone runtime security policy is not final; codemode masks `require`, `process`, and `fetch` in its adapter.
- [ ] `LlrtProcessExecutor` remains as a proof-of-concept fallback; decide later whether to keep, hide, or remove it.
- [ ] Full repo lint/preflight has not been run for this branch.

## Next Engineering Steps

1. **Make the native dependency reproducible**
- Status: implemented for local/source builds.
- `prepare:llrt-source` shallow-fetches `awslabs/llrt` at `80c113ddee03ff1926068193f50fe35f41ca2105`.
- The script runs upstream `yarn install --immutable` and `make js` so `llrt_core` has the generated `bundle/js` assets its build script requires.
- Cargo now uses relative paths under `packages/llrt/vendor/llrt`.
- Remaining release question: whether source builds are acceptable, or whether npm should rely only on prebuilt native artifacts.

2. **Add benchmark and stress evidence**
- Status: baseline benchmarks and core LLRT stress tests implemented.
- Measured cold simple execution.
- Measured JSON-heavy OpenAPI-spec execution.
- Measured async host callback chains.
- Stress-tested repeated fresh VM execution.
- Stress-tested concurrent host callback traffic.
- Stress-tested stalled host callback timeouts.
- Stress-tested per-call memory overrides.
- Compare native LLRT, `isolated-vm`, and QuickJS WASM.
- Remaining: run/report representative CNAP/codemode workloads, preferably in CI.

3. **Harden the native package release path**
- Status: local package layout implemented; native prebuild workflow added.
- Generated napi-rs platform packages under `packages/llrt/npm`.
- Main package ships JS/types only.
- Platform packages ship `.node` binaries with OS/CPU/libc gates.
- Loader tries local development binaries first, then the current optional platform package.
- Prebuild matrix covers darwin arm64/x64 and linux glibc arm64/x64.
- Workflow dry-run-packs the main package and every optional package before release publishing.
- Targeted native builds require `LLRT_TARGET` and fail fast when it is missing.
- Packed-install smoke test imports and executes LLRT from a temporary consumer project.
- Document supported Node/platform combinations.
- Publish with `napi pre-publish` so optional dependencies are injected at the release version.
- Remaining: run the workflow on GitHub and inspect artifacts/logs from all runners.

4. **Finalize codemode default behavior**
- Keep LLRT first in `createExecutor()` when installed.
- Keep explicit fallback engine classes exported.
- Add user-facing docs for selecting a non-default engine if needed.
- Keep installed-but-broken LLRT failures loud.

5. **Prepare CNAP adoption**
- Release or locally link the codemode package.
- Update CNAP dependency.
- Run CNAP-specific codemode flows against LLRT.
- Keep rollback path to `isolated-vm` or QuickJS.

## Verification Commands

Use Node 24 and pnpm 10:

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
--filter @robinbraemer/llrt run dev:native
```

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
--filter @robinbraemer/llrt exec vitest run test/native-loader.test.ts --pool=forks --maxWorkers=1 --testTimeout=15000
```

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
--filter @robinbraemer/llrt exec vitest run test/call-json.test.ts test/runtime.test.ts test/native-smoke.test.ts test/stress.test.ts --pool=forks --maxWorkers=1 --testTimeout=20000
```

```bash
cd /Users/robin/Developer/cnap-tech/codemode/packages/llrt && npm pack --dry-run
cd /Users/robin/Developer/cnap-tech/codemode/packages/llrt/npm/darwin-arm64 && npm pack --dry-run
cd /Users/robin/Developer/cnap-tech/codemode && mise exec -- pnpm --filter @robinbraemer/llrt run smoke:packed-install
```

```bash
cd /Users/robin/Developer/cnap-tech/codemode && mise exec actionlint -- actionlint .github/workflows/*.yml
cd /Users/robin/Developer/cnap-tech/codemode && mise exec -- task ci
```

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
test
```

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
--filter @robinbraemer/llrt run typecheck
```

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
--filter @robinbraemer/codemode run typecheck
```

```bash
PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \
/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \
--filter @robinbraemer/codemode run benchmark:executors
```

Latest local sample on 2026-06-10:

| Engine | Scenario | Mean ms | Errors |
| --- | --- | ---: | ---: |
| `llrt-native` | `cold-simple` | 2.47 | 0 |
| `llrt-native` | `openapi-json-scan` | 3.74 | 0 |
| `llrt-native` | `host-callbacks-parallel` | 2.24 | 0 |
| `isolated-vm` | `cold-simple` | 1.01 | 0 |
| `isolated-vm` | `openapi-json-scan` | 1.32 | 0 |
| `isolated-vm` | `host-callbacks-parallel` | 0.83 | 0 |
| `quickjs-wasm` | `cold-simple` | 5.50 | 0 |
| `quickjs-wasm` | `openapi-json-scan` | 4.92 | 0 |
| `quickjs-wasm` | `host-callbacks-parallel` | 3.61 | 0 |

## Promotion Standard

Promote LLRT from default candidate to production default only when the evidence proves:

- fresh install can build or install `@robinbraemer/llrt`;
- supported platform native artifacts are available;
- codemode executor contract passes in CI;
- safety stress tests cover memory, timeout, callback stalls, and isolation;
- performance is materially better or operationally simpler than the current default for representative codemode workloads;
- fallback engine selection is documented and tested.
Loading
Loading