Check
Validates required manifest fields, container protocol settings, subscribed events, permissions, and JavaScript syntax.
From a tiny HTTP handler to a signed, isolated OCI image—with conversation context and optional private databases.
Start from the runnable example. One command validates it, builds the image, tests the real signed protocol, and leaves the image ready for the desktop.
cd examples/container-plugin npm install npm run smoke # In Individual Open Source: Plugins → Add/reload local folder # Iterate: edit → check → image → reload folder → trigger event
Validates required manifest fields, container protocol settings, subscribed events, permissions, and JavaScript syntax.
Builds and starts the image with runtime restrictions, verifies health and HMAC signing, and round-trips conversation context.
Rebuild the same local tag and choose the folder again. Desktop detects the new image and replaces the development container.
OpenLeash pulls a digest-pinned OCI image and calls a small HTTP API. It never builds plugin source code while an agent request is waiting.
OpenLeash event -> authenticated runtime router -> isolated plugin container -> openleash-container-plugin.v1 response Required: GET /healthz POST /v1/events Optional: POST /v1/transform POST /v1/tools/execute
Every response echoes the exact protocol and requestId. OpenLeash rejects mismatched or stale responses.
Validate the timestamp and HMAC signature before parsing or acting on an invocation.
Run non-root with a read-only root filesystem, explicit resources, network policy, and no Docker or Kubernetes socket.
The application listens on port 8080, writes only to /data or /tmp, and includes both an HTTP health endpoint and OCI health check.
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY server.mjs ./
RUN mkdir -p /data && chown -R node:node /app /data
USER node
ENV NODE_ENV=production
ENV PORT=8080
EXPOSE 8080
VOLUME ["/data"]
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:8080/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"
CMD ["node", "server.mjs"]The host supplies plugin identity, trusted tenant context, resolved settings, input, and results from requested host capabilities.
POST /v1/events
{
"protocol": "openleash-container-plugin.v1",
"requestId": "8fe6...",
"round": 0,
"plugin": {
"id": "acme.history-aware",
"version": "1.0.0"
},
"tenant": {
"organizationId": "trusted-context",
"userId": "trusted-context"
},
"event": "prompt.beforeSubmit",
"settings": {
"profileIds": [],
"configHash": "..."
},
"config": {},
"input": {},
"capabilityResults": {}
}Return typed output only. Plugins do not call desktop UI, OpenLeash tables, or provider credentials directly.
{
"protocol": "openleash-container-plugin.v1",
"requestId": "8fe6...",
"status": "completed",
"output": {
"status": "passed",
"summary": "Event processed.",
"findings": []
}
}Do not build a second conversation database. Request a bounded window from the authenticated current session.
{
"protocol": "openleash-container-plugin.v1",
"requestId": "8fe6...",
"status": "capability_required",
"capabilityRequests": [{
"id": "context.conversation.recent:0",
"capability": "context.conversation.recent",
"request": { "limit": 20 }
}]
}The normalized event already carries its transcript when the agent transport provides one.
The container cannot select another organization, user, or arbitrary session.
Keep capabilities.storage for small plugin-owned values such as checkpoints or notification deduplication.
A userId field does not make unreviewed shared code safe. OpenLeash chooses the workload boundary before the plugin receives traffic.
Warm shared replicas; durable user data only through host-scoped capabilities.
One user + plugin + version workload, route, secrets, persistent volume, and database role.
One organization-bound workload and storage boundary.
The customer operates the workload, database, backups, and policy.
A community image is never promoted to shared-trusted automatically. A user-dedicated image is built and published once by the developer. Enabling it creates its route, secret, persistent volume and running pod. Desktop login/presence prewarms it before agent traffic. An event normally only routes to a ready pod; starting from an event is a bounded recovery fallback.
When the user has no connected desktop and no plugin activity for an operator-defined grace period, the pod may stop while its volume remains. The next desktop presence starts it again. Users can choose always-warm operation when eliminating cold starts matters more than compute cost.
A user-dedicated plugin may run its application and private PostgreSQL as one stateful container appliance. The database is never shared or publicly exposed.
# Entrypoint starts private PostgreSQL first. export PGDATA=/data/postgres initdb -D "$PGDATA" --username=plugin postgres -D "$PGDATA" -h 127.0.0.1 & # The app connects over loopback. export DATABASE_URL=postgresql://plugin@127.0.0.1/plugin exec node server.mjs # Manifest requirements: placement: "either" isolation: "user-dedicated" storage.persistent: true
PGDATA lives under /data on one user/plugin-specific single-writer volume. Pod replacement or suspension does not delete it.
Bundled PostgreSQL is for user-dedicated stateful workloads only. It listens on loopback, runs non-root, and is never horizontally autoscaled.
Cloud-agent events use the cloud runtime and its private volume.
Local-agent events use the desktop runtime and its private volume. OpenLeash does not copy or merge database files.
Persistence does not make two databases synchronizable. Use conversation context for consistent history-aware behavior.
Run locally; /data is private to that desktop runtime.
Run in cloud; /data is private to that cloud runtime.
Run where the event originates; use conversation context for shared history.
Keep the digest out during local iteration. Pin the pushed digest before submitting the release so reviewed source cannot be replaced.
npm install
npm run smoke
docker push ghcr.io/acme/history-aware:1.0.0
docker inspect --format='{{index .RepoDigests 0}}' \
ghcr.io/acme/history-aware:1.0.0