Example (shell)
Example — a shell engine
The simplest possible engine — a few lines of bash with no SDK — showing the raw context/result protocol any language can implement.
The Python SDK removes boilerplate, but an engine is really just a
container that reads /verentis/context.json and writes /verentis/result.json. Any language can do
that. Here's a complete engine in a few lines of bash + jq — no SDK.
It handles .sh files with two tools: execute (run the script) and lint (bash -n syntax check).
The manifest
# sh.engine.yaml
api-version: verentis.io/v1
kind: ExecutionEngine
metadata:
name: shell
display-name: Shell
description: Run and lint shell scripts.
icon: lucide:square-terminal
version: 0.1.0
author: Verentis
spec:
runtimes:
docker:
image: ghcr.io/you/engine-shell:0.1.0
pull-policy: IfNotPresent
file-types:
- pattern: text/x-shellscript
extensions: [.sh]
priority: 100
execution-modes:
- request-response
tools:
- name: execute
description: Execute the shell script and capture stdout.
input-schema: { type: object, properties: {}, required: [] }
output-schema:
type: object
properties: { status: { type: string }, data: { type: object } }
required: [status]
- name: lint
description: Syntax-check the script with `bash -n`.
input-schema: { type: object, properties: {}, required: [] }
output-schema:
type: object
properties: { status: { type: string }, data: { type: object } }
required: [status]
capabilities: [files]
permissions:
- node.file.read
resources: { cpu: "0.25", memory: "128Mi", timeout: 120, max-concurrent: 5 }
sandbox: { network: none, read-only-root: true, no-new-privileges: true }
The entrypoint
The platform mounts the target script at /verentis/script.sh and the context at
/verentis/context.json. Read the tool, do the work, write the result:
#!/usr/bin/env bash
# entrypoint.sh — the whole engine.
set -euo pipefail
CTX=/verentis/context.json
OUT=/verentis/result.json
SCRIPT=/verentis/script.sh
tool=$(jq -r '.execution.tool // "execute"' "$CTX")
# Emit a result document in the shape the platform expects.
emit() { jq -n --arg status "$1" --argjson data "$2" \
'{status: $status, artifacts: [], data: $data}' > "$OUT"; }
case "$tool" in
execute)
if out=$(bash "$SCRIPT" 2>&1); then
emit success "$(jq -n --arg o "$out" '{stdout: $o}')"
else
code=$?
jq -n --arg o "$out" --argjson c "$code" \
'{status:"failed", artifacts:[], error:$o, data:{exitCode:$c}}' > "$OUT"
exit "$code"
fi
;;
lint)
if err=$(bash -n "$SCRIPT" 2>&1); then
emit success '{"diagnostics": []}'
else
emit failed "$(jq -n --arg e "$err" '{diagnostics: [$e]}')"
fi
;;
*)
jq -n --arg t "$tool" '{status:"failed", artifacts:[], error:("unknown tool: "+$t)}' > "$OUT"
exit 1
;;
esac
The image
FROM alpine:3.20
RUN apk add --no-cache bash jq
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
Try it locally
Same mock-context flow as any engine (see Testing your engine):
docker build -t engine-shell:dev .
mkdir -p .local/verentis
echo 'echo "hello from a shell engine"' > .local/verentis/script.sh
cat > .local/verentis/context.json <<'JSON'
{ "version": 1,
"execution": { "id": "local", "mode": "request-response", "tool": "execute", "trigger": "user", "timeout": 120 },
"workspace": { "id": "ws-local" },
"file": { "path": "/scripts/hello.sh", "branch": "main" },
"input": { "arguments": [], "environment": {}, "parameters": {} } }
JSON
docker run --rm -v "$PWD/.local/verentis:/verentis" engine-shell:dev
cat .local/verentis/result.json
# -> {"status":"success","artifacts":[],"data":{"stdout":"hello from a shell engine"}}
Reading & writing files without an SDK
This example doesn't touch the VFS. To read/write workspace files from a no-SDK engine, call the gateway
directly with curl using platform.apiUrl + platform.token.accessToken from the context — the same
/{version}/files/... API the SDKs wrap. See Files & nodes.