Using unhardcoded-engine (Σ_pol)
The engine is a pure-Lua policy algebra: you hand it a contract (what a
call needs) and a policy (how to choose), it returns which (provider, model)
to call, how to mutate the request, and what to do on failure. It performs no
I/O and holds no credentials — the host does all of that. Two ways to "use" it:
embed the core, or author a policy term. Most of what you need is here; the
normative detail lives in docs/ (linked inline).
A. Embedding the core in a host
The package is llm_policy. router.lua is a compat shim (require("llm_policy")).
local llm_policy = require("llm_policy")
llm_policy.init(config, metrics) -- validate catalog + profiles, seed metrics
local result = llm_policy.execute({ prompt = "...", profile = "default" })
if result.ok then print(result.response.text) else print(result.error) end
The host contract. Install a global host table before execute; the core
delegates every side effect to it:
| Field |
Contract |
call_provider(request) |
do the HTTP → {ok, response, error_kind, http_status, error_message} |
now_ms() |
millisecond clock |
log(level, event, fields) |
logging sink |
env(key) |
env-var resolver (auth lives here, never in the core) |
sleep_ms, discover, persist_state, load_state |
optional |
The core decides which provider, when to retry, when to abort; the host
executes the call and resolves auth from the provider's declared
auth = {kind="none"|"bearer"|"oauth"}. The minimal working reference is
example_host/example.py (~96 lines, lupa + a mock provider) — read it before
writing your own. mlua (Rust/GenVM) and luerl (Elixir) embed the same way.
Public surface (all on the llm_policy module):
init(config, metrics?) — admit config, build the candidate matrix.
execute(contract) — synchronous loop to completion.
execute_step(state, contract?, response?) — cooperative async; yields on I/O
so one process overlaps many in-flight calls (first call: state=nil).
rank(contract) → ordered, err, rejected — dry-run ranking, no HTTP.
info(), provider_status(now_ms) — read-only introspection / health.
dump_state() / restore_state(snap), update_metrics(...),
invalidate_discovery(...) — runtime state (breakers, EMA, credits).
M.ir — offline term tooling: ir.term.check/normalize/encode, ir.compile.
M.dsl — the closure verbs (filter/rank/mutate/sequence/policy), a
local-only escape hatch; anything shipped or sent per-call should be IR.
Config/metrics schema is illustrated by config.example.lua /
metrics.example.lua; the design (candidate object, the three price-ingestion
moments, field mutability) is docs/POLICY_DESIGN.md.
B. Authoring a Σ_pol policy term
A policy is data — a plain array over the closed, versioned signature
sigma-pol/v2, admitted before it runs (depth ≤ 64, ≤ 4096 nodes). Five slots
(the Evidence slot was removed in v2); fill the leading three — Pred, Scorer,
Selector — and keep the tail (Xform, FailPlan) as defaults:
["policy", <Pred>, <Scorer>, <Selector>, <Xform>, <FailPlan>]
- Pred — who is eligible:
and/or/not, meets_req, cmp(field, rel, num),
is(boolfield), min_tier/tier_eq, family_eq/provider_eq/served_by_eq,
has_cap.
- Scorer — rank survivors (population-relative):
field(f), lit, scale,
add, neg, normalize, clamp, gate(pred, scorer).
- Selector — pick:
argmax, ordered, sample(temp), top_k, chain,
or prefer(pred, inner) for strict lexicographic priority while retaining
the inner order within each group.
- Xform (default
["id"]) — mutate the outgoing request per attempt.
- FailPlan (default
["always", {"action":"next_candidate"}]) — error_kind → Action.
Observation fields a policy may reference (declared vocabulary, not ops):
price_in, price_out, latency_ms, tok_s, success_rate, credits,
context, has_tee, no_log, breaker_open, disabled; plus categoricals
provider_id, model_family, scope, tier. Hosts add more via
config.fields. The normative operator list, numeric model, encoding and
hashing rules are docs/SIGMA-POL.md — treat it as the source of truth.
Send a per-call policy as policy_ir in the contract; dry-run it with
rank(...) or admit/fingerprint it offline with ir.term.check /
ir.term.encode before spending a token.
Non-obvious gotchas (the ones that bite)
price_in/price_out/latency_ms default to +inf. A candidate with no
observed price fails a ceiling like cmp("price_out","le",25) — this is
deliberate (strict), not a bug. Seed prices via metrics or the catalog.
- v2 removed the composite scorer atoms (
quality/speed/cost/partner/
free_credit) and the Evidence slot — they folded raw fields into opaque
numbers. Score raw fields instead: field("price_out") composed with
scale/add/normalize/neg. Old policies using them are rejected.
- Determinism is the defining invariant. The same
(policy, catalog, ctx, seed) selects identically under mlua, lupa and luerl — bit-for-bit (IEEE ops
only, no transcendentals). If you need reproducible choice, pass seed.
- The host envelope only narrows.
config.policy_envelope is ∧-composed
onto every caller policy, so a per-call term can only tighten the host's
invariants, never widen them.
- Identity is the fingerprint. Every IR run stamps
trace.policy_fingerprint (sha256 of the canonical encoding) — use it for
caching, audit and on-chain commitment, not the raw term text.
Reading the result
execute returns { ok, response|error, chosen, trace }. When debugging a
choice, read trace: ranked (survivors with scores/tier), rejected (each
dropped candidate + reason — this is where a +inf price or a failed meets_req
shows up), decision_path (attempts/skips with latency and error_kind), and
chosen (provider_id, model_family, served_model_id, prices, served_by).
Where to read more
docs/SIGMA-POL.md — normative IR: terms, operators, numeric model, encoding, hashing.
docs/POLICY_DESIGN.md — the candidate object and the four verbs; sync vs async execution.
docs/SIGMA-FLOW.md — Σ_flow: composing several calls as a DAG (flow_ir).
docs/GENVM-LLM-POLICY.md — the on-chain (greyboxing) embedding.
example_host/ — the minimal embedding reference.
- Production off-chain host (HTTP, providers, auth, dashboard):
genlayerlabs/unhardcoded.
1---2name: unhardcoded-engine-use3description: Use the unhardcoded-engine (Σ_pol) policy algebra — either to EMBED the pure Lua core in a host, or to AUTHOR a policy_ir term that decides which (provider, model) serves an LLM call. Load this skill when you need to integrate llm_policy into a host (lupa/mlua/luerl), write or dry-run a Σ_pol policy, understand the request/trace contract, or debug why a candidate was rejected. For extending the algebra itself (new operators/fields) use unhardcoded-engine-contribute instead.4---56# Using unhardcoded-engine (Σ_pol)78The engine is a **pure-Lua policy algebra**: you hand it a *contract* (what a9call needs) and a *policy* (how to choose), it returns which `(provider, model)`10to call, how to mutate the request, and what to do on failure. **It performs no11I/O and holds no credentials** — the host does all of that. Two ways to "use" it:12embed the core, or author a policy term. Most of what you need is here; the13normative detail lives in `docs/` (linked inline).1415## A. Embedding the core in a host1617The package is `llm_policy`. `router.lua` is a compat shim (`require("llm_policy")`).1819```lua20local llm_policy = require("llm_policy")21llm_policy.init(config, metrics) -- validate catalog + profiles, seed metrics22local result = llm_policy.execute({ prompt = "...", profile = "default" })23if result.ok then print(result.response.text) else print(result.error) end24```2526**The host contract.** Install a global `host` table before `execute`; the core27delegates every side effect to it:2829| Field | Contract |30|---|---|31| `call_provider(request)` | do the HTTP → `{ok, response, error_kind, http_status, error_message}` |32| `now_ms()` | millisecond clock |33| `log(level, event, fields)` | logging sink |34| `env(key)` | env-var resolver (auth lives here, never in the core) |35| `sleep_ms`, `discover`, `persist_state`, `load_state` | optional |3637The core decides *which provider, when to retry, when to abort*; the host38*executes* the call and resolves auth from the provider's declared39`auth = {kind="none"|"bearer"|"oauth"}`. The minimal working reference is40`example_host/example.py` (~96 lines, lupa + a mock provider) — read it before41writing your own. mlua (Rust/GenVM) and luerl (Elixir) embed the same way.4243**Public surface** (all on the `llm_policy` module):44- `init(config, metrics?)` — admit config, build the candidate matrix.45- `execute(contract)` — synchronous loop to completion.46- `execute_step(state, contract?, response?)` — cooperative async; yields on I/O47 so one process overlaps many in-flight calls (first call: `state=nil`).48- `rank(contract)` → `ordered, err, rejected` — **dry-run ranking, no HTTP**.49- `info()`, `provider_status(now_ms)` — read-only introspection / health.50- `dump_state()` / `restore_state(snap)`, `update_metrics(...)`,51 `invalidate_discovery(...)` — runtime state (breakers, EMA, credits).52- `M.ir` — offline term tooling: `ir.term.check/normalize/encode`, `ir.compile`.53- `M.dsl` — the closure verbs (filter/rank/mutate/sequence/policy), a54 **local-only escape hatch**; anything shipped or sent per-call should be IR.5556Config/metrics schema is illustrated by `config.example.lua` /57`metrics.example.lua`; the design (candidate object, the three price-ingestion58moments, field mutability) is `docs/POLICY_DESIGN.md`.5960## B. Authoring a Σ_pol policy term6162A policy is **data** — a plain array over the closed, versioned signature63`sigma-pol/v2`, admitted before it runs (depth ≤ 64, ≤ 4096 nodes). Five slots64(the Evidence slot was removed in v2); fill the leading three — Pred, Scorer,65Selector — and keep the tail (Xform, FailPlan) as defaults:6667```68["policy", <Pred>, <Scorer>, <Selector>, <Xform>, <FailPlan>]69```7071- **Pred** — who is eligible: `and/or/not`, `meets_req`, `cmp(field, rel, num)`,72 `is(boolfield)`, `min_tier`/`tier_eq`, `family_eq`/`provider_eq`/`served_by_eq`,73 `has_cap`.74- **Scorer** — rank survivors (population-relative): `field(f)`, `lit`, `scale`,75 `add`, `neg`, `normalize`, `clamp`, `gate(pred, scorer)`.76- **Selector** — pick: `argmax`, `ordered`, `sample(temp)`, `top_k`, `chain`,77 or `prefer(pred, inner)` for strict lexicographic priority while retaining78 the inner order within each group.79- **Xform** (default `["id"]`) — mutate the outgoing request per attempt.80- **FailPlan** (default `["always", {"action":"next_candidate"}]`) — error_kind → Action.8182**Observation fields** a policy may reference (declared vocabulary, not ops):83`price_in`, `price_out`, `latency_ms`, `tok_s`, `success_rate`, `credits`,84`context`, `has_tee`, `no_log`, `breaker_open`, `disabled`; plus categoricals85`provider_id`, `model_family`, `scope`, `tier`. Hosts add more via86`config.fields`. The **normative** operator list, numeric model, encoding and87hashing rules are `docs/SIGMA-POL.md` — treat it as the source of truth.8889Send a per-call policy as `policy_ir` in the contract; dry-run it with90`rank(...)` or admit/fingerprint it offline with `ir.term.check` /91`ir.term.encode` before spending a token.9293## Non-obvious gotchas (the ones that bite)9495- **`price_in`/`price_out`/`latency_ms` default to `+inf`.** A candidate with no96 observed price *fails* a ceiling like `cmp("price_out","le",25)` — this is97 deliberate (strict), not a bug. Seed prices via `metrics` or the catalog.98- **v2 removed the composite scorer atoms** (`quality`/`speed`/`cost`/`partner`/99 `free_credit`) and the Evidence slot — they folded raw fields into opaque100 numbers. **Score raw fields instead**: `field("price_out")` composed with101 `scale`/`add`/`normalize`/`neg`. Old policies using them are rejected.102- **Determinism is the defining invariant.** The same `(policy, catalog, ctx,103 seed)` selects identically under mlua, lupa and luerl — bit-for-bit (IEEE ops104 only, no transcendentals). If you need reproducible choice, pass `seed`.105- **The host envelope only narrows.** `config.policy_envelope` is `∧`-composed106 onto every caller policy, so a per-call term can only *tighten* the host's107 invariants, never widen them.108- **Identity is the fingerprint.** Every IR run stamps109 `trace.policy_fingerprint` (sha256 of the canonical encoding) — use it for110 caching, audit and on-chain commitment, not the raw term text.111112## Reading the result113114`execute` returns `{ ok, response|error, chosen, trace }`. When debugging a115choice, read `trace`: `ranked` (survivors with scores/tier), `rejected` (each116dropped candidate + reason — this is where a `+inf` price or a failed `meets_req`117shows up), `decision_path` (attempts/skips with latency and error_kind), and118`chosen` (`provider_id`, `model_family`, `served_model_id`, prices, `served_by`).119120## Where to read more121122- `docs/SIGMA-POL.md` — normative IR: terms, operators, numeric model, encoding, hashing.123- `docs/POLICY_DESIGN.md` — the candidate object and the four verbs; sync vs async execution.124- `docs/SIGMA-FLOW.md` — Σ_flow: composing several calls as a DAG (`flow_ir`).125- `docs/GENVM-LLM-POLICY.md` — the on-chain (greyboxing) embedding.126- `example_host/` — the minimal embedding reference.127- Production off-chain host (HTTP, providers, auth, dashboard): `genlayerlabs/unhardcoded`.