Compare commits

...

11 commits
v0.3.2 ... main

Author SHA1 Message Date
6575869097 chore: org rename fai-modules/fai-plugins -> chain-modules/chain-plugins + LICENSE holder
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 1s
Signed-off-by: flemming-it <sf@flemming.it>
2026-06-16 18:25:38 +02:00
90f9431096 docs/ci: chain-*-sdk references (renamed SDK)
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 1s
Signed-off-by: flemming-it <sf@flemming.it>
2026-06-16 11:32:24 +02:00
7d52e36606 refactor: depend on renamed chain-module-sdk
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 2s
Signed-off-by: flemming-it <sf@flemming.it>
2026-06-16 11:28:15 +02:00
807cb254e0 chore: contact email platform@ -> chain@flemming.ai
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 2s
Signed-off-by: flemming-it <sf@flemming.it>
2026-06-16 10:16:31 +02:00
5f2544b6e8 docs: old product name F∆I Platform -> Ch∆In
Some checks are pending
CI / Linux x86_64 (Forgejo) (push) Waiting to run
The product is Ch∆In; the old 'F∆I Platform' name survived in module
docs/manifests. Generic 'platform' terms left untouched.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-16 10:15:27 +02:00
b58ede3dd4 refactor: rename to chain:platform WIT + provider chain
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 2s
Tracks the platform's fai->chain rename: capability provider chain,
WIT ABI chain:platform (binding paths/accessors), SDK pin bumped.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-15 22:54:54 +02:00
fcf74f179f feat: schema_version 3 with DE/EN field descriptions
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 1s
Signed-off-by: flemming-it <sf@flemming.it>
2026-06-01 22:46:17 +02:00
a9b315a1a0 feat: declare consumes: llm.chat@^0 (advisory)
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 2s
Plans this module emits typically dispatch llm.chat steps
for downstream LLM-driven work. A flow that runs an
unmodified plan needs an llm.chat provider installed; the
hub now surfaces this as a 'consumes-advisory' warning at
install time so operators see the dependency hint without
the install being blocked.

See fai/platform commit e5ba892 for the platform-side
consumes: schema.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-01 21:05:38 +02:00
d5cb4c0318 ci: sign release bundles with FAI_SIGNING_KEY
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 1s
Drop-in from fai/platform .forgejo/workflow-templates/
sign-bundle-keypair.yml. Triggers on v*.*.* tag push: builds
wasm32-wasip2, fetches the fai CLI from get.fai.flemming.ai,
packs the bundle, ECDSA P-256 signs it against the org-level
Forgejo secret FAI_SIGNING_KEY, round-trip-verifies against
infra/cosign/official.pub on fai/platform main, and attaches
bundle + .sig to the Forgejo Release via the API.

Signed-off-by: flemming-it <sf@flemming.it>
2026-05-28 23:14:29 +02:00
8ce52c775e chore(module): schema_version 2 + explicit provider
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 2s
Brings module.yaml in line with F∆I 0.12.0's capability naming
convention. The legacy schema_version 1 (with implicit provider)
still loads in 0.12.0 via the grace-period default, but this
bump makes the publisher identity explicit and unblocks
storage-layout migration via 'fai migrate'.

See fai/platform docs/reference/capability-namespaces.yaml +
docs/operations/migration-to-0.12.md.

Signed-off-by: flemming-it <sf@flemming.it>
2026-05-28 11:48:25 +02:00
25d8e6570e chore: SDK URL → git.flemming.ai/fai/module-sdk-rust
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 2s
The fai-module-sdk repo was renamed from `fai/module-sdk` to
`fai/module-sdk-rust` to align with the three SDK families
convention (base + language suffix). Pin updated accordingly.
The old git.flemming.ws URL is also retired in this pass.

Signed-off-by: flemming-it <sf@flemming.it>
2026-05-26 13:16:53 +02:00
7 changed files with 325 additions and 46 deletions

View file

@ -30,7 +30,7 @@ jobs:
git fetch --depth=1 origin "$GITHUB_SHA" git fetch --depth=1 origin "$GITHUB_SHA"
git checkout -q FETCH_HEAD git checkout -q FETCH_HEAD
# The fai-module-sdk dependency is a git dep against a repo # The chain-module-sdk dependency is a git dep against a repo
# that lives in another Forgejo org. The repo-scoped # that lives in another Forgejo org. The repo-scoped
# GITHUB_TOKEN cannot read it, and the Forgejo instance has # GITHUB_TOKEN cannot read it, and the Forgejo instance has
# REQUIRE_SIGNIN_VIEW=true so anonymous read is also denied. # REQUIRE_SIGNIN_VIEW=true so anonymous read is also denied.

223
.forgejo/workflows/sign.yml Normal file
View file

@ -0,0 +1,223 @@
# F∆I module bundle signing workflow (cosign key-pinning model).
#
# Drop-in template for any module repo under chain-modules/.
# Triggers on tag push (vX.Y.Z) — builds the module, packs the
# .fai bundle, signs the bundle bytes with the Flemming.AI
# signing key (ECDSA P-256), and attaches bundle + .sig sidecar
# to the matching Forgejo Release.
#
# The hub-side verifier ships in 0.12.0+: when an operator sets
# `require_signatures: true`, install downloads the .sig sidecar
# from `<wasm_url>.sig` and verifies it against the pinned store
# public key (built-in for `bundled` / `official`, per-store
# config for everything else).
#
# Adopt by copying this file to `.forgejo/workflows/sign.yml` in
# the module repo and replacing the MODULE_NAME placeholder.
# Add the signing key to Forgejo secrets as FAI_SIGNING_KEY (PEM,
# unencrypted, ECDSA P-256). Rotation is a 5-minute key-roll +
# binary re-release; see infra/cosign/README.md.
name: sign-bundle
on:
push:
tags:
- 'v*.*.*'
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: "1.86"
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
jobs:
sign:
runs-on: ubuntu-latest
permissions:
contents: write # attach signature to release
env:
# The canonical module name as written in module.yaml.
# Used in the bundle filename + wasm path.
MODULE_NAME: orchestrator-llm
steps:
# See fai/platform CI for background on the manual external
# checkout: the DinD runner cannot resolve forgejo:3000, so
# we clone via the external URL with $GITHUB_TOKEN.
- name: Checkout via external URL
run: |
set -eu
mkdir -p "$GITHUB_WORKSPACE"
cd "$GITHUB_WORKSPACE"
git init -q
git remote add origin \
"https://x-access-token:${GITHUB_TOKEN}@git.flemming.ai/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "$GITHUB_SHA"
git checkout -q FETCH_HEAD
# The chain-module-sdk dependency lives in another Forgejo org
# behind REQUIRE_SIGNIN_VIEW. MODULE_SDK_PAT (org-level
# secret, read-only on fai/module-sdk) authenticates cargo's
# git fetch via insteadOf.
- name: Configure git URL rewrite for SDK fetch
env:
SDK_PAT: ${{ secrets.MODULE_SDK_PAT }}
run: |
git config --global \
"url.https://x-access-token:${SDK_PAT}@git.flemming.ai/.insteadOf" \
"https://git.flemming.ai/"
- name: Install system dependencies
run: |
apt-get update -qq
apt-get install -y --no-install-recommends \
curl \
ca-certificates \
build-essential \
pkg-config \
libssl-dev \
git \
jq \
openssl
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y \
--profile minimal \
--default-toolchain "$RUST_TOOLCHAIN" \
--target wasm32-wasip2
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Build module (wasm32-wasip2)
run: cargo build --release --target wasm32-wasip2
- name: Install fai CLI
# Pinned to the production channel; signing requires the
# `fai pack` command. Override FAI_VERSION when bundle
# format compatibility matters.
run: |
curl -fsSL https://get.chain.flemming.ai | sh -s -- --no-bootstrap
# The installer drops fai at $HOME/.local/bin
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.local/bin:$PATH"
fai --version
- name: Stage bundle inputs
# The wasm artefact is named after the Cargo package
# (underscored), which may differ from the module name
# in module.yaml (dashed). Resolve it from Cargo.toml so
# the template stays agnostic to the project's naming
# convention.
run: |
set -eu
CARGO_NAME=$(grep -E '^name *= *"' Cargo.toml \
| head -1 | sed 's/.*"\(.*\)".*/\1/')
mkdir -p staging
cp module.yaml staging/
cp "target/wasm32-wasip2/release/${CARGO_NAME}.wasm" staging/module.wasm
if [ -f sbom.cdx.json ]; then cp sbom.cdx.json staging/; fi
if [ -f MODULE.md ]; then cp MODULE.md staging/; fi
if [ -f MODULE.de.md ]; then cp MODULE.de.md staging/; fi
- name: Pack bundle
id: pack
run: |
export PATH="$HOME/.local/bin:$PATH"
BUNDLE="${{ env.MODULE_NAME }}-${{ github.ref_name }}.fai"
fai pack staging --output "$BUNDLE"
echo "bundle=$BUNDLE" >> "$GITHUB_OUTPUT"
ls -la "$BUNDLE"
- name: Sign bundle (Flemming.AI signing key)
env:
FAI_SIGNING_KEY_PEM: ${{ secrets.FAI_SIGNING_KEY }}
run: |
if [ -z "${FAI_SIGNING_KEY_PEM:-}" ]; then
cat >&2 <<'EOF'
FAI_SIGNING_KEY secret is empty or undefined.
This workflow signs module bundles with the Flemming.AI
module-signing key (ECDSA P-256, PKCS#8 PEM, unencrypted).
The matching public key is pinned in the fai-hub binary at
infra/cosign/official.pub — hubs that require_signatures
will reject anything not signed with this key.
To configure:
1. Forgejo -> chain-modules org -> Settings -> Secrets
2. Add secret FAI_SIGNING_KEY (org-level recommended)
3. Paste the unencrypted PEM (BEGIN PRIVATE KEY ...)
The encrypted source-of-truth lives on Stefan's Mac at
~/.fai-secrets/flemming-ai-signing.key (passphrase in
mSecure under "Ch∆In — Module Signing Key (Private)").
See fai/platform infra/cosign/OPERATOR-HANDOFF.md for the
full setup runbook and rotation process.
EOF
exit 1
fi
umask 077
echo "$FAI_SIGNING_KEY_PEM" > /tmp/signing-key.pem
openssl dgst -sha256 \
-sign /tmp/signing-key.pem \
-out "${{ steps.pack.outputs.bundle }}.sig.raw" \
"${{ steps.pack.outputs.bundle }}"
base64 < "${{ steps.pack.outputs.bundle }}.sig.raw" \
> "${{ steps.pack.outputs.bundle }}.sig"
rm "${{ steps.pack.outputs.bundle }}.sig.raw"
shred -u /tmp/signing-key.pem || rm -f /tmp/signing-key.pem
ls -la "${{ steps.pack.outputs.bundle }}.sig"
- name: Round-trip verify
# Sanity-check: the freshly-signed bundle must verify
# against the well-known public key before we publish it.
run: |
curl -fsSL https://git.flemming.ai/fai/platform/raw/branch/main/infra/cosign/official.pub \
-o /tmp/official.pub
openssl dgst -sha256 \
-verify /tmp/official.pub \
-signature <(base64 -d < "${{ steps.pack.outputs.bundle }}.sig") \
"${{ steps.pack.outputs.bundle }}"
- name: Attach bundle + signature to Forgejo Release
run: |
set -eu
BUNDLE="${{ steps.pack.outputs.bundle }}"
SIG="$BUNDLE.sig"
REL_JSON=$(curl -sS \
-H "Authorization: token ${GITHUB_TOKEN}" \
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}")
REL_ID=$(echo "$REL_JSON" | jq -r '.id // empty')
if [ -z "$REL_ID" ] || [ "$REL_ID" = "null" ]; then
REL_ID=$(curl -sS -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/json" \
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases" \
-d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\",\"draft\":false,\"prerelease\":false,\"body\":\"Auto-generated by sign-bundle workflow\"}" \
| jq -r '.id')
fi
upload_one() {
local asset_path="$1" asset
asset=$(basename "$asset_path")
EXISTING=$(curl -sS \
-H "Authorization: token ${GITHUB_TOKEN}" \
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets" \
| jq -r ".[] | select(.name==\"${asset}\") | .id")
if [ -n "$EXISTING" ]; then
curl -sS -X DELETE \
-H "Authorization: token ${GITHUB_TOKEN}" \
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets/${EXISTING}" \
|| true
fi
curl -sS -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-F "attachment=@${asset_path}" \
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets?name=${asset}" \
> /dev/null
echo "uploaded $asset"
}
upload_one "$BUNDLE"
upload_one "$SIG"
echo "release ${GITHUB_REF_NAME} ready: https://git.flemming.ai/${GITHUB_REPOSITORY}/releases/tag/${GITHUB_REF_NAME}"

View file

@ -7,17 +7,17 @@
name = "orchestrator_llm" name = "orchestrator_llm"
version = "0.3.2" version = "0.3.2"
edition = "2024" edition = "2024"
authors = ["Dr. Stefan Flemming <platform@flemming.ai>"] authors = ["Dr. Stefan Flemming <chain@flemming.ai>"]
license = "Apache-2.0" license = "Apache-2.0"
publish = false publish = false
description = "F∆I orchestrator module — turns natural-language goals into structured execution plans" description = "F∆I orchestrator module — turns natural-language goals into structured execution plans"
repository = "https://git.flemming.ws/fai-modules/orchestrator-llm" repository = "https://git.flemming.ws/chain-modules/orchestrator-llm"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
fai-module-sdk = { git = "https://git.flemming.ws/fai/module-sdk.git", branch = "main" } chain-module-sdk = { git = "https://git.flemming.ai/fai/chain-module-sdk-rust.git", branch = "main" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
thiserror = "2" thiserror = "2"

View file

@ -58,7 +58,7 @@ APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright 2026 fai-modules Copyright 2026 Flemming.AI
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

BIN
module.wasm Normal file

Binary file not shown.

View file

@ -1,55 +1,111 @@
schema_version: 1 schema_version: 3
provider: chain
name: orchestrator-llm name: orchestrator-llm
version: 0.3.1 version: 0.3.1
# Capability provided by this module.
provides: provides:
- capability: orchestrator.plan - capability: orchestrator.plan
version: 0.3.0 version: 0.3.0
# Inputs the invoke function accepts. # Capabilities the plans this module emits typically reference.
inputs: # Advisory only — Studio surfaces "you'll also want X" when the
goal: text # operator installs orchestrator-llm; `fai admin doctor` flags
# LLM HTTP endpoint, e.g. "http://localhost:11434/api/chat" # unmet entries; the hub does NOT block install.
# for Ollama, "https://api.openai.com/v1/chat/completions", consumes:
# or "https://api.anthropic.com/v1/messages". - capability: llm.chat
llm_endpoint: text version: "^0"
# Model identifier, e.g. "qwen2.5:14b" for Ollama or "gpt-4o-mini". why: "orchestrator-emitted plans dispatch llm.chat steps for downstream LLM-driven work; a flow that runs the plan unmodified needs an llm.chat provider installed."
llm_model: text
# Optional bearer token for cloud providers. Empty string means inputs:
# no Authorization header is sent. goal:
llm_api_key: text type: text
# Optional JSON-encoded list of installed capabilities. If description:
# omitted or empty, the module plans without capability context. en: Operator-stated goal in natural language — what the resulting flow should accomplish.
available_capabilities: text de: Ziel in natürlicher Sprache — was der erzeugte Flow erreichen soll.
# Optional JSON-encoded list of all known store capabilities llm_endpoint:
# (installed + planned + alpha). Used when the orchestrator type: text
# needs to suggest installations. description:
store_capabilities: text en: |
LLM HTTP endpoint, e.g. "http://localhost:11434/api/chat"
for Ollama, "https://api.openai.com/v1/chat/completions",
or "https://api.anthropic.com/v1/messages".
de: |
LLM-HTTP-Endpunkt, z. B. "http://localhost:11434/api/chat"
für Ollama, "https://api.openai.com/v1/chat/completions"
oder "https://api.anthropic.com/v1/messages".
llm_model:
type: text
description:
en: Model identifier, e.g. "qwen2.5:14b" for Ollama or "gpt-4o-mini".
de: Modell-Identifier, z. B. "qwen2.5:14b" (Ollama) oder "gpt-4o-mini".
llm_api_key:
type: text
description:
en: Optional bearer token for cloud providers. Empty = no Authorization header.
de: Optionaler Bearer-Token für Cloud-Provider. Leer = kein Authorization-Header.
available_capabilities:
type: text
description:
en: |
Optional JSON-encoded list of installed capabilities.
Omit or pass an empty string to plan without capability
context.
de: |
Optional JSON-codierte Liste der installierten
Capabilities. Leer = ohne Capability-Kontext planen.
store_capabilities:
type: text
description:
en: |
Optional JSON-encoded list of all known store
capabilities (installed + planned + alpha). Used when
the orchestrator needs to suggest installations.
de: |
Optional JSON-codierte Liste aller bekannten Store-
Capabilities (installiert + geplant + alpha). Nötig,
wenn der Orchestrator Installationen vorschlagen soll.
# Outputs produced.
outputs: outputs:
# JSON-encoded Plan compatible with `fai_hub::plan::Plan`. plan:
plan: json type: json
# The endpoint the plan was generated against. Empty when the description:
# deterministic stub path (no llm_endpoint) ran. en: JSON-encoded Plan compatible with `fai_hub::plan::Plan`.
model_endpoint: text de: JSON-codierter Plan, kompatibel mit `fai_hub::plan::Plan`.
# The model name as supplied to the LLM API. model_endpoint:
model_name: text type: text
# SHA-256 digest of the served Ollama model, when reachable. description:
# Empty for cloud providers (OpenAI / Anthropic do not expose en: |
# a digest API) and for the deterministic stub path. Together The endpoint the plan was generated against. Empty when
# with model_endpoint and model_name this answers the audit the deterministic stub path (no llm_endpoint) ran.
# question "which exact model produced this plan?" de: |
model_digest: text Endpunkt, gegen den der Plan erzeugt wurde. Leer beim
deterministischen Stub-Pfad (ohne llm_endpoint).
model_name:
type: text
description:
en: The model name as supplied to the LLM API.
de: An die LLM-API übergebener Modellname.
model_digest:
type: text
description:
en: |
SHA-256 digest of the served Ollama model, when
reachable. Empty for cloud providers (OpenAI / Anthropic
do not expose a digest API) and for the deterministic
stub path. Together with model_endpoint and model_name
this answers the audit question "which exact model
produced this plan?"
de: |
SHA-256-Digest des bedienten Ollama-Modells, wenn
erreichbar. Leer für Cloud-Provider (OpenAI / Anthropic
bieten keine Digest-API) und für den deterministischen
Stub-Pfad. Zusammen mit model_endpoint + model_name
beantwortet das die Audit-Frage "welches genaue Modell
hat diesen Plan erzeugt?"
# Permissions required. # Permissions required.
# #
# The module makes outbound HTTP to the configured LLM endpoint. # The module makes outbound HTTP to the configured LLM endpoint.
# Default declarations cover the common providers (cloud OpenAI,
# Anthropic, local Ollama on loopback). Operators with different
# LLM endpoints add a host-level entry via operator config
# (Phase 1+) — for Phase 0.5 they fork module.yaml.
permissions: permissions:
- "net: api.openai.com" - "net: api.openai.com"
- "net: api.anthropic.com" - "net: api.anthropic.com"

View file

@ -1,7 +1,7 @@
//! orchestrator-llm — turns natural-language goals into Plan JSON. //! orchestrator-llm — turns natural-language goals into Plan JSON.
//! //!
//! v0.3.0 migrates from a hand-written wit_bindgen scaffold to the //! v0.3.0 migrates from a hand-written wit_bindgen scaffold to the
//! `#[fai_module]` macro from `fai-module-sdk`. The behavior is //! `#[fai_module]` macro from `chain-module-sdk`. The behavior is
//! unchanged from v0.2.1: a configured Ollama-compatible endpoint //! unchanged from v0.2.1: a configured Ollama-compatible endpoint
//! drives a real plan when `llm_endpoint` is set, otherwise the //! drives a real plan when `llm_endpoint` is set, otherwise the
//! deterministic stub from v0.1.0 takes over. //! deterministic stub from v0.1.0 takes over.
@ -19,7 +19,7 @@ mod plan;
pub use plan::{Plan, PlanStep, build_stub_plan, parse_and_validate, validate}; pub use plan::{Plan, PlanStep, build_stub_plan, parse_and_validate, validate};
use fai_module_sdk::prelude::*; use chain_module_sdk::prelude::*;
#[fai_module] #[fai_module]
pub fn invoke(_ctx: Context, inputs: Inputs) -> Result<Outputs, ModuleError> { pub fn invoke(_ctx: Context, inputs: Inputs) -> Result<Outputs, ModuleError> {