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.
A trigger
Section titled “A trigger”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.setandprevious: 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.
A trigger on a topic
Section titled “A trigger on a topic”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 } });opf fn.put name=ads kind=trigger topic=ads.set_budget code=@ads.jsopf msg.send topic=ads.set_budget payload:='{"daily_budget":20}'opf msg.pull topics=ads.budget_set,ads.rejected wait_seconds=30Messages 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.
What code can use
Section titled “What code can use”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 |
Judgements with AI
Section titled “Judgements with AI”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 interviewsconst 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.
Guard rails
Section titled “Guard rails”- 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.putandfn.deletedefault toask, 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 |
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 fileopf fn.run name=nightlyopf fn.runs name=nightly limit=5