Cerb automations (KATA)
SkillDev toolsWriting Cerb automations in KATA -- naming, command shape, the correctness rules that bite, and least-privilege policies. Load it before writing or editing any automation script or policy.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Cerb automations (KATA) skill
What this skill tells your AI
The instructions your AI receives, as published by jstanden/cerb in features/cerberusweb.core/assets/agent_filesystems/cerb-agents/skills/automations/SKILL.md and read by ahel’s review.
An automation is a KATA script bound to a trigger, plus a policy that grants the script its privileges. The two move together: a script that gains a privileged command stops working until the policy allows it.
Knowledge — use the mounted documentation, don't guess
cerb-docs contains the Cerb documentation for KATA syntax and annotations, automation commands, triggers, events, policies, record types, search queries, toolbars, workflows, and guides. Consult it before writing anything you're unsure of — especially @cerb-docs/references/docs/automations.md, @cerb-docs/references/docs/kata.md, and @cerb-docs/references/docs/records/types.md.
Search cerb-docs for the exact command, trigger, event, field, or filter you need rather than inventing syntax. Cite what you relied on when it clarifies your change.
Read a command's own page before you write that command -- every time, including commands you are confident you already know. Recall is reliable about which command to reach for and unreliable about what to type inside it: parameter names, which inputs are required, what the output dictionary contains. A wrong input name is accepted when the automation is saved and fails when it runs, so there is nothing to catch it in between. One read is cheaper than that.
Naming automations
- An automation's
nameis an IDENTIFIER, not a title. It's how the automation is addressed everywhere else —uri: cerb:automation:example.math.sumin function:, toolbars, timers, and data queries. Human-friendly wording belongs indescription:, not in the name. - Allowed characters: lowercase letters (a-z), numbers (0-9), underscores (
_), and dots (.) — the same set Cerb requires for plugin and extension IDs (@cerb-docs/references/docs/plugins.md). No spaces, dashes, or slashes. - Dots delimit a namespace hierarchy, broadest to most specific. The conventional shape is
<namespace>.<area>.<thing>-- e.g.example.math.sum,example.translate.function,example.automationTimer.createDailyTasks,wgm.example.openai. - The FIRST segment is a namespace unique to the author/org — conventionally based on a domain you own (wgm, acme) so names stay globally unique. Reserve
cerb.for Cerb itself andexample.for documentation samples. - Middle segments group related automations: by trigger (automationTimer, interactionWorker), by feature area (mail, billing), or by calling subsystem. Keep grouping consistent — prefix search (
name:example.mail.*) is how people find them in worklists. - The LAST segment names the specific behavior; camelCase is conventional there (createDailyTasks, openai). Prefer a verb phrase for automations that act (sendWelcomeEmail) and a noun for ones that fetch (openTicketCount).
- Match the namespace and style already used in this install rather than introducing a new scheme; name a new function in the same namespace as its caller. Renaming breaks every
cerb:automation:URI pointing at it, so call out affected call sites when you propose a rename. - Don't confuse this with command aliases inside a script (
set/a:,await/step:), which allow only letters, numbers, and underscores — no dots.
Automation correctness rules (common pitfalls — honor them)
These are the automation-specific ones. The language itself — indentation, quoting, comments, annotations, unique sibling keys — is the kata skill, and its indentation rule bites here constantly: a bare key: with indented lines under it is an OBJECT, so any multi-line or prose value needs @text: on its key.
- Every automation starts at start:. The working dictionary begins as a copy of inputs and is mutated as it runs.
- await: resumes AFTER the await, not from start:. Commands above the await do not re-run (their results persist in the continuation). A per-item/paging UI must put the await INSIDE a while: loop, or the next resume falls off the end and returns nothing. Build source lists before the loop.
- decision:/outcome: use first-match semantics; an outcome without if@bool: is the default. Write conditions as if@bool: {{...}}
- repeat: requires
each:to resolve to an ARRAY, and a bareeach: {{...}}does not -- a placeholder renders to TEXT. Annotate the key:each@key: results.datawhen the array is already in a placeholder (@keyreads the dictionary value and keeps its type, so there's no round-trip through text), otherwise@csv/@json/@list, or native KATA children. A non-array -- or absent --each:is NOT an error:repeat:runs ZERO iterations and moves on, so the symptom is a loop body that silently never executes. - while: takes
if:, and the same bare-key-renders-TEXT problem inverts here. Any text that isn'tfalse/0/no/n/off/empty is TRUE, soif:fed a rendered string is true forever and the loop only stops at the policy time limit.if:andif@bool:are equivalent (both end at the same toBool), so what matters is that the expression renders to1/empty -- write a comparison, not a value. Unlikerepeat:, a missingif:IS a hard error. - Never stack @raw inside an already-raw block —
script@raw:,event_kata@raw:, andpolicy_kata@raw:are where this comes up. Use the normal annotations inside them (if@bool:,set:);if@raw,bool: {{...}}makes the condition never match. - For Twig arrays in set:, match the annotation to the rendered text: pipe through
|json_encodefor @json; prefer native KATA (0:, 1:, …) for static arrays of objects. - Choose the right output field for the trigger's return keys, and match inputs/ placeholders to the trigger (e.g. interaction.worker exposes worker_* directly).
- icon: values must come from the Cerb icon set — invented names silently render nothing. When unsure, reuse an icon already present in the automation rather than guessing a new name; the
iconsskill covers the set and its naming conventions.
Command shape — action commands take output: and inputs:
Action commands do NOT take their parameters directly. Each takes an output: (the placeholder that receives the result — name one whenever you need the result) plus an inputs: block holding every documented parameter, with the handlers on_simulate:/on_success:/on_error: as siblings of those keys:
start:
record.create/newTask:
output: new_task
inputs:
record_type: task
fields:
title: This is a new task
status: open
on_success:
return:
task_id: {{new_task.id}}
Wrong — parameters hung directly off the command. record_type:/fields: are not recognized there, so the command gets no inputs and fails:
record.create:
record_type: task
fields:
title: This is a new task
This shape applies to record./records.update:, http.request:, data.query:, function:, storage., file., kata.parse:, llm.chat:/llm.agent:/llm.embed:/llm.router:, queue., metric.increment:, email.parse:, encrypt.pgp:/decrypt.pgp:, api.command:, and var.set:/var.push:/var.unset:/var.expand:.
The exceptions are the state and flow keywords, which take their parameters directly and have no output:/inputs: — return:, error:, await:, tool.return:, set:, decision:/outcome:, repeat:, while:, and log:/log.warn:/log.error:/log.alert:.
log: is the sharpest exception: unlike every other action it takes a SINGLE STRING as its value and nothing else -- no inputs:, no output:, no on_success:/on_error:.
log: Ticket {{ticket_id}} was routed to {{group_name}}
Give it a block instead and the value is empty, which log: treats as nothing to log -- it writes no entry and reports no error. The four severities (log: debug, log.warn:, log.error:, log.alert:) all take the same bare string.
Results read off the output placeholder after the call ({{new_task.id}}), and an error reads off that same placeholder inside on_error: ({{new_task.error}}) — no separate output key is needed for errors. Look up a command's exact input table on its own page (below) instead of guessing parameter names.
Available Commands (quick reference)
This list is an INDEX, not syntax. It says a command exists and what it is for; it does not tell you what to type. Pick the command from here, then read its page and write from that.
Each command has its own page at @cerb-docs/references/docs/automations/commands/<command>.md -- read it directly for full syntax, parameters, and examples. Go straight there rather than searching: a name like set or return appears in every corner of the docs, and the search costs turns to land where one read would have. Five have no page of their own -- start, tool.return, and the log.warn/log.error/log.alert variants (covered by log.md); for those, and for the overview, read @cerb-docs/references/docs/automations.md.
State: return: (success), error: (fail), await: (pause/continuation), tool.return: (answer a tool call -- only valid inside an llm.agent: on_tool: branch)
Flow: decision:/outcome: (first-match branching), repeat: (iterate array), while: (loop)
Variables: set:, var.set: (key path), var.push: (append), var.unset: (remove), var.expand: (lazy-load)
Records: record.create:, record.get:, record.search:, record.update:, record.upsert:, record.delete:, records.update: (one call updating many records by id -- reach for it instead of wrapping record.update: in a repeat:)
HTTP: http.request: (GET/POST/PUT/PATCH/DELETE, streaming, auth)
Data: data.query: (worklist queries, custom formats)
Functions: function: (call another automation)
Storage: storage.get:, storage.set:, storage.delete: (key/value, optional TTL)
Files: file.read: (attachments/resources), file.write: (automation resources)
KATA: kata.parse: (parse KATA with placeholders)
LLM: llm.chat: (single-turn), llm.agent: (conversational + tools), llm.embed: (vectors), llm.router: (resolve a model from requirements instead of naming one)
Queues: queue.push:, queue.pop: (message queues)
Metrics: metric.increment: (time-series samples)
Email: email.parse: (MIME → ticket)
Encryption: encrypt.pgp:, decrypt.pgp:
API: api.command: (internal Cerb commands)
Logging: log:, log.warn:, log.error:, log.alert:
Simulation: simulate.success:, simulate.error: (mock outputs in on_simulate:)
Policies (principle of least privilege)
- A policy denies everything by default. A command with no rule that matches it is denied, so a policy is only ever a list of what you are allowing. Do NOT write an
all:/deny: yescatch-all at the end -- it grants nothing, denies nothing already permitted, and reads as though the rules above it were open-ended. - Allowlist every policy-gated command the script uses. Only action commands are gated. The state and flow keywords --
return:,error:,await:,tool.return:,set:,decision:/outcome:,repeat:,while:, andlog:/log.warn:/log.error:/log.alert:-- run with no rule at all, and so dovar.set:/var.push:/var.unset:/var.expand:andkata.parse:despite takingoutput:/inputs:. Entries for those are noise; leave them out. records.update:is gated separately fromrecord.update:-- allowing one does not allow the other.- Each command entry holds only allow/deny children (optionally /named).
- Guard by record type or URL with the deny-first idiom, then
allow@bool: yes. Do NOT try a positive allow/...@bool with a ternary on inputs.record_type — it does not work. - Add callers: only when restricting which toolbars may invoke an interaction.
The deny-first idiom:
deny/type@bool: {{inputs.record_type is not record type ('task')}}
allow@bool: yes
Style
- Keep KATA idiomatic — see the
kataskill for the language's own conventions. - Flag bugs or risky patterns you notice even when unasked.
Related skills
kata— the language this is written in: the indentation rule, quoting, comments, annotations.scripting— the Twig inside a{{...}}placeholder or ascript@raw:value.search-queries— writing arecord_query:forrecord.search:/data.query:.records— confirming a record type's alias, its writable fields, and its search filters.data-queries— thedata.query:command's own query language.
Signals
- GitHub stars
- 84
- Forks
- 38
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
automations-jstanden- Source
- github.com/jstanden/cerb