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.
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 automatically receive thestate.get/state.set/session.updatehost interface (WIT v0.6). - 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.
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.
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.