Skip to content

Functions

A function is a piece of JavaScript that the hub runs for you:

  • A trigger runs when rows in a collection are added, updated or deleted, or when a message is sent to a topic.
  • A job runs on a schedule (cron, UTC).
  • Either kind can also be run by hand in the UI, or by an agent with fn.run.

Functions call the same tools agents use, as their own actor fn:<name>. So trust levels apply, every change appears in the feed, and actions set to ask wait in your inbox.

Tell billing when an order is done:

// event: {type: "add"|"update"|"delete", collection, id, row, set, previous, actor, seq}
if (event.row.status !== "done") return "skipped";
aw.msg.send({ to: "billing", type: "order.done", payload: { id: event.id, item: event.row.item } });
log("told billing about", event.row.item);
return "sent";

What a trigger receives in event:

  • row: the current row, including computed columns.
  • set and previous: for updates, the changed fields and their old values.
  • previous: for deletes, the deleted row.

A trigger never fires on its own changes, so the function can safely update the row it reacts to.

With topic instead of collection, a trigger runs for every message sent to that topic with msg.send. ads.* matches every topic starting with ads.. This turns a function into a small service that agents talk to by message:

// event: {type: "message", topic, id, from, to, message_type, payload, correlation_id, seq}
const eur = Number(event.payload.daily_budget);
if (eur > 50) {
aw.msg.send({ topic: "ads.rejected", to: event.from, correlation_id: event.id, payload: { reason: "over 50 €/day" } });
return "rejected";
}
aw.kv.set({ key: "ads/daily_budget", value: eur });
aw.msg.send({ topic: "ads.budget_set", to: event.from, correlation_id: event.id, payload: { daily_budget: eur } });
Terminal window
opf fn.put name=ads kind=trigger topic=ads.set_budget code=@ads.js
opf msg.send topic=ads.set_budget payload:='{"daily_budget":20}'
opf msg.pull topics=ads.budget_set,ads.rejected wait_seconds=30

Messages the function sends itself don’t trigger it again. The message stays deliverable to agents pulling the topic as usual. The Sim-Firma example simulates a small company this way: agents set the ad budget, change the website or the price by message, and a job computes each day.

Warn when work piles up, every morning at 07:00 UTC:

const open = aw.tasks.list({ status: "open" });
if (open.tasks.length > 10) aw.inbox.notify({ title: open.tasks.length + " tasks are waiting" });
return open.tasks.length;

Schedules are standard cron in UTC (0 7 * * *, */15 * * * *), or descriptors like @hourly, @daily and @every 10m.

aw.<module>.<verb>({…}) Call any tool, e.g. aw.coll.update, aw.msg.send, aw.files.put. Throws on errors such as conflict or not_found
awCall("tool.name", {…}) The same, by name
event Triggers: the change or message that fired it. Manual runs of a trigger: the given input, to test it with a sample event
input The input of a manual run
log(…), console.log(…) Write to the run log
return … The run’s result, shown in the run log
round(x, n), days(from, to), today(), rate_ci(k, n), count_ci(k) Same helpers as in computed columns, including 95 % intervals for shares and counts

When the hub has a language model configured (the same OPF_LLM_* settings as the assistant), functions can ask it for a judgement about text: pick a category, pull out a few fields, or write a short summary.

// Trigger on a collection of customer interviews
const x = ai.extract("Assess this statement from a customer interview.", event.row.quote, {
topic: ["time spent", "errors", "price", "privacy", "integration", "other"],
urgent: "boolean",
key_point: "string",
});
aw.coll.update({ collection: "interviews", id: event.id, set: { topic_ai: x.topic, urgent_ai: x.urgent } });
Call Returns
ai.choose(question, text, options) Exactly one of options, or null
ai.extract(instruction, text, fields) An object with exactly these fields. A field is "string", "number", "boolean" or a list of allowed values; anything that doesn’t fit becomes null (and is noted in the run log)
ai.json(instruction, text, schema) Any JSON value (lists, nested objects), like aiJSON in agentloop. schema is a JSON schema or an example. The shape is not checked, so check what you use; prefer ai.extract where a flat object will do
ai.text(instruction, text, maxWords) A short text, default 80 words

The model judges, the code acts. The model never calls tools: what happens with its answer (which column, which queue, whether to ask a person) is decided by your code. That also protects against instructions hidden in customer text, which is passed to the model as delimited data.

  • Numbers don’t come from the model. Let it suggest a category or a yes/no, store that in its own column (e.g. urgent_ai), and count with code. Keep it apart from what people entered.
  • Limits: 5 AI calls per run and 300 per function and day. Waiting for the model doesn’t count against the 10-second limit.
  • Repeatable: the model runs at temperature 0, and the same question within a while is answered from a cache instead of asking again.
  • Visible: each call is in the run log (model, tokens, answer) and in the feed as fn.ai, so its cost can be counted.
  • Without a model the calls fail with a clear error.
  • Isolated: each run gets a fresh JavaScript interpreter with no file system, network or shell, only aw.*.
  • Limits: 10 seconds and 100 tool calls per run. Tools that wait (events.wait, inbox.wait, kv.watch) aren’t available.
  • Loops: a function that runs more than 120 times in a minute (for example two triggers updating each other) is paused automatically, with a note saying why.
  • One at a time: runs execute one after another, so triggers and jobs never race each other.
  • New code needs approval: for agents, fn.put and fn.delete default to ask, so new or changed code waits for your approval. Your own edits in the UI apply directly.
  • Run log: every run records its cause, duration, log output, result or error, kept for 14 days. Failed runs also appear in the feed as fn.failed.
Tool Use
fn.list / fn.get Functions and their code
fn.put Create or update (kind, collection + on or topic for triggers, schedule for jobs, code)
fn.run Run now, with optional input; returns result and log
fn.runs Recent runs with logs
fn.status active or paused
fn.delete Remove a function
Terminal window
opf fn.put name=nightly kind=job schedule="0 2 * * *" code="return aw.coll.query({collection: 'orders'}).total"
opf fn.put name=nightly kind=job schedule="0 2 * * *" code=@nightly.js # code from a file
opf fn.run name=nightly
opf fn.runs name=nightly limit=5