greentic-runner 0.4.23

Greentic runner binaries and re-exported host/new-runner APIs
Documentation

greentic-runner

Monorepo for the Greentic runner host, CLI, and integration tests.
The workspace centres around crates/greentic-runner-host, which is the production runtime (pack ingestion/resolvers, canonical ingress adapters for Telegram/Teams/WebChat/Slack/Webex/WhatsApp/webhook/timer, session/state glue, admin API). The top-level crate greentic-runner exposes a thin binary that embeds the host.

Quick start

# Run the HTTP host on port 8080
cargo run -p greentic-runner -- \
  --bindings examples/bindings/demo.yaml \
  --port 8080

# Optional: point at an explicit config file and print the resolved config
cargo run -p greentic-runner -- \
  --config examples/greentic.toml \
  --config-explain

# Trigger a Telegram-style webhook
curl -X POST http://localhost:8080/messaging/telegram/webhook \
  -H "Content-Type: application/json" \
  -d '{"update_id":1,"message":{"chat":{"id":42},"text":"hello"}}'

By default the host resolves greentic.toml/greentic.json (or the workspace defaults) and uses the packs/paths settings to locate the pack index (.greentic/index.json by default, falling back to examples/index.json for local runs). Network, telemetry, and secrets wiring are also taken from greentic-config. Every ingress payload (Telegram/WebChat/Slack/Webex/WhatsApp/ webhook/timer) is normalized into the canonical schema with deterministic session keys so pause/resume + dedupe work the same way across providers.

Public API

The greentic_runner crate is the supported embedding surface:

use greentic_runner::{run_http_host, start_embedded_host, RunnerConfig, HostBuilder};
use greentic_runner::config::HostConfig;
use greentic_config::ConfigResolver;

// Mirror the CLI
let resolved = ConfigResolver::new().load()?;
run_http_host(RunnerConfig::from_config(resolved, vec![bindings_path])?).await?;

// Or build an API-only host (no HTTP server) and drive it manually
let host = start_embedded_host(
    HostBuilder::new().with_config(HostConfig::load_from_path("tenant.yaml")?),
)
.await?;
host.load_pack("tenant", "./packs/demo.gtpack".as_ref()).await?;

run_http_host matches the behaviour of the greentic-runner binary (pack watcher + HTTP ingress). start_embedded_host is designed for developer tools and tests that want to load packs/bindings and call handle_activity directly without starting axum or the watcher.

Pack index schema

Pack resolution is driven by a JSON index (see examples/index.json). Each tenant entry supplies a main_pack plus optional ordered overlays:

{
  "tenants": {
    "demo": {
      "main_pack": {
        "reference": { "name": "demo-pack", "version": "1.2.3" },
        "locator": "fs:///packs/demo.gtpack",
        "digest": "sha256:abcd...",
        "signature": "ed25519:...",
        "path": "./packs/demo.gtpack"
      },
      "overlays": [
        {
          "reference": { "name": "demo-overlay", "version": "1.2.3" },
          "path": "./packs/demo-overlay.gtpack",
          "digest": "sha256:efgh..."
        }
      ]
    }
  }
}

During a reload the watcher resolves each locator (filesystem, HTTPS, OCI, S3, GCS, or Azure blob), validates the digest/signature, populates the content-addressed cache, warms Wasmtime, and swaps the TenantRuntime atomically. Overlays can be added/removed tenant-by-tenant without touching the base pack; crates/tests/tests/host_integration.rs contains a regression test for overlay reloads.

Sessions & pause/resume

Packs can emit the session.wait component to pause execution (e.g., waiting for a human reply). greentic-runner-host automatically:

  1. Serializes the FlowSnapshot (next node + execution state) into greentic-session.
  2. Uses a canonical session key (tenant:provider:channel:conversation:user) hashed into a UserId, so the next inbound activity finds the correct snapshot.
  3. Resumes the snapshot on the next activity, continues execution, and clears the stored state once the flow finishes.

No glue code is required inside packs; authors just emit session.wait and persist any additional state via greentic-state. The canonical session key format is {tenant}:{provider}:{conversation-or-channel}:{user} so every adapter participates consistently (documented in crates/greentic-runner-host/README.md).

OAuth broker world

Tenants can opt into the OAuth broker world by adding an oauth block to their bindings file. The host wires greentic-oauth-host, connects to the broker (HTTP + NATS), and exposes the WIT world greentic:oauth-broker@1.0.0/world broker to components that import it. Each flow execution receives the tenant’s TenantCtx, so deployment packs or channels can call get-consent-url, exchange-code, and get-token without embedding provider-specific logic.

tenant: acme
flow_type_bindings: { ... }
oauth:
  http_base_url: https://oauth.api.greentic.net/
  nats_url: nats://oauth-broker:4222
  provider: greentic.oauth.default
  env: prod        # optional, defaults to GREENTIC_ENV/local
  team: ops        # optional logical scoping hint

When oauth is omitted nothing changes—the linker simply skips the OAuth world and packs behave exactly as they did before. This keeps environments that do not run the broker lightweight while enabling deployment packs and channels to request consent URLs or tokens wherever the broker is configured.

Repository layout

Path Description
crates/greentic-runner-host/ Production runtime crate (docs, canonical adapters, env table, admin API)
crates/greentic-runner/ Binary that embeds the host (CLI entrypoint)
crates/tests/ Integration test harness (demo pack execution, watcher reload/overlay regression, adapter fixtures)
examples/ Sample bindings, reference index.json, example packs

Development

cargo fmt
cargo clippy
cargo test

Integration tests under crates/tests/tests/*.rs exercise the demo pack, watcher reloads (including overlays), and scaffold future adapters (webhook/timer). Enable new fixtures as adapters mature.

Ingress adapters at a glance

Provider Route Env/deps Notes
Telegram Bot API POST /messaging/telegram/webhook TELEGRAM_BOT_TOKEN (used by the egress bridge) Canonicalises update ids, dedupes via cache
Microsoft Teams (Bot Framework) POST /teams/activities None (HTTPS listener; add auth proxy externally) Uses replyToId/conversation/channel to derive session key
Slack Events API POST /slack/events SLACK_SIGNING_SECRET Handles url_verification, dedupes via event_id
Slack Interactivity POST /slack/interactive SLACK_SIGNING_SECRET Parses payload= form body; same canonical contract
WebChat / Direct Line POST /webchat/activities None Mirrors Bot Framework schema; attachments mapped 1:1
Cisco Webex POST /webex/webhook WEBEX_WEBHOOK_SECRET (optional signature) File URLs surfaced in canonical attachments
WhatsApp Cloud API GET/POST /whatsapp/webhook WHATSAPP_VERIFY_TOKEN, WHATSAPP_APP_SECRET Normalizes interactive/list replies into canonical buttons
Generic Webhook ANY /webhook/:flow_id Idempotency via Idempotency-Key header Passes normalized HTTP request object to the target flow
Timer / Cron internal bindings.yaml timer entries Schedules flow invocations using cron expressions

All adapters emit the canonical payload (tenant, provider, provider_ids, session.key, text, attachments, buttons, entities, metadata, channel_data, raw). The canonical session key {tenant}:{provider}:{conversation-or-thread-or-channel}:{user} drives dedupe and pause/resume semantics universally.

Environment variables

Common settings (full table lives in crates/greentic-runner-host/README.md):

  • PACK_REFRESH_INTERVAL – watcher cadence (e.g., 30s, 5m).
  • PORT – overrides the HTTP server port (also settable via CLI).
  • TENANT_RESOLVER, DEFAULT_TENANT – HTTP routing behaviour (host/header/jwt/env).
  • OTEL_* – OTLP exporter overrides; otherwise telemetry follows greentic-config.
  • Provider secrets such as SLACK_SIGNING_SECRET, WEBEX_WEBHOOK_SECRET, WHATSAPP_VERIFY_TOKEN, WHATSAPP_APP_SECRET, TELEGRAM_BOT_TOKEN.

Publishing

Versions are tracked per crate. Tagging master with <crate>-vX.Y.Z triggers the publish workflow which pushes the crate to crates.io. Use ci/local_check.sh before tagging to mirror the CI pipeline locally.

Bindings inference

greentic-gen-bindings can inspect a pack directory and emit a complete bindings.yaml seed using the same schema the host expects:

cargo run -p greentic-runner --bin greentic-gen-bindings \
  --pack examples/weather-demo \
  --out generated/bindings.complete.yaml \
  --complete

--complete fills safe defaults for env passthrough, network allowlists, secrets, and MCP server stubs; --strict additionally fails if HTTP/secrets/MCP requirements cannot be satisfied so pack authors can share hints via bindings.hints.yaml or meta.bindings annotations. The CLI also understands --component so future packs compiled to a Wasm component can be inspected for host imports before generating bindings.

Repo settings

Enable GitHub’s “Allow auto-merge” in repo settings and configure required branch checks; the Dependabot auto-merge workflow only acts on dependabot[bot] PRs once required checks pass.

License

MIT