ElementsAI

Overview

Machine intelligence — bind models, ask versioned prompts, run bounded agents, embed into store.index, and connect MCP.

AI is how your backend calls a model as a declared effect. Ticket triage, a weekly summary, a planner that tools your own Flows — each ask is a named prompt or agent with budgets, not an SDK call buried in do.

For developers wiring OpenRouter or any OpenAI-compatible /v1 endpoint — bind a model, mint a prompt, ask through fx.

The one rule

Declare ai.modelmodel.prompt (or ai.agent / ai.embed), then call through fx.ask / fx.run / fx.embed. There is no top-level ai.prompt. Budgets and maxSteps are contracts; PII to a third-party model needs explicit allowPii.

Four blocks, one element

ai.model · prompt · embed · agent
  • model
    bind

    Logical binding

    ai.model("smart", { provider, model })

    Name → provider / wire model — prod must declare the driver

    smart / fast / local — swap per environment

  • prompt
    ask

    Versioned artifact

    smart.prompt("ticket-triage", { out, version })

    fx.ask validates the response against `out`

    Typed triage, summaries, structured answers

  • embed
    into

    Embedding pipeline

    ai.embed("kb", { into: index })

    Vectors land in store.index — searched via fx.search

    Knowledge base, semantic recall

  • agent
    step 1/6

    Bounded agent

    ai.agent("support", { tools, maxSteps })

    Tools are flows — each step goes through fx.call

    Support cases, multi-step resolve with budgets

Smallest Example

Bind a model and mint a prompt

src/core/ai.ts
import { ai } from "okengine";
import { z } from "zod";

export const smart = ai.model("smart", {
  provider: "openrouter",
  model: "openrouter/free",
  apiKey: process.env.OPENROUTER_API_KEY,
});

export const triage = smart.prompt("ticket-triage", {
  version: 1,
  budget: { maxCostPerCall: 0.02 },
  out: z.object({
    category: z.enum(["billing", "technical", "other"]),
    urgency: z.enum(["low", "medium", "high"]),
  }),
});

Import this module before oke() so registries adopt the decls.

Ask from a Flow

src/flows/support/triage.ts
import { on, flow, http } from "okengine";
import { z } from "zod";
import { triage } from "@/core/ai";

export const classify = on(
  http.post({
    in: z.object({ message: z.string().min(1) }),
  }),
  flow({
    do: async ({ message }, fx) => {
      return await fx.ask(triage, { message });
    },
  }),
);

The compiler stamps asks: ["ticket-triage"] on the Flow’s effects.

Call the endpoint

curl -X POST http://localhost:6530/support/triage \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{"message":"Invoice double-charged last week"}'

With out set, the model must return JSON matching the schema or the ask throws AiSchemaValidationError.

Progressive Patterns

From a bare ask to fallback models, tools on ask, and a bounded agent:

Prompt handle + input — model comes from the parent ai.model:

const result = await fx.ask(triage, { message: "Cannot reset password" });

Declaration Reference

DeclarationSignaturePurpose
ai.modelai.model(name, options?)Logical binding (provider / wire model / key)
model.promptmodel.prompt(name, options?)Versioned prompt on that binding
ai.agentai.agent(name, options?)Bounded tool-calling agent
ai.embedai.embed(name, options?)Embedding pipeline into store.index
ai.mcpServerai.mcpServer(name, options)Outbound MCP client (allowlisted tools)

There is no ai.prompt(...) — mint prompts only via model.prompt.

fx surface

MethodCapabilityMeaning
fx.ask(prompt, input?, opts?)asksComplete a versioned prompt (optional tool loop)
fx.run(agent, input?)asks (agent name)Run a declared agent
fx.stream(model, opts?)asksToken stream from a model binding
fx.embed(model, text)embedsReturn a vector (does not write an index)
fx.search(embed, query, opts?)readsSimilarity search over an embed / index

fx.ask options

OptionTypeDefaultMeaning
viarefsprompt via / [model]Recovery chain for this call
timeout"30s" | msprompt timeoutPer-call deadline
toolsFlow refsOffered as tools via fx.call
maxStepsnumber6Cap on tool rounds

Undeclared ask → OKE1005 UNDECLARED_ASK. Undeclared embed → OKE1009.

Guardrails

Budgets, version pins, step limits, and PII egress are contracts — not guidelines:

Guardrails — contracts, not guidelines

fx.ask(prompt, input)
scenarioclean ask
  1. 1versioned promptsmart.prompt("ticket-triage", { version: 1, out })
  2. 2PII build gatethird-party + .pii() field → needs allowPii
  3. 3steps · budgetmaxSteps: 6 · budget.maxCostPerRun
  4. 4prod driverdrivers.ai.prod must be named — no default
  5. Checking guardrails…

PII egress

Sending a .pii() field to a third-party provider fails the build unless the Flow declares allowPii: true (or pii: "allow"). mock, local, and openai-compatible are not third-party egress.

PII — same field, opposite egress

fx.ask · .pii() · email
ask
check
verdict
shared beat — third-party vs local
  • anthropic

    Third-party — build fails

    Cloud provider counts as egress. Without allowPii the build stops before deploy.

    AiPiiBuildError · field(s) [email]
  • openai-compatible

    On-premise — ask proceeds

    mock, local, and openai-compatible are not third-party egress.

    ask proceeds · no AiPiiBuildError

Checking egress…

flow({
  allowPii: true,
  do: async (input, fx) => fx.ask(triage, { email: input.email }),
});

Per-environment drivers

AiDriverId: mock · anthropic · openai-compatible · bedrock · vertex.

Boot implements mock, anthropic, and openai-compatible. bedrock / vertex are reserved and throw until implemented. Dev/test fall back to mock when unset — prod must declare drivers.ai.

oke.config.ts
import { defineConfig } from "okengine/config";

export default defineConfig({
  drivers: {
    ai: {
      dev: "openai-compatible",
      test: "mock",
      prod: "openai-compatible",
    },
  },
});
DriverRuns asBest for
mockIn-process stubTests, CI, no network
openai-compatibleOpenAI HTTP APIOpenRouter, cloud registries, any /v1 server
anthropicNative Messages APIProduction Claude + reliable tools

Env knobs: OKE_AI_DRIVER, OKE_AI_URL / OPENAI_BASE_URL, provider API keys. Compose does not pin inference — BYO URL + keys, or OpenRouter.

The Capabilities of AI

Troubleshooting

Learn more

  • Models — provider registry and baseUrl rules
  • Promptsvia, budgets, versions, evals
  • Agentsfx.run, tools, maxSteps
  • MCP — inbound mcp.tool and outbound ai.mcpServer
  • OpenRouter — zero-Docker cloud path
  • fxfx.ask / fx.run / fx.embed
  • Errors — OKE1005 · OKE1009

Next

On this page