ElementsAI

Agents

Bounded agents — tools are Flows, maxSteps and budget.maxCostPerRun cap the loop, invoke with fx.run.

An agent (ai.agent) is a bounded tool-calling loop. Tools are your app’s Flows (and allowlisted MCP refs) — each step goes through fx.call. The loop stops at maxSteps or budget.maxCostPerRun.

For developers who need a planner or support assistant without writing a custom tool runtime — declare the bag, run with fx.run.

The one rule

Pass Flow names (or handles) in tools, set maxSteps and budget.maxCostPerRun, then fx.run(agent, {message}). There is no instructions / in / out on ai.agent — shape the user message yourself.

Smallest Example

Declare model + agent

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

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

export const supportAgent = ai.agent("support.assistant", {
  model: "smart",
  tools: ["docs.search", "tickets.create"],
  maxSteps: 5,
  budget: { maxCostPerRun: 0.1 },
});

Run from a Flow

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

export const assist = on(
  http
    .post({
      in: z.object({ query: z.string().min(1) }),
    })
    .gate(member),
  flow({
    do: async ({ query }, fx) => {
      return await fx.run(supportAgent, {
        message: `Help the member: ${query}`,
      });
    },
  }),
);

Runtime records the agent name under asks. Prefer declaring effects: { asks: ["support.assistant"] } when inference does not see fx.run.

Bound termination

Default maxSteps is 6 when omitted. Hitting the step cap ends the loop; exceeding budget.maxCostPerRun throws AiBudgetExceededError: ai: agent "…" exceeded maxCostPerRun N.

Progressive Patterns

String names or Flow handles — same capability path as fx.call:

import { searchDocs } from "@/flows/docs/search";
import { createTicket } from "@/flows/tickets/create";

export const supportAgent = ai.agent("support.assistant", {
  model: smart,
  tools: [searchDocs, createTicket],
  maxSteps: 8,
});

Options

OptionTypeDefaultMeaning
modelAiModelDecl | stringfirst modelLogical binding for the loop
toolsFlow / MCP refs[]Callable via fx.call
maxStepsnumber6Hard cap on tool rounds
budget{ maxCostPerCall?, maxCostPerRun? }Cost contracts for the run

fx.run input

Pass a string or { message: string } (and any extra fields your tools need in context). The agent declaration does not take Zod in / out — validate on the surrounding Flow.

await fx.run(supportAgent, "Summarize open tickets");
await fx.run(supportAgent, { message: "Summarize open tickets" });

Agents vs ask-with-tools

SurfaceWhen
ai.agent + fx.runReusable tool bag, shared step/budget policy
fx.ask(prompt, …, { tools })One prompt, occasional tools

Both default maxSteps to 6. Denied or unknown tools surface as ai: all tool calls denied for prompt "…" / ai: model requested unknown tool "…".

Troubleshooting

Learn more

  • Promptsfx.ask and ask-time tools
  • MCP — inbound tools and outbound servers
  • Flow — tools are ordinary Flows
  • AI — guardrails figure and drivers

Next

On this page