greentic-runner-host
greentic-runner-host packages the Greentic runner as a standalone crate. It owns tenant bindings, the pack watcher, Wasmtime glue, canonical ingress adapters (Telegram, Teams, Slack, WebChat, Webex, WhatsApp, generic webhook, timers), the state machine (pause/resume, session/state persistence), and admin/health endpoints. Binaries such as greentic-runner or greentic-demo embed this crate instead of vendoring runtime internals.
Provider execution is provider-core only: packs declare provider runtimes via the greentic.ext.provider extension and invoke them with the provider.invoke node, instantiating greentic:provider/schema-core@1.0.0 components. Legacy typed provider worlds are not supported.
Architecture highlights
- Pack ingestion – consumes a JSON index (local path, HTTPS, or cloud bucket) via
runner-core, verifies signatures/digests (PACK_PUBLIC_KEY,PACK_VERIFY_STRICT), caches artifacts underPACK_CACHE_DIR, and supports ordered overlays per tenant. - Hot reload –
PACK_REFRESH_INTERVALdrives a watcher that resolves the index, preloads packs, and swaps tenant runtimes atomically;/admin/packs/reloadtriggers the same path on demand. Overlays can be added/removed without touching the base pack. - Canonical ingress – all adapters normalize raw provider payloads into the shared schema:
Canonical session keys follow{tenant}:{provider}:{conversation-or-thread-or-channel}:{user}, ensuring pause/resume and dedupe behave consistently per adapter. - Sessions & state – the host bundles
greentic-session/greentic-state. Multi-turn flows pause viasession.wait; the runtime storesFlowSnapshots keyed by the canonical session, resumes on the next ingress event, and clears the entry on completion. Packs can accessgreentic:state/store@1.0.0when the component declares state capability and the policy allows it. - Telemetry & admin – optional OTLP bootstrapping (
greentic-telemetry),/healthz, and bearer-protected/adminendpoints (loopback-only whenADMIN_TOKENis unset).
Pack index format
Pack ingestion is driven by a JSON index (see examples/index.json). Each tenant entry declares a main_pack plus optional overlays, letting you layer overrides without rebuilding the base artifact:
During a reload the watcher resolves each locator (filesystem, HTTPS, OCI, S3, GCS, Azure blob), verifies digests/signatures, caches artifacts, and constructs a TenantRuntime that loads the main pack plus overlays in order. Overlay changes are safe to deploy independently—crates/tests/tests/host_integration.rs includes regression coverage.
Materialized packs
Runner can also execute a materialized pack directory (contains manifest.cbor, flows/templates, and components/<id>.wasm) or a .gtpack paired with local component files. Component resolution now prefers explicit overrides, then the materialized directory, and finally embedded archive entries; missing components raise a clear error. The desktop CLI exposes --components-dir / --components-map so distributor-produced layouts can run without the runner fetching OCI components itself.
Pause & resume semantics
Packs can pause mid-flow by emitting the session.wait component. The host persists the FlowSnapshot (current node pointer + execution state) into greentic-session. The next inbound activity for the same canonical session key (tenant:provider:channel:conversation:user) automatically resumes the stored snapshot, continues execution, and clears the entry when the flow completes. This makes multi-message LLM flows and human-in-the-loop approvals idempotent without bespoke session wiring.
OAuth broker integration
Each tenant bindings file may optionally declare an oauth block:
oauth:
http_base_url: https://oauth.api.greentic.net/
nats_url: nats://oauth-broker:4222
provider: greentic.oauth.default
env: prod # optional, falls back to GREENTIC_ENV/local
team: platform # optional logical scope
When present, the host wires greentic-oauth-host, keeps a tenant-scoped
client configuration, and registers the greentic:oauth-broker@1.0.0/world broker WIT world in Wasmtime. Packs that import the world can ask the host for
consent URLs, exchange codes for tokens, or fetch stored tokens, while
environments without the block behave exactly as before (no broker world is
wired).
Quick start
use ;
async
Cargo features
verify(default) – validate pack files exist before loading.mcp– enable tool invocation through themcp-execbridge.telemetry– wire OTLP export viagreentic-telemetry.
Environment
| Variable | Description | Default |
|---|---|---|
PACK_SOURCE |
Default resolver scheme when the index omits one (fs, http, oci, s3, gcs, azblob) |
fs |
PACK_INDEX_URL |
URI or path to the pack index consumed by the watcher | required |
PACK_CACHE_DIR |
Location for the content-addressed pack cache | .packs |
PACK_PUBLIC_KEY |
Optional Ed25519 key (ed25519:BASE64...) to enforce signature checks |
unset |
PACK_VERIFY_STRICT |
Force signature verification even when no PACK_PUBLIC_KEY is provided (1/true), or opt out when the key is present (0/false) |
PACK_PUBLIC_KEY driven |
PACK_REFRESH_INTERVAL |
Interval used by the background watcher (30s, 5m, etc.) |
30s |
TENANT_RESOLVER |
Router mode for HTTP requests (host, header, jwt, env) |
env |
DEFAULT_TENANT |
Fallback tenant identifier when the resolver cannot infer one | demo |
SECRETS_BACKEND |
Secrets provider to initialise (env, aws, gcp, azure) |
env |
OTEL_SERVICE_NAME |
Overrides the OTLP service name advertised to the collector | greentic-runner-host |
OTEL_EXPORTER_OTLP_ENDPOINT |
Explicit OTLP collector endpoint | provider preset / unset |
ADMIN_TOKEN |
Bearer token required for /admin endpoints (loopback-only access if unset) |
unset |
SLACK_SIGNING_SECRET |
HMAC secret for Slack Events/Interactive adapters | unset |
WEBEX_WEBHOOK_SECRET |
Signature key for Cisco Webex webhook validation | unset |
WHATSAPP_VERIFY_TOKEN / WHATSAPP_APP_SECRET |
Verification + signature secrets for WhatsApp Cloud API | unset |
PACK_VERIFY_STRICT |
Enforce signature checks even without a public key | driven by key |
Admin API
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
Liveness check (telemetry, secrets, active packs) |
GET |
/admin/packs/status |
Lists loaded tenants, versions, and digests plus last reload info |
POST |
/admin/packs/reload |
Triggers an immediate pack refresh via the watcher |
If ADMIN_TOKEN is set, clients must send Authorization: Bearer <token>; otherwise, admin endpoints are limited to loopback connections.
Ingress adapters
| Adapter | Route | Session anchor | Notes / Env |
|---|---|---|---|
| Telegram Bot API | POST /messaging/telegram/webhook |
chat.id:user.id (fallback to user.id) |
Uses update_id for dedupe; outbound path relies on TELEGRAM_BOT_TOKEN |
| Microsoft Teams (Bot Framework) | POST /teams/activities |
replyToId → conversation.id → channel |
Accepts Activities JSON (channelData, attachments) |
| Slack Events API | POST /slack/events |
thread_ts → channel |
Requires SLACK_SIGNING_SECRET, dedupes via event_id, handles retries |
| Slack Interactive | POST /slack/interactive |
channel/thread from payload |
Same signing secret; parses payload= form body |
| WebChat / Direct Line | POST /webchat/activities |
conversation.id |
Mirrors Bot Framework schema; attachments mapped 1:1 |
| Cisco Webex | POST /webex/webhook |
parentId → roomId |
Optional WEBEX_WEBHOOK_SECRET; keeps requires_auth metadata for file URLs |
| WhatsApp Cloud API | GET/POST /whatsapp/webhook |
messages[].from |
WHATSAPP_VERIFY_TOKEN (challenge) + WHATSAPP_APP_SECRET (signature); interactive/list replies → canonical buttons |
| Generic Webhook | ANY /webhook/:flow_id |
Idempotency-Key header (if present) |
Wraps method/path/headers/body into canonical payload |
| Timers / Cron | Defined in bindings.yaml |
schedule_id |
Schedules flows with normalized cron (seconds field injected) |
Each adapter injects the canonical payload (tenant, provider, provider_ids, session, timestamp, text, attachments, buttons, entities, metadata, channel_data, raw) and uses the same session-key policy {tenant}:{provider}:{conversation-or-thread-or-channel}:{user} enforced everywhere. Custom adapters can follow the same pattern by translating incoming payloads into an IngressEnvelope.
License
This project is licensed under the MIT License.