In Development: 12.0
Cerb (12.0) is a platform upgrade in development as of September 07, 2026. It includes more than 479 new features and improvements from community feedback.
This upgrade is free for everyone. You never need a current subscription to run the latest version of Cerb, and there's nothing to renew before upgrading. Cerb Cloud subscribers will be upgraded automatically.
Important Release Notes
-
Cerb 12.0 requires PHP 8.5+ and MySQL 8.0+ (or MariaDB 10.5+).
-
To upgrade your installation, follow these instructions.
Added
Licensing
-
[Setup/Licensing] Self-hosted Cerb is now free, with unlimited workers and unlimited seats. Cerb has always been free to self-host, but Community Edition allowed only a single simultaneous worker login, and that limit is gone – there's no seat enforcement anywhere, and no worker is displaced to make room for another. What a license raises instead, at Setup » Configure » Subscription, is concurrency: how much background work and how many AI agent turns your installation can run at once. Every install, licensed or not, gets a floor of three concurrency slots, and a license carrying fewer than three grants three. A license removes the ceiling on that pool – a licensed installation sets its own slot count on Setup » Configure » Queues – and unlocks lane configuration, so the pool can be divided between batch work and agent turns rather than following a fixed formula. A license also raises how many agent turns a single slot carries at once, from two to six – an agent turn spends nearly all of its life waiting on a model provider, so that multiplier is most of what a busy install feels. Licensed-only plugins will join that list by release. An expired license drops concurrency back to the community floor – three slots and the default lane split, with a stored split preserved and reapplied if the subscription returns – while every feature keeps working and workers and seats stay unlimited, so lapsed coverage slows an installation down rather than locking anyone out.
AI agents
-
[Agents] Added AI workers. An agent is an ordinary worker record with an
is_aiflag rather than a separate record type, so it can own tickets, be@mentioned, join groups, and hold API credentials like anyone else. An AI worker never has an interactive session (logins and SSO are refused), and doesn't require an email address.
-
[Agent Models/LLMs] Added Agent Model records for configuring the LLM models available to automations and agents. A model record holds its provider, model ID, optional API endpoint URL, an encrypted connected account for credentials, its context window, a display icon and color, and a block of provider-specific parameters. It also carries an availability status – Available, Unlisted, or Disabled – and a
priority. Unlisted is the state for a model you want reachable by name without it turning up wherever something asks for "a model", and priority is the order models are offered in when nothing names one. Automations reference a model by name instead of repeating a provider block inline, so credentials and tuning live in one place. Because the endpoint is overridable, an OpenAI-compatible provider entry can point at a self-hosted or proxied server, which is how a locally served model is configured. ATestbutton sends one short message to verify the credentials and model string before the record is used. -
[Agent Models/LLMs] The model ID field on an agent model record autocompletes. It starts from the IDs shipped with each provider, and a
Refreshbutton replaces those with your own account's catalog – fetched live from the provider using the credentials in the form, so it works before the record has ever been saved. The field stays free text either way, so a model ID works the day it ships whether or not it's in a list. Picking a model fills in the record's name, along with its context window and image support wherever the provider publishes them, and shows the provider's own description of it as a hint. A refresh that fails says why, reporting the provider's own error rather than quietly falling back to the shipped list.
-
[LLM/Agent Models] Agent models now record what they can do and how they compare, so an automation can describe the model it needs instead of naming one. The idea is one line: hard requirements belong to the pool, and best-effort preferences belong to the caller. A model that can't accept images isn't eligible at all, because sending it one is an error; asking a model to think harder costs nothing on a model that thinks less.
Capabilities are facts about the model – whether it accepts images (
hasVision:) and whether it supports extended thinking (hasThinking:) – and they filter, yes or no. Ratings are comparisons, set as a row of four glyphs you fill in like a star rating: Intelligence in brains, Speed in bolts, Privacy in locks, and Cost in coins. They work as floors and as sort orders, sointelligence:>=advanced privacy:>=zdr cost:<=cheapis one query expressing three different kinds of requirement.Every tier has a name you can type: intelligence runs
basic,efficient,advanced,frontier; speed runsslow,moderate,fast,instant; cost runsfree,cheap,moderate,premium. Privacy is a ladder where each step includes the ones below it –standard,no-training,zdr, thenlocal– so asking forprivacy:>=zdrpicks up self-hosted models too without naming them. It's a hard requirement rather than a preference on purpose: a retention rule that was requested and quietly ignored looks exactly like one that was honored. Cost is the one rating where less is better, and the editor says so; it's a rough tier for routing, not billing.The tier names are deliberately separate from the values behind them, which means the scale can move as the field does – when today's frontier models become ordinary,
frontieris repointed and existing records read as the tier below it without a single row being rewritten. Ratings start blank and unrated models sort last, so an installation that never fills them in behaves exactly as it did before. The free-text Description field is gone: these fields say more about a model than a sentence did, and unlike prose they can be searched and routed on. The provider's own description of a model still appears as a hint while you're picking one.
-
[LLM/Agent Models] An agent model's ratings draw as meters wherever they appear, with one color per rating so you can tell them apart without reading the labels: intelligence is purple, speed is orange, privacy is blue, and cost is green. Cost reads as money rather than as a score because it's the one rating where a higher tier is worse. You'll see them on the agent model worklist, profile, and card, matching the agent prompt's model picker, and the model editor keeps its glyphs tinted to the same colors. Each meter names its tier in a tooltip. The worklist shows all four ratings by default, since four compact meters fit where four words don't. Tier names are capitalized for display only, so
ZDRreads as an acronym while search queries and bulk updates use the lowercasezdryou type.
-
[LLM/Agent Models] The
agentPromptmodel picker now shows what each model can do and how it compares, so the choice is informed before it's made. Picking a model used to mean reading a list of names: everything that would inform the choice – whether it can see an image, whether it thinks, how capable or fast or private or expensive it is – lived on the model record, one navigation away from the moment you were deciding.Vision and thinking now appear beside each model's name, with intelligence, speed, privacy, and cost as small meters underneath. Capability marks appear only when a model has them, so a model that can't accept images is visibly bare beside one that can – and that absence is the useful signal, because it's the difference between choosing a text-only model on purpose and choosing it by accident.
Ratings draw as four blocks per measure at the same position on every row. Blocks rather than a bar on purpose: there are exactly four tiers and nothing between them, and three-of-four against four-of-four is a count you take in at a glance where a length is a judgement call. It's also how you set the rating on the model itself, so the two places you meet a rating look like the same thing. An unrated model keeps its blocks in gray rather than leaving a gap, which is what makes a partly rated set still readable. The ratings are what the model record claims, not measurements, and the tooltips say so.
-
[Agents/Models] The pool of agent models an automation may use is resolved by searching them rather than from a hand-maintained list. Adding a model puts it in every pool it qualifies for with no edit anywhere, and a pool can ask about anything a model records – its capabilities, its ratings, its context window, or your own custom fields. This is the resolver the
llm.router:command and the implicit default are both built on. A search can only ever narrow a pool: unlisted and disabled models are excluded before the caller's search is applied, and each model's status is re-checked on every resolve, so unlisting a model takes it out of every pool immediately. That's what makes it safe to let an automation write one. Omit a search entirely and you get every available model, in thepriorityorder an admin set. -
[Automations/LLM] In automations, added the
llm.router:command. It resolves one or more agent model searches to a list of models a script can work with as data – to round-robin them, weight them by cost, skip one that's over a rate or spend budget, balance across credentials, or feed two commands from a single resolution. Nothing names a model and nothing names a router record, so a shared automation asks for what the work needs rather than for something that has to already exist on the installation it's running on.Each search is its own
models_query/<name>:key:models_query/work: hasVision:ysays this turn is sending an image, whilemodels_query/pool:can carry what an admin allows through a workflow config value. The first query decides the set and its order and every later one only narrows it, so there's never a question of whosesort:wins. Omit the input entirely and you get the zero-config pool.A blank query is deliberately kept rather than dropped – a restriction that quietly disappears when a setting happens to be empty looks exactly like one being honored – so annotate a key
@optional:when you mean to drop it. To simply use models, letllm.agent:fall through to the default pool instead. -
[Automations/Agents] The
llm.agent:command takes an optionalagent:input naming the AI worker a turn runs as. The transcript picks up that worker's name and image, and the conversation uses that agent's memory and credentials. An agent is identity, not model policy – naming one says who the work is attributed to, never what it may run, so the same agent can do cheap work and expensive work and which models a turn may use depends on the work rather than on whose name is on it. A human or disabled worker is rejected. -
[Automations/LLM]
llm.chat:accepts an agent model by name throughmodel:instead of repeating a provider block inline. Autocompletion reads the records rather than a static list, so a newly added model appears on reload. Disabled records are excluded, since they can't be referenced. -
[Interactions] Added the
agentPromptform element for worker interactions – the composer a worker types into when an automation runs an agent conversation. It supports@mentioncompletion for workers and filesystem files, image paste and attachment, model selection, and custom slash commands, including rewrite commands that expand in the browser before the turn is submitted.
-
[Agents] Added Agent Filesystem records – named volumes of files that agents and workers can both read and write. Each volume tracks its file count and total size.
-
[Agents/Automations] Agents can mount virtual filesystems through a single
agent_terminaltool. An automation lists the volumes to mount undermounts:, and the agent can read and write them for the rest of the conversation. Each mount can be read-only (the default) or read-write, mounted at a chosen path, and provisioned on first use. Because one tool covers every volume, the tool schema stays constant as mounts change – which keeps the cached prompt prefix intact. -
[Agents] Agent filesystems are worked with using familiar file commands:
ls,find,cd,read,search,write,append,edit,copy,rm, andpwd. The same command set is available to an agent through its tool and to a worker through the Setup terminal, so the two can't drift. Command output can be piped through a Twig filter chain, and oversized output spools to a/tmpscratch area.
-
[Agents/Filesystem] Added ZIP bulk import for agent filesystems. Upload an archive and its files are imported by a background queue job, with progress shown on the filesystem record.
-
[Agents/Filesystem] Added an 'Agent Filesystem' card widget. It shows a volume's file count and size, imports a ZIP archive, and displays a history of past imports.
-
[AI/Agents] Where an agent chat appears is part of the agent, not a separate list to keep in sync with it. Every agent pane and the command bar lists the agents enabled for that surface, each with its own name and picture and a second line saying what it's for. An agent is enabled somewhere by having a block for that surface in its own configuration, so setting one up is one record rather than a record and a toolbar section that have to agree – and one shared interaction serves every agent instead of being copied per agent.
-
[Interactions] Added the
uiCommandawait for driving a host editor from an interaction. An agent chat can read and write the editor it was launched beside. A singleawait:form:can carry several commands, each gated on what the agent actually asked for. -
[Automations/LLM] An
llm.agent:turn can join an existing conversation. A script mints one session id and hands it to the agent, the composer, and the transcript, so all three work on the same conversation instead of the agent starting its own. -
[Automations/LLM] Added transcript compaction for long agent sessions. A conversation that outgrows the model's context window folds older turns behind a summary instead of failing. By default this triggers at 90% of the context window and keeps roughly the last 5% verbatim, both configurable per model under
compaction:. A worker can also fold a session on demand with/compact. -
[Automations/LLM] Agent turns can run off-request through a queue. A long turn survives navigating away and finishes in the background.
-
[Platform/LLM] Added Server-Sent Event streaming for LLM turns. A long generation is no longer killed by a request timeout partway through, and a running turn can be stopped. Streaming landed first on the Anthropic provider and now covers every chat provider Cerb ships.
-
[LLM/Providers] Added prompt caching for agent turns, on by default. A long conversation re-sends the same prefix every turn, and caching it cuts what that costs.
-
[LLM/Providers] Added per-turn token usage accounting. What each turn consumed is recorded and shown in agent transcripts, along with the conversation's current size and session totals that separate full-price input from cached reads and writes – so it's visible at a glance whether prompt caching is actually earning its keep on a long conversation.
-
[LLM/Transcripts] Effort level and reasoning tokens are now shown on LLM agent transcripts, alongside the per-turn token usage already reported there.
-
[LLM/Providers] Standardized reasoning configuration on a canonical
effort:key plus a groupedthinking:block. One authoring form now drives reasoning across every provider that supports it. -
[LLM/Reasoning] Reasoning effort now reaches every model provider, and a level you pick is either sent or refused instead of quietly ignored. Set it with
effort:in anllm:block, or from the effort submenu on theagentPromptmodel picker.Four providers were dropping the level on the floor: Groq, Together AI, and Hugging Face never forwarded
effort:at all; Ollama collected a model's reasoning out of the response but never asked for it, so none ever arrived; and on Gemini, any turn carrying tools had the level stripped before it went out, which silently ran agent loops with reasoning off. The composer could lose it too – a model whose entry declared no effort choices accepted whatever the picker posted and then sent nothing, so the conversation ran at the provider's default while the interface showed the level the worker had chosen.The picker now offers the levels that particular model takes, an unavailable level is an error you can see rather than a silent fallback, and a model Cerb can't identify offers no levels at all rather than a plausible guess.
effort:autocompletion covers every chat provider, each offering the levels it actually documents instead of a generic list.Two upgrade notes: if you already set
effort:on a Groq, Together AI, or Hugging Face model it was being ignored and will now genuinely be sent, which can surface as a rejection from a model that doesn't accept the parameter; and Ollama'sthinkisn't a graded scale like the others, sononeturns reasoning off,low,medium, andhighmap across directly, and anything abovehighis sent ashighrather than rejected. -
[LLM/Providers] Added image support to LLM chats. Images can be attached to a turn or pasted into an agent prompt, and they render inline in transcripts.
-
[LLM/Tools] Agent tools can define display
labels:(summary:andactive:) and anicon:for transcripts, so a tool call reads as "Searching the knowledge base" rather than as a raw function name. A label can also carry placeholders, resolved against that call's own parameters, so the row reports what the agent actually asked for rather than a fixed phrase. The agent never sees either; the icon defaults tohammer.
-
[Platform/LLM] Added a neutral
finish_reasonto LLM chat responses. This is what tells a truncated or interrupted turn apart from a completed one, and it's shown in the transcript viewer. -
[LLM/Providers] Added Qwen Cloud and z.ai as first-class LLM providers. Both appear in the provider list with their own endpoints, model hints, and brand icons.
-
[LLM/Providers] Added OpenRouter as a first-class LLM provider. One account reaches nearly every frontier model, and until now using it meant configuring a generic OpenAI-compatible provider: type the endpoint URL by hand, and accept that every model showed the OpenAI logo in transcripts and pickers. Selecting OpenRouter from the provider list fills that in. The bigger gain is configuration – OpenRouter publishes a context window and the accepted input types for every model it serves, so refreshing the model list on an agent model record fills in the context window and image support for whichever model you pick, across every vendor it fronts. Most providers don't report that much about their own models. Model IDs are namespaced by vendor, like
anthropic/claude-sonnet-5oropenai/gpt-5.6-terra, and transcripts and pickers show the OpenRouter mark rather than the underlying vendor's, since that's the route the request and the bill actually took; add adisplay:block to the model record if you'd rather see the vendor. -
[LLM/Providers] Updated the Claude model IDs offered by autocompletion for the Anthropic and AWS Bedrock providers.
-
[Toolbars/Search] A worklist search bar can now host an AI agent that reads, rewrites, and runs your query. Search syntax is the single hardest thing in Cerb to learn, and the cost of that lands on exactly the people least equipped to pay it – describing what you want in plain language is a large win, and it's a win everywhere a worklist appears.
An agent icon sits at the right of the search bar; click it and a floating window opens over the list, offering the chats available there, and picking one starts the conversation. The icon appears once an agent is enabled on the
worklistsurface; a search bar with no agent to offer hides it rather than showing an empty one. The agent also needs an agent model under Search » Agent Models, without which the chat opens but every turn errors. The window can be dragged wherever suits you.The agent can read what's currently in the field, propose a replacement, write it back, and run it, and it can look up the record type's searchable fields and preview how many results a candidate query would return before touching your screen. Nothing about the worklist is rearranged to make room – it keeps its full width and stays usable underneath, so you can keep paging and refining with the chat open. Which agents a search bar offers is set on each agent, in the AI tab of its worker record.
-
[Automations/Interactions] Added an
interaction.worker.agenttrigger for automations that drive an editor from an agent pane.interaction.internalexists to hide the automations Cerb runs on its own behalf – record choosers, autocomplete helpers, the dialogs inside the automation and metric builders – so you can filter them out of a worklist and never think about them again. Agent chats had been sharing that trigger for one reason: it was the only one offeringuiCommand, which put a worker's own conversations in the bucket meant for machinery. They now have a trigger of their own. Script autocompletion offers the element and its options, the Form Builder gives it a real inspector instead of a raw KATA box, and a toolbar'sinteraction: uri:autocompletion lists agent interactions alongside ordinary worker ones. In every other respect it's an ordinary worker interaction – same form elements, same awaits, same resumable conversations. -
[Automations/Interactions] A parked conversation now describes itself in the lists that offer it back to you. Previously every one looked alike: the automation's own description on a generic clock icon, separated only by "Last active 18 hours ago". Ten chats with one automation produced ten identical rows, so the list was quicker to ignore than to read, and the work quietly got redone in a fresh conversation instead. A conversation now takes its name and icon from the toolbar item that launched it, and when the form shows an
llmTranscriptit also picks up the model provider's mark and an excerpt of your most recent message. Rename a toolbar item and its past conversations are renamed with it. None of this needs configuring; an automation that wants something different can add an optionalresume:block to itsawait:form:withlabel:,preview:,icon:, orcolor:. Each is sticky – a key that evaluates empty keeps what was stored rather than clearing it, so a long agent turn can't blank the name of the conversation it belongs to. -
[Automations/Interactions] A failed agent turn now returns to the chat instead of ending the interaction. Until now any failure on a background turn ended the conversation with a generic "please try again", and whatever the worker had typed was gone – including failures that clear on their own in seconds, like a rate limit that came back in a third of a second having generated nothing and billed nothing. The reason now appears above the composer, led by what to do about it and followed by the provider's own text, which is often the only thing that names the model or the region. The message is restored to the composer, so sending again is a click instead of a retyped paragraph, and the session is rewound to where it stood before Send so a resend can't leave behind a duplicate of a message the model never answered. Automation authors reach the same failure through the
llm.agent:node'son_error:branch, whose output var carrieserror_status,retryable, andretry_promptfor branching on the failure class. -
[LLM/Providers/Interactions] A rate-limited agent turn now retries itself instead of interrupting the chat. Providers with low per-minute request limits can rate limit an ordinary turn in a busy conversation, and until now that ended the turn – you had to press Send again even though nothing had been generated and the limit would clear within seconds. A rejection (429) or an overloaded provider (529) is now retried up to three times, honoring the provider's own wait when it names one and backing off 5 seconds and then 15 when it doesn't. While it waits, the conversation says why and for how long, so a pause never looks like a stalled model. Only rejections are retried, and deliberately so: those are requests a model never saw, so nothing was generated and nothing was billed. A turn that timed out is never retried, because the provider may well have produced – and charged for – a full response that we simply stopped waiting for. Rate limits from AWS Bedrock and Ollama are also reported accurately now, rather than described as the provider being unreachable.
-
[LLM/Providers] Added response streaming to OpenAI and every OpenAI-compatible provider: OpenAI, Docker Model Runner, Gemini, Qwen, z.ai, Groq, Together AI, and Hugging Face. Until now only Anthropic streamed, so a turn that ran past the request timeout was cut off mid-generation and everything it had produced was paid for and thrown away. Streaming replaces that total time limit with an inactivity cutoff, so a turn that's actively producing output is never interrupted and only a genuinely stalled one is – and a running turn can be stopped, keeping whatever it had already written. Groq, Together AI, and Hugging Face each carried their own copy of the request logic and now share the OpenAI implementation, picking up the longer per-turn budget that background turns use along with failure classification, so a rate limit is retried rather than surfacing as an error. Streaming is on by default wherever it's supported; add
stream@bool: noto a provider's block to turn it off, which is worth trying against an endpoint that doesn't stream correctly. -
[LLM/Providers] Ollama now streams responses. A streamed turn appears as it's written and the Stop button ends it mid-generation, and a turn cut short – by a stop, a dropped connection, or a worker that dies – keeps the text and completed tool calls that arrived, recorded as truncated rather than lost. Reasoning models stream their thinking alongside the reply, and token counts and the stop reason are recorded exactly as on a non-streamed turn. Because Ollama sends no keepalive while it works, the entire wait for the first token counts as silence: a local server loading a large model off disk, or Ollama Cloud queueing behind other requests, can be quiet for a long time before anything arrives. The allowance for that is generous by default and can be raised with
stream_stall_secs. -
[LLM/Providers] In the Ollama provider, the Ollama Cloud API endpoint is now offered as a suggestion.
-
[LLM/Providers] Added token usage counts to the Ollama provider, so an Ollama turn reports what it consumed in transcripts like every other provider.
-
[LLM/Providers] AWS Bedrock now runs on the Converse API, so every model family works rather than only Anthropic. DeepSeek, Qwen, GLM, Llama, Nova, Mistral, and others now return their reply text, token counts, and tool calls, and models that emit reasoning no longer leak it into the answer.
-
[LLM/Providers] AWS Bedrock now streams responses. It was the last chat provider still sending one blocking request, so a turn that ran past the timeout was cut off mid-generation and everything it had produced was discarded. Streaming is used automatically on the models that support it – Bedrock reports that per model, and a model that can't stream quietly falls back to a normal request instead of failing, so nothing needs configuring either way. Prompt caching, tool calls, and separated reasoning all behave exactly as they do on a non-streamed turn.
-
[LLM/Providers] AWS Bedrock now supports prompt caching on the models that offer it. Cerb checks your account's own model catalog to find them, so a model without caching is never sent a cache marker.
-
[LLM/Providers] Extended thinking and reasoning effort can now be set on AWS Bedrock models. Until now a Bedrock model reasoned at whatever depth it chose, with no way to raise it, lower it, or turn it off – every other provider already read the
thinking:block and theeffort:key, and Bedrock ignored both, so a hard problem couldn't be given more thinking and a simple one couldn't be made cheaper. Both keys now work the way they do elsewhere:thinking:withtype: adaptiveon current Anthropic models ortype: enabledon older ones, andeffort: low|medium|high|xhigh|maxfor depth. On older models effort becomes a thinking budget sized to fit insidemax_tokens, and if there's no room for a valid budget thinking is left off rather than sent as a request that would fail. These keys are for Anthropic models only, since Bedrock forwards the parameter straight to the model. Nothing is sent unless you set one, so existing configurations are unchanged, and the provider block's autocompletion now offersthinking:,effort:, andstream@bool:. -
[LLM/Providers] AWS Bedrock now lists its available models when you create an agent model record. The list comes from your account's own Bedrock catalog and includes the cross-region inference profile IDs that most current Claude and Nova models require.
-
[LLM/Providers] When adding an agent model record, providers now suggest API endpoints as you type. This is particularly useful on AWS Bedrock, which uses regional endpoints.
-
[LLM/Providers] When listing models fails, the provider's own error message is now shown instead of a bare HTTP status code. On AWS Bedrock this names the exact IAM action that's missing.
-
[LLM/Providers] Long agent turns no longer time out after 30 seconds on Ollama, Groq, Hugging Face, Together AI, or AWS Bedrock. Every provider now honors the extended per-turn timeout that background turns allow, which previously only Anthropic and OpenAI did.
-
[Agent/Terminal] An agent can now ask Cerb about itself, through a
cerbcommand line in its terminal. Ask an agent for "Kina's open tickets" and it has to know thatticketis a record type here, thatstatusandowner.atare things you can filter by, and that your custom fields exist at all – none of which is in its training data, and none of which is a file anyone would think to import, so until now an agent guessed, and a wrong guess looks exactly like a working one until the query returns nothing.cerb records typeslists every record type defined in your installation, custom record types included, andcerb records filters ticketorcerb records fields ticket,workerreports one type's search keys or writable fields, custom fields and fieldsets included. The answers come from the same reflection that powers Setup » Records, so what an agent is told and what an admin sees can never disagree.Output is a compact table by default,
--format jsonwhen something needs to parse it, and it pipes like any other terminal command, so an agent can narrow a long list without spending the whole thing on context. An automation opts in by naming the namespace under itsterminal:input; a superuser can try the same commands at Setup » Developers » Agent Filesystem Terminal, where a Commands panel turns each namespace on or off.
-
[Agent/Pane] An AI agent chat now gets its host editor's commands as tools automatically. Until now a chat that drove an editor had to carry the whole bridge itself – a
tool/definition per command plus anon_tool:branch with auiCommandawait to dispatch each one – which made a chat for the automation editor 279 lines before you wrote a word of your own, and which went stale silently when the editor gained a command afterwards.The
interaction.worker.agenttrigger now contributes those commands itself, resolved from wherever the pane is mounted, the same waymounts:provisions the agent's terminal. A chat opened beside the automation editor, a worklist, or the Icon Builder can read and rewrite it with nothing in its script, and an editor that gains a command reaches every existing chat on its next turn, including ones already saved.An
on_tool:branch still runs before every editor command, so that's still where a tool is gated behind a human: answer the call withtool.return:and the editor is never touched. -
[Agent/Pane] An agent pane can now answer a tool on the server, with no round-trip to the browser. Every tool an agent pane gave a chat used to be a question for the editor – read this field, write that one – which is the right shape when the answer is in the document in front of you, and the wrong one when the answer belongs to the installation itself. The Icon Builder's assistant is the case that made it obvious: naming an existing icon is most of the work, and the browser has no way to say what the set contains. It now gets a
cerb_list_iconstool answered on the server, returning every icon name in the set, narrowable with a filter when the list is long, so the assistant matches an existing icon instead of guessing whether one exists. -
[Agent/Pane] The command bar can now host an AI agent chat. Every agent pane so far has sat beside an editor, with the job of reading and writing the document in front of you, which left nowhere to put a chat that follows you around the app instead of belonging to one screen. A chat written for the command bar gets two things an editor chat has no use for:
cerb_get_pagereads the page Cerb actually routed, which is what you're looking at even when the browser URL says otherwise, andcerb_open_searchputs a search popup for any record type in front of you, prefilled with a query the agent wrote – better than describing a query and asking you to paste it. Both are one-way on purpose. The command bar can put something in front of you but can't read anything back, so a chat there tells you what it searched for and lets you read the results; it never sees them itself, and it knows where you are but never what's on the screen. Which agents the command bar offers is set on each agent, the same way every other surface is, and the command bar's existing non-agentic shortcuts are untouched. -
[Agent/Chat] An agent chat in an agent pane now follows the newest turn, and holds its place when you scroll up to read. Following the newest turn is right until you scroll up to re-read something, and then it's exactly wrong: every poll tick yanked you back to the bottom mid-sentence, and a long agent run was the worst case because the ticks never stop. The transcript now pins to the bottom only when you were already there, including across the re-render that each step of a tool loop causes, so reading something is no longer a race against the next turn. When there's newer content below you, a "Jump to latest" pill appears; clicking it takes you down and re-arms the follow. Sending a message re-arms it too, since nobody scrolls up in order to miss their own reply.
-
[LLM/Providers/OpenAI] Agent models pointed at OpenAI's own API now use the
/v1/responsesendpoint, where tools and reasoning work together. The older/v1/chat/completionsendpoint refuses function tools on a reasoning turn for gpt-5.4 and newer, which costs a tool-using agent its reasoning in the one configuration where it matters most. That endpoint also returns reasoning summaries, which is the only way an OpenAI model shows its thinking; it's authored asthinking: display:– the same block and keys as Anthropic's, so it reads the same across providers – and it's on by default for a model flagged as thinking. Any other endpoint keeps using/v1/chat/completions: Azure, llama.cpp, vLLM, MLX, and every OpenAI-compatible provider. Setapi: chaton an OpenAI record to pin it to that endpoint, andapi: responsesis refused on an endpoint that can't serve it rather than quietly downgraded. -
[LLM/Providers/OpenAI] LLM providers using an OpenAI-compatible API can now pass arbitrary
extra_body:parameters through to the inference server. The OpenAI-compatible ecosystem puts its knobs somewhere other than where the OpenAI spec does, and a fixed set of typed parameters can't keep up – Apple-silicon oMLX serving Qwen3 forwardschat_template_kwargsto the Jinja template but doesn't map the standard top-levelreasoning_effortinto it, soeffort:reached the server, was dropped on the floor, and nothing said so. llama.cpp, vLLM, SGLang, and unsloth all have their own such keys. The key is named and shaped after the OpenAI SDK's ownextra_body, deliberately, so a snippet from a server's documentation transcribes straight into KATA: the contents merge into the top level of the request body, soextra_body: chat_template_kwargs: reasoning_effort: mediumputschat_template_kwargson the wire rather than a key calledextra_body. Cerb keeps ownership of the request's structure, somodel,messages,stream,stream_options, andtoolscan't be overwritten. -
[LLM/Agents/Performance] An agent's system prompt is composed once and frozen for the life of the conversation, so the most cacheable part of every request stops moving. It's rebuilt when someone edits the automation – the authored
system_prompt:,tools:,mounts:, orterminal:block – and that takes effect on the worker's next message. Nothing else rebuilds it: a host gaining a tool, or an agent writing a file into a volume it can write to, leaves an open conversation's prompt alone. The session now stores the full prompt that was sent rather than the author's fragment, so a transcript can answer what the agent actually saw. A trigger may contribute the opening of that prompt, the same way it already contributes tools, and the author's ownsystem_prompt:is appended to it rather than replacing it. -
[Agent/Terminal] An agent can now ask Cerb about the installation it's running in, through a
cerb platformcommand line in its terminal. The documentation describes the plugins that exist; it can't tell an agent which of them are in your installation, which are switched off, or what a third party added. Ask an agent whether it can file a JIRA issue, or which automation triggers it may write for, and until now it either guessed or told you to go look.cerb platform pluginslists every plugin with its ID, version, and whether it's enabled – disabled ones included and marked, because "the JIRA plugin is here but switched off" and "there is no JIRA integration" are different answers and only one of them is yours to fix.cerb platform pointslists every extension point with how many extensions this installation has on it, andcerb platform extensions cerb.automation.triggerlists what's on one.That last one isn't only for plugin development. An extension ID is a value stored on ordinary records, so it's a value you search by: an automation's
trigger:filter takescerb.trigger.interaction.worker, not the shortinteraction.workerthe trigger calls itself. Listing the extensions on a point is how an agent gets from "automations that run when a worker starts an interaction" to a query that returns them, and the same holds for queue consumers, search indexes, connected services, and card widgets.Output is a compact table by default,
--format jsonwhen something needs to parse it,--filterto narrow a long point, and it pipes like any other terminal command. An automation opts in by naming the namespace under itsterminal:input; a superuser can try the same commands at Setup » Developers » Agent Filesystem Terminal, where the Commands panel gained aplatformtoggle. -
[Agent/Terminal] An agent can now validate KATA and JSON before it saves them, through a
cerb codecommand line in its terminal. A model writing an automation has no feedback loop: it writes the whole thing, saves, and learns from a save error what one line got wrong – then rewrites the whole thing, because it can't tell which parts were fine. That's several turns and a lot of guessing for what a linter answers in one.cerb code kata lint /tmp/draft.kata --schema automationreports every problem with its line and the line itself, validating against the same schemas Cerb enforces on save, so passing here means the save won't be rejected for that reason.cerb code kata schemaslists the schemas this installation has – automations, automation policies, sheets, toolbars, workflows, charts, and the rest – and something not written to a file yet can be checked directly with--payload.cerb code json lintdoes the same for JSON, and it says where the document stops being JSON and why: a trailing comma before a brace, a single-quoted key, a missing comma between two values,Truewheretruebelongs. PHP's own answer to all of those is the words "Syntax error" with no position, which is exactly what makes a model rewrite the file instead of the line.cerb code json formatprints a document in Cerb's own JSON formatting, and refuses to format one that isn't valid.Superusers can try all of it at Setup » Developers » Agent Filesystem Terminal, where the Commands panel gained a
codetoggle and the Payload box holds the document being checked. -
[Agent/Terminal] The agent terminal's
searchcan now tell you which words are worth searching for.search --termscounts how many files contain each word instead of searching for all of them, so an agent can throw a dozen synonyms at a volume in one call, keep the winner, and refine – rather than guessing a query at a time. Prefix a word with+to require it and count the others alongside it, which collapses the numbers fast and shows what actually co-occurs. A wildcard or stem term lists the concrete words it covers, soauto*reportsautomations (476) automation (374) automatically (228) ..., which is how you learn the vocabulary a corpus actually uses instead of guessing at it. -
[Agent/Terminal] An agent terminal
searchthat matches nothing now answers with per-word counts rather than "No matches".searchis AND – every word must appear in the same file – so a pile of near-synonyms reliably matched nothing, and the reply gave no hint of which word emptied it. The fallback prints the same tablesearch --termsgives, naming the word that matched nothing and the ones worth keeping. It keeps any+already in the query, so the second step of a search loop doesn't throw away the term you just established. -
[Agents/Filesystems] Agent filesystems can now ship with Cerb and import themselves on update, so a bundled volume automatically stays current for the version you're running. It lives in the source tree as ordinary files and is imported on
/update, so an agent has reference material without anyone uploading an archive. Each volume carries a manifest and only its hash is read at runtime: an update where nothing changed costs one registry read and does no work at all, while a changed volume is zipped and run through the same import job an admin upload uses, inheriting its batching, retry, prune, and queue job progress. The job is drained inline, so when/updatefinishes the content is there. A bundled volume mirrors what shipped – a file created in it by a worker or an agent is removed on the next update – while content imported by hand through the ordinary upload is never touched. Setup » Developers » Platform gains a 'Reload' button that reconciles every bundled volume against its rows without waiting for a version change. -
[Agents/Filesystems] Cerb now ships a skills volume for its own AI agents. The bundled
cerb-agentsfilesystem carries ten skills – the agent terminal, KATA, automations, scripting, search queries, records, data queries, icons, mail replies, and the documentation – each one file an agent reads when it needs it. An agent can look up the grammar of a search filter or the shape of an automation command instead of guessing, which is where a capable model is most often subtly wrong.Skills compose, so one agent can be an expert in several domains at once for a single request: an automation agent asked for a chart of open tickets by group reads
automationsfor the command shape,data-queriesfor the source and its aggregation,scriptingfor the Twig in between, andsearch-queriesfor the filter grammar – four short reads on the turn that needs them, rather than one prompt large enough to hold all four being re-sent on every turn of every conversation. The same volume serves every agent, so a skill improved once is improved for all of them.Nothing is loaded up front; an
INDEX.mdsays what each skill covers and when to reach for it. Reading a skill grants knowledge, not a new role – an agent's identity and tools come from its system prompt and stay fixed for the conversation, and work that genuinely belongs to another role belongs to a subagent. -
[Agents/Filesystems] Cerb now ships its own documentation as a bundled agent filesystem. The
cerb-docsvolume carries the complete documentation as Markdown – the reference manual, guides, worked solutions, tips, workflows, and release notes – and imports itself on update like any other bundled volume.Mount it on an agent and it can look a feature up rather than recall it, which matters most exactly where a capable model is subtly wrong: search query syntax, KATA keys, API endpoints, and automation commands. It's over a thousand files, so it's something to search rather than browse – an
INDEX.mdat the volume root says what lives in each directory, which paths are predictable enough to read directly, and what each page's front matter carries, which is enough to orient an agent that mounts the documentation and nothing else.A
docsskill in thecerb-agentsvolume covers the same ground in more depth for an agent that has both. Because it's bundled, the documentation tracks the release: what an agent reads is the documentation for the version it's running on. -
[Agent/Pane] An agent pane's role and tool inventory are composed by Cerb at runtime. An agent sitting beside an editor is told what it is, where it sits, and what that editor can do, with nothing in the automation that drives it. The author's own
system_prompt:is appended to that rather than replacing it, so adding a line about a custom tool is one line, and leaving it empty – the normal case – still produces a complete prompt. Composing at runtime rather than generating into each script is what lets an improved role reach an automation that was deployed before it: nothing is patched and nothing is regenerated. The tool inventory beneath each role is built from the live catalog, so it can't drift from the tools actually offered, and each agent-pane component names the skills its work needs and the documentation paths that cover it. Both pointers are emitted only when the volume behind them is really mounted – a chat can be authored with no filesystem at all, and sending an agent after files it can't read is worse than saying nothing. -
[Agents/Files] Added bulk update for agent files. The agent files worklist has a bulk update button for moving files between filesystems, editing their fields, and running automations over a batch. The popup also lists any
record.bulkUpdateautomations bound to agent files, so a batch can be handed to a script – generating embeddings for every file on a volume, for instance. Moving a batch to another filesystem skips any file whose path is already taken on the destination, since one filesystem can't hold two files at the same path; the rest of the batch still moves. Bulk updating agent files requires an administrator, matching the rest of agent file editing. -
[AI/Agents] An AI worker's peek has an AI tab for setting what the agent brings to a turn and where it runs – instructions, which models it may use, filesystems, tools, and its
cerbcommand line. A rail lists Everywhere first and then each surface the agent can run on, with a green dot beside the ones it's turned on for. 'Everywhere' is what the agent brings no matter where it's running, and a surface adds to that rather than replacing it: filesystems, tools, and command-line namespaces are the union of the two, and only the model query and the chat it runs are swapped outright.That's why the scope is called Everywhere rather than Defaults – there's nothing there to override. The command line is grants only. A surface can hand the agent a namespace it doesn't have everywhere and can never take one away, so the field is a picker of things to add rather than a row of checkboxes that would suggest unticking one could revoke it.
Each surface shows what it's adding to, in the field itself: filesystems, tools, the chat, and the command line show what Everywhere contributes as faded tiles beside your own choices, while instructions and the model query show it as the placeholder, since there's nothing there to sit beside. All of it follows an edit to Everywhere immediately, without saving, so turning something on above takes it out of every surface below while you watch. Turning a surface off keeps everything configured for it – the switch says "not running here", not "start over". Configuration written by hand through the API or an automation survives a visit to this tab, including things the form draws no control for; anything it can't show as a chip is listed plainly with a button to remove it, so "carried through untouched" doesn't mean "stuck there forever".
-
[AI/Agents] Cerb now ships the chat an agent runs by default. The
cerb.ai.agentworkflow installs aninteraction.worker.agentautomation namedcerb.ai.agent.chat, and an agent that names no chat of its own runs that one – so creating an AI worker and turning it on for a surface is all it takes to have a working conversation there.The chat adapts to wherever it opens: the surface contributes that editor's commands as tools and its orientation as a system prompt at runtime, so one script drives whichever screen it sits beside. It mounts Cerb's own documentation and reference skills read-only, and enables the
cerbcommand line over this installation's record types, fields, and filters, so it looks a name up rather than recalling one that may not exist here.It needs an agent model under Search » Agent Models; without one the chat opens and every turn reports an error, which is the honest failure for something that needs a model nobody has configured yet. The workflow is installed on new installations and once on upgrade; disable or delete it and it stays gone, since it's never re-enabled on a later update. To build your own, use Automations » Build » AI Agent Chat, which generates a copy you own and point an agent's
automation:at it. -
[AI/Agents] Added Agent Tool records – a record type for the tools an AI agent can call. A tool is defined once and then added to any agent, rather than restated in each one. The record wraps an
agent.toolautomation with everything the automation itself has nowhere to say: the name the model calls, the description it reads, and the icon and wording its transcript shows. The automation knows how to do the work; the record says what the model should call it and what the conversation shows while it runs.What the tool takes isn't restated on the record – the schema the model is shown is built from the answering automation's own
inputs:block, which already carries a description,required,allowed_values, and adefaultper input, and is what the script reads back. Declaring the arguments twice would mean two places to change and one of them silently wrong. The icon and the two labels are what a conversation shows – "Searching the web…" while the call runs, "Searched the web" once it returns – and either label can quote the call's own arguments, soSearched for {{query}}reads back what the agent actually asked for.A tool is Available (offered wherever tools are picked), Unlisted (hidden from the pickers but still runs when an agent names it directly), or Disabled (refused everywhere). Its name is the function name the model calls, so it must be unique, start with a lowercase letter, and contain only lowercase letters, numbers, and underscores;
cerb_is reserved for Cerb's own built-in tools, so yours can never shadow one. Agent tools are searchable and work in worklists and workspaces like any other record. A tool record holds no credentials of its own – an API key for whatever the tool calls lives in the automation behind it, or in a connected account that automation reads.
-
[AI/Agents] Agents can now call the tools you define, referenced by name from an agent's own configuration or from a chat's
tools:block. A reference is just the agent tool record's name, and the entry under it may override the presentation the record supplies –description:,icon:, andlabels:– or switch the tool off for this agent withdisabled@bool: yes. A conversation's tools are settled when it starts, because the tool set sits in the prompt prefix the provider caches: editing a tool reaches new conversations rather than rewriting one already under way, and switching a tool off refuses new calls to it rather than making it vanish mid-conversation. A tool that's disabled, or whose record is missing, when a conversation starts is never offered at all, so the model is never shown something it would only be refused. -
[Automations/Agents] Added an
agent.tooltrigger for the automation behind an agent tool record. It's handed the environment of the call rather than only the model's arguments, so a tool can refuse work that doesn't belong where it was invoked. The arguments arrive asinputs.*, declared in the automation's owninputs:block – that block is the schema the model is shown, so a tool is self-documenting and there's exactly one place to change what it takes.Alongside them a tool receives
tool_*, the agent tool record that was called;agent_*, the AI worker running it;transcript_user_*, the worker that agent is serving;transcript_surface, where the conversation is happening; andtranscript_triggerandtranscript_uuid, which kind of conversation this is and which one in particular. Because the record owns the schema, one automation can back several tools – branch ontool_nameto tell them apart.An agent tool runs to completion inside a single tool call, so it returns a
content:string and can'tawait:; anawait:at any depth is refused when the automation is saved, with a message saying to returncontent:instead. The AI Agent Tool starter in the automation builder seeds a script on the new trigger, and an automation's Usage panel now resolves the agent tools that reference it by looking them up rather than by scanning every script for its name. -
[AI/Agents/Transcripts] An agent chat can now show who is speaking instead of a generic "Agent". An
llmTranscript:element takes anagent:block, and every agent turn is attributed to it: the byline reads the agent's name and the avatar is the agent's rather than the vendor's. The model stays visible – its mark moves to a small badge on the lower right of the avatar, in the provider's brand color – so a reader can see who they're talking to and still see that it's an AI, which one glyph alone couldn't say.Name an AI worker (
agent: '@cerb', a worker id, orcerb:worker:<id|handle>) and the name and avatar come from that record, or write the identity inline withname:,icon:, andcolor:for a chat with no worker behind it.color:takes a Cerb UI hue – red, blue, green, gray, orange, or purple – which resolves to the theme's own color rather than the CSS keyword of the same name, so it matches every other pill and tag in the product; any CSS color works too, and left out, the color is hashed from the name.This is display only: nothing is written to the conversation, so an agent renamed today relabels the chats it already had, and naming an AI worker on
llm.agent:remains what gives a chat a real identity – attribution, memory, and credentials. -
[Setup/Developers] LLM Agent Transcripts now identify the agent behind each session. A transcript is titled by its agent, an Agent chip links to the record holding the instructions and tools the turns were composed from, and every agent turn carries that agent's picture and byline instead of a generic "Agent". The session uuid stops being the headline: only its leading characters distinguish one session from another, so it's quoted like a git commit – eight characters in the eyebrow, the whole value on hover and on the Permalink button. That leaves the list showing what you actually pick a transcript by – which agent ran it, for whom, how long ago, and how large it got.
-
[AI/Agents] Agents appear in the command bar under their
@handle, over a line saying what picking one does. The bar mixes agents with every other shortcut in Cerb, so a row reads as the agent you'd@mentionanywhere else rather than as the name of the script behind it. -
[Agent/Terminal] Added
cerb platform versionto thecerbcommand line. An agent can ask what version of Cerb it's working in, whether that install is self-hosted or on Cerb Cloud, and what PHP and database it runs on. It reports Cerb's version and build, the deployment, the Devblocks platform build, PHP with its SAPI, and the database server's own version string, which names the flavor too. This is what an agent should ask before assuming a feature exists:versionis the exact string a workflow'scerb_version: '>=12.0'gate or a package'srequires.cerb_versioncompares against, whilebuildis a date serial that moves with every release and is what the updater compares – it isn't a version and doesn't sort against one. The deployment row reads Cerb Cloud when Cerb hosts the install, naming its subdomain, and Self-Hosted otherwise. Like the other sub-commands it takes--format jsonand binds its rows for piping. -
[Records/Workers] On worker worklists, AI accounts now carry a visual
botindicator, so an agent is recognizable at a glance in a roster of people. -
[AI/Agents/Mail Routing] An agent can now sit beside both mail routing editors – the standalone Mail Routing Rule record and a group's Mail: Incoming tab – reading and writing the rules and testing them against a sample message. It's one surface rather than two, because the grammar, the schema, and the evaluator are shared; what differs is where in the pipeline the document runs, and the agent is told that when it reads:
scopecomes back asrulefor a standalone rule that decides the group, orgroupfor a group's own rules that decide the bucket.Alongside the read, edit, grep, and diff tools every editor surface offers, this one adds
cerb_test_routing, answered on the server rather than through the browser bridge: it runs a routing document against a sample message – subject, body, recipients, sender, spam score, headers – and reports which rule matched. That's the difference between an agent checking a rule and asserting that it works, and because the document is an argument rather than the editor's contents, a candidate rule can be tested before it's written in.Both editors also mark changed lines in the gutter against the last save, whichever of you made the change, and clicking a mark shows what changed; Save and continue takes a fresh checkpoint, so the marks always answer what you've touched in this sitting. The agent reads that same diff through
cerb_get_diff, so its account of an edit is checked against what it actually did rather than what it meant to do.
-
[Records/Workers] An AI worker's whole agent configuration can now be set through the records API and workflows, as an
agent_configfield on the worker record. Until now the AI tab was the only way to author one, which left agents out of the text-based tooling every other record type already has – an agent couldn't be version-controlled, diffed, or synchronized between a development and a production install. Write it as a nested KATA object, so a workflow template can target a single leaf – amodels_query:drawn from workflow config, say – instead of interpolating into one opaque blob; a KATA string is accepted too for anyone writing it by hand. Either way it's validated against the same agent schema the AI tab uses, so a malformed configuration is refused rather than stored. It's only meaningful on a worker withis_aiset.
Automations
-
[Automations] New automations open on a list of starter templates rather than an empty editor. Picking one seeds a working trigger, script, and policy; several ask a few questions first and generate the script from your answers. The five bundled templates cover AI agent chats and agent tools, worker interactions, reacting to ticket changes, and sending a Slack message. Plugins can contribute their own through a new
cerb.automation.templateextension point, and the list gains a filter box once enough templates are installed to need one.
-
[Automations] Added a read-only control-flow graph for automations. It maps out a script's decisions, outcomes, and loops visually, which makes an unfamiliar or deeply nested automation far easier to follow. The graph appears in the editor's 'Visualization' tab and is also available as an 'Automation: Graph' card, profile, and workspace widget. Double-clicking a node or one of its properties jumps to that line in the code, so the graph works as a way to navigate a long script rather than only to read it.
-
[Automations] Added "Simulate initial state" for priming a test run from the automation editor. Test input is filled in as a form built from the trigger's inputs and the automation's own
inputs:, rather than handwritten as state YAML. Triggers describe their simulatable scope, so a mail filter, webhook responder, or worker interaction each prompt for the fields that actually apply – and a trigger can replace that scope wholesale, as a mail filter does by asking for one example message and expanding it into sender, subject, headers, body, and recipients. Priming the form runs nothing, so it costs nothing even on an automation that calls a model.
-
[Automations] Added a visual Form Builder for interaction
await:form:blocks. Form elements are assembled by dragging components onto a canvas that renders as the finished form, with the KATA generated beneath it as you build. A field's options are edited in an inspector beside the canvas. This makes it much faster to build a worker or website interaction without memorizing every element's options.
-
[Automations/Editor] Added a "What changed" diff between a run's input and output in the automation editor's 'Run' tab. Previously you compared two YAML documents side by side and spotted the differences yourself; now the editor shows what the automation actually changed.
-
[Automations/Editor] Added least-privilege policy generation to the automation editor. It reads the script in front of it and writes the tightest policy that still allows it to run, which is a much better starting point than an overly broad policy that quietly permits more than intended. The generated policy takes the form of deny guards under an allow, scoping each command to the values the script actually uses.
-
[Automations/Interactions] Worker interactions can now be listed, resumed, and disposed of. An interaction abandoned by navigating away or closing a popup is no longer lost – it can be picked back up where it left off.
-
[Cerb UI] Added the interactions command bar. Every interaction you can run, plus the conversations still waiting on you, opens from one searchable panel instead of whichever toolbar happened to own it. Resumable interactions are listed first so they're easy to pick back up, and an entry carries its automation's description as a subtitle wherever one is set – which is a good reason to write them.
-
[Automation/Scripting/Functions] In automation scripting, added a
uuid()function to generate a UUIDv1. This is useful for minting a stable identifier within a script – for instance, an LLM session id shared between an agent, a composer, and a transcript. -
[Automations/Scripting] In automation scripting, a new
|strip_pem_blocks()filter removes PEM blocks like PGP keys and SSL certificates. This is particularly useful when combined with the new search index functionality, or when otherwise sanitizing blocks of text. -
[Automations/Scripting] In automation scripting, a new
|strip_url_querystrings()filter removes the query string from URLs. This is particularly useful when combined with the new search index functionality or when otherwise sanitizing blocks of text. -
[Automations/Scripting] In automation scripting, a new
|strip_data_uris()filter removes base64-encoded content from data URIs. This is particularly useful when combined with the new search index functionality or when otherwise sanitizing blocks of text. -
[Automations/Builder] The 'AI Agent Chat' starter template now generates a chat that drives the editor it sits beside. Before, the wizard made you hand-pick models and gave you a chat that could only talk: it had no way to see the thing you were looking at, no files to work with, and nothing to do about a conversation that outgrew its context window. The wizard now asks where the chat lives – the automation editor, the Data Query or Scripting testers, the Icon Builder, a worklist search bar, or a mail reply – and the generated automation ships the tools for that place, so the agent can act on what's on screen instead of asking you to paste it. Leave it blank and you get a standalone chat, same as before.
It also writes the system prompt, which is why the wizard no longer asks for one: an agent in the Icon Builder needs different orientation than one in the automation editor, and both need to be told what's mounted, so the prompt is composed from the answers you've already given. Filesystems are mounted through a chooser, one row per volume with a read-only or read-write switch, matching the Setup filesystem terminal; the agent gets a writable
/tmpeither way.The model picker is gone – a generated chat falls through to your default model pool, so it runs on whatever models you already have configured, and pinning it is a matter of uncommenting the
agent:line and naming an AI worker, which stays portable between installations where naming a model does not. Long conversations get an exit too:/compactis offered to the agent and to you in the composer, alongside a/flattenexample showing how to add your own. A generated chat isn't reachable from an editor until an agent is pointed at it and enabled on a surface, so the generated script says so in its header. -
[Automations/Records] Added a
records.update:command for updating a batch of records in a single action. It takes arecord_type, a list ofrecord_ids, and thefields:to set, with an optionaldisable_events:toggle. Fields that must be unique – auri, an org or automationname, an addressemail– can't be bulk updated, since the same value can't be unique across many rows, and they're left out of autocomplete suggestions accordingly. A single-ID batch is treated as an ordinary single-record update. Workers must have the appropriate update permission for every target record. -
[Automations/Records] Added a
record.bulkUpdatetrigger for extending bulk update actions with your own. An automation on this event runs once at the start for setup, once for each batch of target records (up to 100 IDs), and once at the end for tear-down. The event listener determines who can see or use these custom actions, and any automationinputs:are displayed as prompts in the bulk update popup. Workers must still have the appropriate update permission for the target records. -
[Automations/Scripting] In automation scripting, a new
|number_prettyfilter formats large numbers in a human-friendly form –{{12345678|number_pretty(1)}}reads as12.3M. Its oneprecisionparameter sets the decimal places and defaults to none. It truncates rather than rounds, so a 32,768-token context window reads as32Kthe way everyone actually says it, and999999can't round up into a nonsensical1000K. -
[Automations/Scripting] In automation scripting, new
|bin2hexand|hex2binfilters convert between a binary string and its hexadecimal representation. Decoded binary – the bytes of a message header, a hash digest, a packed identifier – is unprintable as a raw string and unsafe to slice by character, since a cut can land inside a multi-byte sequence. As hex it's two stable characters per byte, so it can be read, compared, and sliced at any even offset, and|hex2binturns it back. Anything that isn't a string, and any hex value of odd length or with a non-hex character in it, returns nothing rather than raising an error. -
[KATA/Automations] Converting a dictionary to KATA with
|kata_encodenow annotates decimal numbers with@float, so they read back as numbers rather than text. The filter labels each value with the annotation for its type –@intfor a whole number,@boolfor a true or false – but decimals were written bare, so parsing the result returned the digits as a string. A script that encoded1.5and read it back got text it couldn't do arithmetic on without converting first. Whole numbers already survived this round trip and decimals did not.
Queues
-
[Queues/Jobs] Introduced parallel queues with consumer extensions, FIFO ordering, and first-class queue jobs. Added a new
Extension_QueueConsumerextension point with built-in consumers (manual, internal, automation) for routing queue messages. Queue messages can be grouped under ajob_idto track progress against a queue job record with singleton keys, worker ownership, record-level count caches, and arbitrary metadata.
-
[Queues/Automations] Added automation-backed queues. Previously, user-created queues were always manual and each one tediously required an automation timer and loop to drain. Automation-backed queues are automatically managed by the background queue. A batch size can be configured per queue (e.g.
1for expensive operations, and10-100+for batch operations). An automation is invoked with a batch of messages and inputs for the queue and optional queue job. Any non-error response is considered successful delivery. Like event listeners, the most appropriate automation can be selected programmatically based on the event. -
[Automations/Queues] Added the
queue.consumerautomation event to process messages from automation-backed queues. -
[Queues/Scheduler] The 'Background Queue' scheduled task randomly round-robins through batches of available queue messages and jobs. It continues until all work is drained or the allotted time is exhausted. This also catches worker-initiated queue jobs that are no longer being actively monitored.
-
[Queues] Queue message progress reporting now efficiently buffers per-request and drains once at the end. This allows status reports to be simpler and incremental (loop iterations; per-error). Queue job stats are automatically recached when their linked queue messages change, which allows O(1) lookups for the queue job monitor popup without database index aggregation.
-
[Queues/Performance] A licensed installation now sizes its own concurrency pool from Setup » Configure » Queues – a field in the interface, with no configuration file to edit and no restart. There's no ceiling on it: you're sizing Cerb to the machine it runs on, and you're the one paying for that machine. Every installation keeps a floor of three slots; a Community installation stays there, and on Cerb Cloud the platform sets the count because it provisions the compute. Slots are reserved on the MySQL writer connection and released automatically when that connection closes, so an interrupted request can never permanently consume one.
-
[Queues/Performance] The concurrency pool's lane split is now configurable rather than derived from the slot count. Two fields on Setup » Configure » Queues reserve slots for one kind of work – Batch only and Agent turns only, each at least one. Shared is whatever the pool has left after those two, and serves either kind; the form labels it What's left. That's why raising the slot count lands in Shared: the new slots haven't been reserved for anything, so both kinds of work may take them. An Auto button restores the default split, a quarter to each dedicated lane with the rest shared.
This is what stops batch work queuing behind a run of long agent turns: an installation that knows it's batch-heavy or agent-heavy can say so, rather than living with a quarter-of-the-pool formula that can't know. Lane configuration requires a subscription – a Community installation sees the same fields, filled in at three slots divided evenly and locked, with no Save and no Auto.
-
[Queues/Jobs] Queue Jobs are first-class records with cards, profiles, and worklists. Jobs are initiated in the UI by a worker and group together related queue messages (search index, record import). Opening a queue job's card displays its current progress until complete. A 'singleton_key' ensures certain jobs are only run once at a time (search re-indexing).
-
[Queues/Cards] Added a 'Queue Job Monitor' card widget for queue job records.
-
[Queues/Profiles] Added a 'Queue Job Monitor' profile widget for queue job records.
-
[Queues/Jobs/UX] Queue job monitor widgets display linked attachments directly. For instance, a worklist export's file appears on the job widget rather than only on the worklist marquee – workers can choose to view inline or download.
-
[Automations/Queues/Performance] Moved
record.changedevent processing into the parallel background queue. This particularly improves performance on bulk updates and imports. -
[Worklists/Import/Queues] Worklist file imports (CSV/JSONL) now run as a parallel background queue job instead of blocking the request. [#1408]
-
[Queues/Performance] Added a
queue_job_chunktable for queue jobs that run in parallel but need post-processing on completion. For instance, exporting a worklist in parallel (CSV/JSONL/XML) but reassembling the chunks in sorted order at the end and writing a file attachment. -
[Worklists/Export/Queues] Worklist file exports (CSV/JSONL/XML) now run as a parallel background queue job instead of blocking the request. On completion the chunks are sorted and saved as a file attachment linked to the job. The originating worker can close their browser and the export will continue in the background. They will receive a notification when the file is ready. [#1837]
-
[Worklists/Bulk/Queues] Worklist bulk updates now use parallel background queue jobs for improved performance and accountability. This fixes fragility with the older browser-based incremental updates that could abort after a timeout, navigation, or other error. When a bulk update starts, the queue job progress monitor popup opens. If a worker closes their browser or navigates away the update job will continue in the background. They will receive a notification when the job is complete.
-
[Queues/Jobs] Added
cardinalityto queue messages. This represents the work units per message. For instance, each message in therecords.bulk_updateorrecords.exportqueues may represent 100 record IDs. The queue job monitor reports progress in work units rather than raw message counts. -
[Queues/Jobs/Log] Added a permanent log for queue job processing. When a queue message succeeds or fails, a summary message and metadata are stored. For instance, on a worklist bulk update, the metadata contains the affected
record_idsfor an audit trail. -
[Queues/Jobs] The queue job monitor now displays parallel consumer processes. Consumers ramp up to max concurrency as needed, but start with one to reduce contention and let small jobs finish with less network noise. Consumer processes report back on their progress so the monitor can scale them in and out.
-
[Queues/Jobs] Queue jobs can now be canceled to immediately remove pending messages and end the job. Previously, jobs could only be paused or deleted.
-
[Metrics/Queues] Added a new metric
cerb.queue.messages.opengauge to snapshot available and in-flight message counts per queue. This helps identify stalled queues and backpressure. -
[Metrics/Queues] Added a new metric
cerb.queue.messages.processedcounter for done and failed messages per queue and job. -
[Queues] Queues can now configure a retry policy – a maximum number of retries and a retry window – using exponential backoff so each successive failure waits longer. Setting max retries to
0disables retries for that queue and makes failures terminal. For instance, thecerb.storage.migrationsqueue may hit a temporary network error while sending attachments to an S3 storage profile; it will now retry over the following day, while the content continues to be served from its current storage profile in the meantime. -
[Platform/Storage/Queues] Added a
cerb.storage.migrationsqueue covering the whole lifecycle of storage objects – migrations, archival, and deletions. Thecron.storagescheduler job now enqueues storage operations for parallel processing instead of performing them inline and blocking. -
[Platform/Storage/Queues] Storage schema deletions are now handled by the
cerb.storage.migrationsbackground queue rather than thedevblocks_storage_queue_deletetable, and existing rows are migrated to the queue on upgrade. This wasn't used by the 'disk' and 'database' storage engines, but it is by 's3'. -
[Queues] Queues now have a configurable 'claim window' – the amount of time a consumer is granted to process a message it has claimed. If a message stays in flight longer than that, the stalled claim is released and the message is either failed or retried, which handles a consumer that dies unexpectedly instead of leaving work stuck forever.
-
[Queues] Background work now runs in lanes, so a queue of AI agent turns can no longer take every concurrency slot. Slots are split into a fast lane, a slow lane, and a commons that either may use. Work that holds a slot for as long as a model takes to answer – an agent turn, a scheduler job that runs in parallel – draws from the slow lane; work that yields every batch – a bulk update, an import, an export – draws from the fast one. Neither can take the other's dedicated slots, and half the pool stays shared, so an idle install still gives any single job everything it can use. The axis is how long a slot is held, not how important the work is. A three-slot install gets one slot per lane and one shared; below three there's nothing to divide and every slot is shared. Nothing about this is configured – a job names a lane in code and gets back a slot or nothing, and the split follows the pool size.
-
[Queues/Scheduler] The per-job 'runs in parallel up to' setting is gone from Setup » Configure » Scheduler. There's nothing left to tune: a parallel job takes a slot from the slow lane like any other long-running drain, and drains as fast as it can. A concurrent job's row now counts down on a real timer instead of showing a static "continuous" label – though it stays out of the page's next to fire chip, since a job that drains as fast as it can has no cadence to be next on. Run now on a parallel job takes a real slot – so it answers "All concurrency slots are busy. Try again in a moment." rather than starting work that has nowhere to run.
-
[Queues/LLM] A single background worker now runs several agent turns at once, so a queue of waiting turns finishes in about the time of its slowest instead of the sum of all of them. An agent turn spends nearly all of its life waiting on the model provider, so a worker holding one turn sits idle for almost all of it. A worker now carries several turns together, taking the next queued turn as each one finishes, for as long as its admission window is open – and every turn admitted still gets its full time budget, since the window decides whether another turn starts, never whether one finishes. One conversation still advances a single turn at a time across every worker on the install: resuming the same chat in two tabs, on two devices, or from an unattended agent can no longer interleave two replies into it, because a worker that finds a conversation already advancing puts its message back and runs it once the conversation is free, without counting that as a failed attempt. There's no configuration option; the count follows the license.
-
[Setup/Queues] Added a Setup » Configure » Queues page: what the concurrency pool is divided into, and what's running in it right now. Setup » Subscription answers how much capacity you have; this answers what it's doing. The split is drawn one block per slot on two rows, Batch jobs and Agent turns, so you can see which slots each kind of work holds and which ones both may use – and it's drawn from the same component the Subscription page uses, so the two can't come to disagree about the shape of the pool.
Beneath it, a live occupancy row marks the slots that are busy this second. The reading is polled, so the page carries its own countdown ring showing when the next one lands; the ring doubles as the interval picker, the way it does on a workspace worklist tab, and a toggle beside it turns refreshing off entirely. The countdown holds while the tab is hidden rather than draining and firing a burst of catch-up polls when you come back.
A pool whose occupancy can't be read says so, rather than drawing an empty pool – an idle install and a failed read look identical in the numbers, and quietly showing zero would be the most reassuring possible way to be wrong. The page is also where a licensed installation sets the pool size and divides it between the lanes.
Search indexes
-
[Search/Records] Added a Search Index record type. Each index manages a linked custom search filter on any record type. New search index extensions can be implemented with plugins (e.g., TF-IDF, BM25, vector embeddings, Elasticsearch, Qdrant, Pinecone).
Previously, full-text search schemas required backend code and database changes (e.g., email content, comments).
A search index can be created on any combination of fields with a strategy specific to the use case; improving performance and efficiency.
For instance, a "sparse" message-based search index can only include outgoing worker replies from the past year. This has a dramatically smaller search space in large environments and provides near-instantaneous results.
Similarly, a search index can be created for a high-traffic custom field, or an arbitrary combination of fields (title + url + summary).
External resources (docs, KB, FAQ, websites) can be indexed using custom records and made available to workers and AI agents.
-
[Search/Full-Text] Implemented a local full-text search index extension that is optimized for performance and intended to replace MySQL's hard-to-scale FULLTEXT indexes. For each index, a record type, filter query, and content template are configurable. This makes it simple to create custom search indexes on only the content you care about; for instance, a set of custom fields on the past 3 years of tickets. Cerb will automatically manage these constraints to keep the index updated. Each search index adds a new
filter:to worklists of that record type. When a search index has priority zero it overrides the default filter when a search query has no explicit filters. -
[Search/Full-Text/Developers] Technical notes. The new local full-text search index extension splits tokens on spaces and punctuation but allows dashes and dots within terms (e.g., hostnames, email addresses, IPs, ticket masks). This significantly improves search performance because these terms can be matched directly without a phrase search. Tokens are stored as xxHash 64-bit signed integers, so each is a fixed 8 bytes regardless of length. Token hashes are consistent across all Cerb environments. Lookups are O(1). Distinct tokens are linked to record IDs with a precached term frequency for quick TF-IDF/BM25 scoring. Queries sort 'AND' terms by the lowest document frequency first and JOIN in that order to significantly reduce lookups. Since indexes are now managed by Cerb and not MySQL, indexes can be analyzed, pruned, merged, split, sharded, etc.
-
[Search/Records] On all record types, the default search filter is now configurable when no explicit
filter:is given by setting the priority of the search index to zero. For instance, default queries can route to a custom search index for a specific set of custom fields rather than just the record name (e.g., part number lookup). With any other priority, custom search filters will be sorted to the top of query autocompletion suggestions. -
[Search/Full-Text] Added wildcard support to custom search indexes with asterisk (
*). This matches tokens in the vocabulary using OR for that term. For instance:11.1* release*to match11.1.6 releasedand11.1 release.
-
[Search/Full-Text] Added stemming support to custom search indexes with a tilde suffix (
~). This fuzzy matches tokens in the vocabulary using OR. For instance:automate~expands to: automate, automates, automation, automating, automated. -
[Search/Full-Text] Added support for excluded terms in custom search index queries by prefixing a term with a dash (
-). At least one included term is still required. For example,workflow -automationmatches documents that containworkflowwhile excluding any that also containautomation.
-
[Search/Full-Text] Search index filters send additional parameters through to extensions. For instance,
top:10for ranked results. -
[Search/Full-Text] Added support for
top:parameter to limit and rank results by score in full-text search queries. -
[Search/Full-Text] Added support for boosting TF-IDF scores using custom templates in full-text search indexes. This gives more relevancy to document titles, etc.
-
[Search/Scheduler] Updated the
searchscheduler job to handle custom search indexes. -
[Search/Queues] Search index cards now display indexing progress and have a re-index option to launch a background queue job.
-
[Search] Added search facets – named subsets of a record type. Some useful views are a filtered slice of an existing record type rather than a type of their own; AI workers are the obvious case, since they're workers but "Workers" isn't where you'd look for them. A facet gives that slice its own name, icon, and Search menu entry with its filter always applied, and sits beside the real record types rather than nested in a submenu. Each facet keeps its own columns and sort, so customizing it doesn't disturb the parent type's search.
-
[Metrics/Search] Added a
cerb.search.index.recordsgauge metric for counting records over time per search index and engine. -
[Platform/Search] Added on-demand search re-indexing for specific records. A record can be made searchable immediately after it's written rather than waiting for the next scheduler sweep, so a newly created record is findable right away. Bulk writers – ZIP imports, re-indexes, and bundled filesystem syncs – index in batches instead, so a thousand-file import doesn't reindex a thousand times.
Cerb UI
-
[Platform/Cerb UI] Added a modern, reusable Cerb UI component library. This gives Cerb full control of its own interface and makes it much easier for widgets and plugins to add visual elements with consistent styling, light and dark mode support, and interactivity. It also fully replaces jQuery UI, which has been in maintenance-only mode for several years and is removed entirely in this release. Components are plain JavaScript with no bundler and no
node_modules: whenDEVELOPMENT_MODEis enabled the individual scripts load fromcerberusweb.core/resources/js/cerb-ui/for real-time editing, andcomposer build-js(orcomposer dist) concatenates and minifies them into a singlecerb-ui.jsfor production. The first dozen components totaled around 35KB, compared to jQuery UI's 230KB+. -
[UI/Icons] Replaced Glyphicons with a modern, custom Cerb SVG icon set. The icons are monochromatic line icons that inherit the current text color and render cleanly at any size in light or dark mode, and new icons can easily be generated in the same style for any use case. All
glyphicons glyphicons-*CSS classes were replaced withcerb-icons cerb-icon-*versions. The reference set was pared down to the icons actually used, with modern additions likebot. If a sheeticoncolumn specifies an unknown icon name,zapis now used as the default. If you've customized toolbar sections, update them to reference the new icon names.
-
[Cerb UI] Redesigned the ticket conversation view. Messages, drafts, comments, and notes now read as distinct entries with badged author avatars, aligned headers, and hover toolbars.
-
[Platform/Cerb UI] Added a
CerbUI.Dialogcomponent for draggable, resizable, and minimizable floating dialogs. It wraps an existing element and restores it to its original place when destroyed, and its colors are theme tokens so dark mode is automatic. This replaces jQuery UI Dialog. -
[Cerb UI/Dialog] Minimized popup windows now group into a single tray icon in the top right, which stays fixed as the page scrolls. Clicking the tray opens a menu of popup names to restore from, and 'Restore all' fans them out rather than stacking them on top of each other. Previously, minimized popups piled up at the top of the page and had to be shuffled through to find the right one.
-
[Cerb UI/Dialog] When two or more popups are minimized to the tray, its menu offers a 'Close all' shortcut.
-
[Cerb UI/Dialog] Added
Shift+Escto minimize the topmost dialog popup to the tray. -
[Cerb UI/Dialog] In
CerbUI.Dialog, added an opt-incloseWarnOnUnsavedChangesguard that tracks whether form controls were actually modified. Previously the warning fired on close if a popup contained any form controls at all, changed or not. The same guard also catches navigating away from the page entirely, so unsaved work isn't lost to a stray click. Elements or whole subtrees can be excluded with adata-cerb-ui-dialog-no-dirtyattribute. -
[Cerb UI/Confirm] Added
CerbUI.Confirmas the modern replacement forconfirmPopup(), with a locked-down modal design and customizable title, body, buttons, and callbacks. -
[Platform/Cerb UI] Added a
CerbUI.Menucomponent for lightweight cascading menus, replacing jQuery UI for large and deep trees. It parses a nested list once into a data model, renders only the open path into the DOM, and virtualizes any panel past a threshold – a 100,000-item menu stays at roughly 25 DOM nodes. Optional type-to-filter searches every leaf and shows matches with breadcrumb context. -
[Platform/Cerb UI] Added a
CerbUI.Tooltipcomponent for floating, auto-positioning tooltips with DOM-aware auto-hide and screen boundary handling. -
[Cerb UI]
CerbUI.Tooltipsupportspointmode for charts andanchoredmode for tutorials and callouts. Anchored mode accepts jQuery-stylemyandatrelative position strings, such ascenter top. -
[Platform/Cerb UI] Added a
CerbUI.Tabscomponent for lightweight, accessible tab interfaces. Tabs can toggle content already on the page or fetch it on first activation, with a loading spinner, caching, and keyboard navigation. -
[Platform/Cerb UI] Added a
CerbUI.Accordioncomponent for expandable and collapsible sections with keyboard navigation, callbacks, and scrollable panels. This replaces jQuery UI accordions. -
[Cerb UI] Added a
CerbUI.Sidebarcomponent for collapsible vertical navigation rails with sections, icons, and badges. Sidebar options can open internal or external links, fetch content dynamically likeCerbUI.Tabs, or run arbitrary callbacks. Apalettemode variant turns the rail into a library of item templates that can be dragged onto another element or canvas. -
[Cerb UI] Added a
CerbUI.Toolbarcomponent for configurable toolbar strips with buttons, menus, badges, and interaction support. It's the standalone successor to the jQuerycerbToolbar()plugin, and existing worker interactions keep working unchanged. -
[Cerb UI] Added a
CerbUI.SplitPanecomponent: two panes separated by a draggable divider, horizontal or vertical. Sizing is percentage-based, so a split pane built while hidden still renders correctly once revealed and tracks container resizes automatically. -
[Platform/Cerb UI] Added a
CerbUI.Sortablecomponent for drag-and-drop reordering. During a drag it shows both the item's original slot and the current insertion point, so where it will land is unambiguous. -
[Cerb UI] Added a
CerbUI.Draggablecomponent for lifting and dragging elements into designated drop zones. -
[Cerb UI] Added a
CerbUI.Droppablecomponent for defining drop zones that accept draggable elements. -
[Cerb UI] Added a
CerbUI.KataEditorcomponent to replace the Ace.js in-browser code editor for KATA. It provides KATA-aware syntax highlighting, contextual autocompletion, line numbering, gutter markers, code folding with fold-to-depth controls, indentation guides, find and replace onMod+F, comment toggling onMod+/, and clickablecerb:URI markers in the gutter that open the referenced record. Large documents render through viewport virtualization. It also implements the primary Ace API for driving an editor programmatically, which allows much tighter integration with KATA and automations.
-
[Cerb UI/Editors] Introduced
diffGutteracross the Markdown, KATA, and Scripting editors for real-time change tracking against a save checkpoint, with added, modified, and deleted markers, a reset action, and interactive diff popovers. You can see exactly what changed since the last save without leaving the editor. -
[Cerb UI] Added a
CerbUI.JsonEditorcomponent with syntax highlighting and bracket-based code folding for JSON documents, replacing the Ace editor. It also validates JSON as you type and marks errors in the gutter. -
[Cerb UI/ScriptingEditor] Added a
CerbUI.ScriptingEditorcomponent for Cerb's Twig and KataScript template documents – email signatures, HTML and widget templates, and bot scripting – replacing the Ace Twig editors. It highlights embedded{{ }}and{% %}tags, autocompletes scripting commands, functions, and filters, and offers a line-number gutter with markers, find and replace, line move and delete, autosizing, and read-only and single-line modes. -
[Cerb UI/MarkdownEditor] Added a
CerbUI.MarkdownEditorcomponent – a lightweight Markdown prose editor that retires the legacy jQuery UI text editor, toolbar, autocomplete, and inline-image-paste stack used by the mail reply and comment composers. It adds subtle color-only Markdown syntax highlighting, a formatting toolbar, a Markdown/plaintext switcher,@mentionautocompletion with rich avatar rows, inline image paste and upload, and find and replace. -
[Cerb UI] Added a
CerbUI.SearchQuerycomponent for building search queries with syntax highlighting and autocompletion, replacing the Ace editor. It understands Cerb's quick search grammar – plain full text, booleans, filters, quoted literals, lists, and nested searches – and autocompletes against the filter path at the caret, so suggestions stay relevant several levels deep. -
[Cerb UI/Search] Replaced Ace.js in the worklist quick search editor with
CerbUI.SearchQuery. -
[Cerb UI] In
CerbUI.SearchQuery, added colored field-type icons to the autocomplete suggestions menu, so the kind of field being suggested is obvious at a glance. -
[Cerb UI/DataQuery] Added a
CerbUI.DataQuerymulti-line editor component for data queries, with syntax highlighting for both the query syntax and embedded scripting tags, toolbars, and autocompletion. -
[Cerb UI/DiffViewer] Added a
CerbUI.DiffViewercomponent – a side-by-side KATA diff that replaces the ace-diff viewer. Two editor panes show per-line add and remove tints joined by curved connectors, with synchronized scrolling. Long runs of identical lines collapse behind expandable dividers to keep large diffs readable, and the right pane can optionally be made editable so the diff recomputes live as you type. The comparison is computed in the browser, with no server round-trip and no external library. -
[Records/Change History] Revamped the 'Change History' popup in the automation, event listener, and widget editors to use the new
CerbUI.DiffViewerrather than ace-diff. Any two revisions can now be compared, not just a revision against the current version, and a 'Restore this version' action copies the historical document back into the editor.
-
[Cerb UI] Added
CerbUI.SyntaxHighlightfor lightweight, read-only code highlighting with reusable KATA and JSON tokenizers. The server emits raw code carrying a language marker, so the output stays portable for API consumers and non-Cerb UI portals while the browser adds the color. -
[Cerb UI] Added a
CerbUI.RecordChoosercomponent for server-backed single and multiple record selection, with ACL-filtered autocompletion, lazy-loaded avatars, an inline create button for adding a missing record without leaving the form, and integration with the existing chooser popups. Selections render as chips that open the record's card on click. -
[Cerb UI] Added a
CerbUI.ContextChoosercomponent for multi-record-type selection. A chip head at the front switches the record type on the fly and autocompletes within it, and selections can span types – an 'Owner' chooser can move between global, role, group, and worker without a separate control for each. -
[Cerb UI/TextChooser] Added a
CerbUI.TextChoosercomponent – a plain text input with optional endpoint-backed suggestions. It's the freeform sibling ofRecordChooserandContextChooser: accepting a suggestion just writes text, and any typed value is always allowed. -
[Cerb UI/ValuePicker] Added a
CerbUI.ValuePickercomponent for small, local option sets, replacing tall page-scrolling 'Multiple Checkbox' lists. Selections show as tags in the options' defined order, and a filterable dropdown lists every option with the picked ones dimmed and checked. Values post under the original field names, so existing save handlers are unchanged. -
[Cerb UI/TagInput] Added a
CerbUI.TagInputcomponent – a freeform tag field that replaces tall 'List' fields of repeating text inputs. Type and pressEnter(or comma) to turn text into a removable chip, paste to add several at once by splitting on newlines, tabs, or commas, pressBackspaceon an empty input to remove the last tag, and double-click a chip to rename it in place. -
[Platform/Cerb UI] Added a
CerbUI.SelectMenucomponent that enhances native<select>elements with searchable, styled dropdowns. -
[Platform/Cerb UI] Added a
CerbUI.Datepickercomponent for populating date inputs from a calendar. It also provides a free-text field that accepts natural language, parsed on the server – sonext tuesdayor+3 daysworks as well as a date. -
[Cerb UI]
CerbUI.DatePickercan display indicator pips on the calendar for arbitrary datasets, so days that already have activity stand out before you pick one. -
[Cerb UI] Added a
CerbUI.ColorPickercomponent for Photoshop-style color selection with palette presets, hue, saturation, and opacity. This replaces the miniColors jQuery plugin. -
[Cerb UI/IconPicker] Added a
CerbUI.IconPickercomponent – a zero-dependency icon picker that turns a text input into a "well" button opening a floating, filterable grid of every Cerb icon, with full keyboard navigation and type-to-filter. -
[Cerb UI/ImageEditor] Added a
CerbUI.ImageEditorcomponent – an editable image "well" that opens a canvas editor in a dialog. Pan and zoom an image under a fixed-aspect crop frame (rectangular or circular), fill a background color, tint, erase, or pick a Cerb icon, and pull image bytes from the configured profile image toolbar as text or a monogram, an upload, a remote URL, or an icon. Edits are non-destructive – pan, zoom, and crop only rasterize on save. -
[Cerb UI] Replaced the avatar chooser popup with the inline image editor. Profile pictures are now cropped and positioned in place instead of in a separate dialog.
-
[Cerb UI/FileUpload] Added a
CerbUI.FileUploadcomponent for inline drag-and-drop and click-to-browse file uploads with no popup. Files upload as you add them and show per-file progress, with validation for accepted types, maximum size, and file count. -
[Cerb UI/Slider] Added a
CerbUI.Slidercomponent, succeeding the legacy jQuery UI slider. It supports minimum, maximum, and step bounds, optional midpoint delta coloring, keyboard control, and a value tooltip on hover, focus, and drag. Positioning is percentage-based, so it renders correctly even when initialized while hidden – in a bulk update popup or a custom field row, for instance. -
[Cerb UI] Added a
CerbUI.PriorityPickercomponent for multi-selecting items and ordering them by priority with drag-and-drop. Selection and ordering are independent, so an overall priority can be set once and individual items toggled on and off without losing the order. -
[Cerb UI] Added a
CerbUI.Avatarcomponent for monogram avatars and avatar stacks, generated in the browser with hash-locked colors so the same record always gets the same color. It supports image placeholders, configurable sizes, status badges, and non-square aspect ratios. -
[Cerb UI] Added a
CerbUI.NodeEditorcomponent – a visual node-based canvas with extensible block types, inlets and outlets, hierarchical data types, draggable edge connections, blackboard variables, a scripting expression builder, a drag-to-add palette, zoom and pan, a minimap, JSON import and export, and a read-only mode. Dragging from an outlet onto empty canvas opens a picker that creates and connects a new node in one step.
-
[Cerb UI] Introduced
CerbUI.AgentPromptfor composing an agent turn, with@mentionautocompletion, image paste, and a model picker. It backs the agentPrompt form element in worker interactions. -
[Cerb UI/Agent Transcript] Introduced
CerbUI.AgentTranscriptfor reading agent sessions. Turns read like ticket messages: a large author avatar, a sender and meta header, token chips, a hover-revealed action toolbar, and an indented sub-thread of thinking and tool-call bubbles. -
[Cerb UI] Introduced
CerbUI.AgentPane, a collapsible agent chat that sits beside an editor. It's available in the automation editor, the Bot Scripting tester, the Data Query tester, the Icon Builder, and mail Reply.
-
[Platform/Cerb UI] Added a
CerbUI.Switchercomponent for segmented toggles, withlocalStoragepersistence so a chosen segment survives a page reload, and programmatic selection. -
[Platform/Cerb UI] Added a
CerbUI.Togglecomponent for styled on/off switches with programmatic control and event callbacks. -
[Platform/Cerb UI] Added a
CerbUI.TimeRingcomponent for rendering SVG countdown and progress rings with customizable labels and dynamic updates. -
[Platform/Cerb UI] Added a
CerbUI.Spinnercomponent for reusable, pure-CSS animated SVG spinners, and integrated it into the header. -
[Cerb UI] Added new
CerbUI.Spinnervariants: arc, dots, and spark. -
[Cerb UI] Added a
CerbUI.datemodule of date and time formatters:remain()for countdowns (45s,2:05,20h,1d),ago()for elapsed time (just now,3m ago,2h ago), andstrftime()for lightweight formatting with GNU-style patterns. -
[Cerb UI] Added a
CerbUI.nummodule for number formatting in chips, legends, and stat values – compact counts (1500renders as1.5k), digit grouping, percentages, and humanized durations. -
[Cerb UI/Async] Added
CerbUI.utils.apply,CerbUI.utils.series, andCerbUI.utils.parallelLimitfor client-side async flow control – building task lists, running them strictly one at a time, or running them with a cap on how many are in flight at once. -
[Cerb UI/Effects] Added
CerbUI.effects– small one-shot animation helpers (flash,transfer,pulse) that replace the cosmetic jQuery UI.effect('highlight'|'transfer'|'pulsate')API with CSS animations. Each isprefers-reduced-motionaware: it skips the animation and runs theonEndcallback synchronously, and arms a timeout fallback so a dropped animation event never strands the callback. -
[Cerb UI] Added
cerb-ui-formstyles for consistent form layouts with minimal markup. -
[Cerb UI] Added
cerb-ui-kbdstyles for rendering keyboard key caps. -
[Cerb UI/Separator] In Cerb UI separators, added optional arrows on the start and end lines, plus a
--thickvariant. -
[Cerb UI/Icons] Added
cerb-u-anim-*andcerb-u-anim-*-hovericon animation utilities, so an icon can animate continuously or only on hover. -
[Cerb UI/Icons] Added animated "magic gradient" styles for icons, including sweep, aurora, and cycle effects.
-
[CerbUI/Icons] Updated several editor toolbars for consistency, replacing the 'tags' icon with 'placeholders'.
-
[Cerb UI/Agents]
CerbUI.AgentPanecan float its chat in a dialog instead of splitting its host. A pane assumed its host was a big rectangle worth splitting down the middle, which rules out anything one line tall and anything already as wide as it wants to be – whole classes of host could never have an agent no matter how much they'd benefit from one. Float mode leaves the host completely untouched and puts the chat in a non-modal, draggable, resizable, minimizable dialog. Non-modal is the point: you keep working underneath it rather than dismissing the chat to do anything. Minimizable is the other half, since it docks into the shared dialog tray and a long conversation survives being set aside. Split mode is unchanged and remains the default.
-
[Cerb UI/Search]
CerbUI.SearchQuerycan mount an agent pane on any search field, with a built-in command bridge covering the three things an agent needs from one: read the current query, write a replacement, and run it. A host can override any of the three and extend the vocabulary without restating the rest. To make room on the right, the manual-suggestions trigger moves to the left magnifier – the affordance nearest the thing it acts on – across every search field, including record choosers and query prompts.Ctrl/Cmd+Spaceis unchanged. -
[Cerb UI/Icons] Added brand icons for more LLM providers:
logo-azure,logo-deepseek,logo-meta,logo-mistral,logo-moonshot, andlogo-xai. -
[Cerb UI/Rating] Added a
CerbUI.Ratingcomponent for ordinal ratings, drawn as a row of repeated glyphs filled from the left up to the selected value. It's documented with a live example under Setup » Developers » UI Reference » Rating. The host owns the glyph and the scale and the component only paints state and reports the value, so any icon works and the same control reads as stars, brains, locks, or coins depending on what's being rated. Hovering previews the value you would set – repainting the glyphs and live-updating the tier label beside them – and moving off the control restores both to what's stored. Clicking the glyph at the current value clears back to unrated, which is what keeps a mis-click fixable without the host adding a separate Clear affordance; clicking a lower filled glyph sets that lower value instead. A text label beside the row always names the current tier, readingUnratedat zero, so a reader tellsno-trainingfromzdrwithout counting glyphs. Values are compared, never counted: a scale can be a plain 1-5 or sparse (10/20/30/40) so a tier can be inserted later without rewriting stored rows, and the component behaves identically either way. -
[Cerb UI/Meter] Added a
cerb-ui-metercomponent that reports an ordinal level as a row of small filled blocks, with an entry under Setup » Developers » UI Reference » Meter. It's markup and CSS with no JavaScript to construct, which is what lets a worklist template, the agent prompt's model picker, and a record card all draw one without instantiating anything. A meter is the read-only twin of the rating control – the same 1-of-N read, sized for a worklist cell, a menu row, or a property grid. Its two neighbors cover the other cases: a rating when the value should be editable, and a distribution bar when the segments are proportions of a whole at continuous widths. Profile and card property grids can draw a field as a meter through a newmeterfield type. An unrated meter keeps all of its blocks and fills none rather than rendering nothing, because a missing meter leaves a hole and breaks the comparison down a column, where a row of gray reads as "not rated". -
[Cerb UI/RecordChooser/ValuePicker] A
CerbUI.RecordChooserorCerbUI.ValuePickercan now show values it doesn't own – an inherited set, a grant that comes from somewhere else – as faded "ghost" tiles sitting alongside your own selections. A ghost is the same tile as the ones beside it, just faded and without a remove button: it isn't yours to remove, and it posts nothing, so the field still submits only what you picked. What it answers is "what do I already have here?", in the one place you're looking while you decide what to add. That question used to be answered by a placeholder, which only works while a field is empty – it disappears the moment you add anything, which is exactly when you most need to see what you're adding to. A ghosted value is also dropped from the field's own menu, so you can't add the same thing twice. Ghosts are managed by the page rather than by the field, because what they stand for lives somewhere else on the form, so a field can follow an edit made elsewhere without being rebuilt. Both components are documented in Setup » Developers » UI Reference with a live example each: two fields side by side where the left one grants and the right one adds to it.
Charts and dashboards
-
[Cerb UI/Charts] Added
CerbUI.ChartandCerbUI.CartesianChartcomponents for shared-axis charts, with series plotted as bars, lines, splines, and areas. Series can be stacked into arbitrary groupings, the x-axis supports either categories or a continuous time or linear range and can be rotated to horizontal, and a second y-axis is supported. Legends, tooltips, ticks, axis labels, scaling, and events are all managed automatically. Clicking a mark drills through to a Cerb search, and every chart in the family shares that behavior.
-
[Cerb UI/Charts] Added a
CerbUI.PieChartcomponent for pie and donut visualizations. Slices and legend rows share one color scale keyed by label, so the two always match, and hovering shows the value with its percentage share. -
[Cerb UI/Charts] Added a
CerbUI.ScatterChartcomponent for XY scatterplots with independent series on two continuous axes, including legend support, hover tooltips, and customizable interaction handlers. -
[Cerb UI/Charts] Added a
CerbUI.Gaugecomponent for single-value radial gauges with customizable thresholds that recolor the fill, plus a formatted center value and label. -
[Cerb UI/Charts] Added a
CerbUI.Timeblockscomponent for GitHub-style activity heatmaps, with rows as calendar days and columns as hours, hover tooltips, click events, and customizable color scaling.
-
[Cerb UI/Map] Added a
CerbUI.Mapcomponent for interactive SVG region and point maps with customizable projections, choropleth coloring, points of interest, and pan and zoom. It reads GeoJSON and TopoJSON and does its own decoding, projection, and rendering with no external library – the Web Mercator and AlbersUsa projection math is validated against D3's equivalents. Projections also pick a sensible default scale now, so choosingalbersUsano longer renders the map too small to read until you find a workable scale by trial and error.
-
[Platform/Cerb UI] Added a
CerbUI.Sparkchartcomponent for compact, multi-series categorical charts of bars and lines, with independent scaling per series, tooltips, and event publishing. Bars sharing a stack key stack cumulatively, and lines sharing a scale group share one range, so a min/avg/max band nests instead of each line filling the full height on its own scale. -
[Platform/Cerb UI] Added stacked bar charts to
CerbUI.Sparkchart– for instance, counting successes in green and failures in red as proportions of the same bar. -
[Platform/Cerb UI] Added a
CerbUI.Distbarcomponent for horizontal stacked bars with segment-based legends and palette support. Zero-valued segments are hidden from the bar rather than rendering as a sliver, and can optionally be omitted from the legend as well. -
[Platform/Cerb UI] Added a
CerbUI.Legendcomponent for color-matched keys with dynamic data binding, palette support, and integration withDistbar. Swatches render as bars or lines in horizontal or vertical layouts. -
[Platform/Cerb UI] Added shared color palettes and an ordinal color scale to Cerb UI.
CerbUI.ColorScaleassigns colors by hash, so separate components – a sparkchart, a distbar, and a legend – synchronize on the same color for the same series without being wired together. -
[Cerb UI/QR Code] Added a
CerbUI.QrCodecomponent for generating and rendering self-contained, scalable QR codes as SVGs with no external dependencies. -
[Cerb UI/Calendar] Added a
CerbUI.Calendarcomponent for interactive, multi-view calendars with support for event sources and custom interactions. A switcher toggles between daily, weekly, monthly, and yearly views, multi-day events render as a spanning strip, and clicking a day creates a new event. -
[Cards/Widgets/Calendar] Added a configurable 'Calendar' card widget with multi-view displays and linked calendars.
-
[Cards/Widgets/Charts] Added a 'Chart: KATA' card widget type, bringing the full Chart KATA visualizations to record cards. This was previously only available on workspace widgets.
-
[Profiles/Widgets/Charts] Added a 'Chart: KATA' profile widget type, bringing the full Chart KATA visualizations to record profile tabs. The widget resolves
record_placeholders against the profile's record, renders in light or dark mode per the viewer's preference, and offers the same dataset and chart KATA autocompletion and live preview testers in its config as the other Chart KATA widgets. -
[Metrics/Cards] Added a 'Chart: Metrics Explorer' card widget – an interactive, client-side metric chart builder for metric and other record cards. Build and live-edit multiple series (metric, aggregate function, dimension filters with is and not, per-series line, bar, or area type, left or right axis, stacking, color, and visibility) over an adjustable range and period, then export the result as a
metrics.timeseriesdata query, an importable 'Chart: KATA' widget, or an explorer config.
-
[Cerb UI/Charts] Added
CerbUI.Gantt, a chart of named rows of spans across a shared numeric or time axis. A row is a label plus any number of spans, and rows covering the same range overlap on screen. That overlap is the point: a region two rows share is drawn as the place they meet, rather than invented as a third series – which would imply a third kind of thing where there are only two.Spans are half-open
[start, end), so a job running 09:00 to 10:00 doesn't overlap one starting at 10:00. Settingstepmakes the axis discrete units of that size and the span end inclusive, so slots[1,6]cover six cells rather than five; it also governs the tick placement, which otherwise falls between units and labels a position that doesn't exist – on a three-slot pool the generic tick generator offers a slot 1.5. A time axis picks its own tick step, from one second to one week, and aligns from local midnight rather than from the epoch, which lands on the hour only in whole-hour zones and would label every tick at :30 for anyone on a half-hour offset.It's documented under Setup » Developers » UI Reference » Gantt with three live examples – a discrete stepped axis, a segmented one, and a time axis – and it's what the new Setup » Configure » Queues page draws the concurrency pool with.
Tasks and projects
-
[Daily Task Boards] Added a Daily Task Board workspace tab – a personal, day-oriented planning board built on real task records and task projects. Unlike project boards, nothing board-specific is stored: every column and day placement is derived from a task's status, active flag, importance, and completion or wake dates, so the board and the underlying tasks can never disagree.
The board is a horizontal timeline of days, newest first. Today is fully interactive with TODO, In Progress, and Done columns, while the previous six days are a read-only completion log. A board-spanning Stash holds waiting tasks grouped by wake date, and the scheduler revives a stashed task into today's TODO once its "stashed until" date passes.
Cards support inline title editing, drag-and-drop between columns and days, manual ranking in the TODO lane by importance, quick-add, delete, and moving a task to another project. A multi-select column action bulk-moves tasks between columns or projects, and a card's meta strip shows the assignee avatar with an "assign to me" claim. 'Jump to Date' renders any off-window day on demand, with month pips marking which days carry completed or waiting tasks.
Quick Triage deals a lane out as a stack of cards and sorts it one keystroke at a time: raise or lower a task's importance by one – or by five holding
Shift– move it to another column, stash it, schedule it for a date typed in plain language, skip it, or delete it. A progress meter tracks what's left, and a shuffle re-deals the remainder. It's offered on the TODO column and the Stash, the two places where a pile actually needs sorting.
Each board has a shared configuration – which task projects appear, their accent colors, and their order – stored on the tab, while every worker overlays a personal selection, order, and "focus my tasks" preference. A task's permissions derive from its project, and archived or unreadable projects are dropped from the board gracefully.
-
[Tasks/Projects] Added a 'Task Project' record type for grouping related tasks together and sharing ownership. Tasks gain a project and an active flag, and a project's permissions carry through to the tasks inside it.
-
[Tasks/Projects] In task project worklists, added a 'Tasks' distribution bar column showing how a project's tasks are split between done, stashed, todo, and in-progress – so a project that has quietly stalled is visible without opening it.
-
[Tasks/Projects] In task project worklists, added a
tasks:deep search filter. This matches any filter on the tasks within a project, so a project can be found by the properties of its tasks. -
[Tasks] The
titlefield on task records may now contain 4-byte emoji characters. -
[Workspaces/Packages] Added a "Daily Task Board" package to the workspace tab library. It sits alongside Dashboard and World Clocks when you add a tab to a workspace page. The package prompts for a tab name and, optionally, the task projects to show – pick them there and the board comes up ready to use, with an accent color assigned to each. Skip the prompt and the board opens to its setup note pointing at the Projects picker, which is also where you change the selection, the order, and the colors later.
Setup and developers
-
[Setup/Records] In Setup » Records there is now a full reference for every record type and field, including their API keys and search filters. Customization through custom records and custom fields is surfaced here rather than buried in the global 'Search' menu. The old 'Custom Fields' and 'Snippets' items were removed from the 'Records' menu since both are better reached elsewhere.
-
[Setup/Records] A custom "Record Link" field now names the record type it points at, and a "Picklist" lists its valid values. The schema reported a custom field's label and type, and for those two types that isn't enough to use the field: "Record Link" tells you the shape but not what it points at, and "Picklist" tells you there's a fixed set of values without saying what they are – so anyone reading the schema to write a search query or set a field, a person or an agent, had to go dig through the field's own settings, or guess. A Record Link now reads "Links to
ticketrecords", naming the target by its alias, which is the same name a search query or arecord.createwould use, and a Picklist reads "One of:" followed by its values, capped with a count of what was left off when the list is very long. Both show in Setup » Records and in an agent'scerb recordsoutput. -
[Setup/Automations] A new Setup » Developers » Automation Events overview shows the sequence of every event flow – "On new inbound mail", for instance – as a node graph of each automation event and legacy behavior event triggered along the way. Double-clicking an event, listener, automation, or bot behavior opens it for editing. This makes it far easier to answer "what actually runs when mail arrives, and in what order?"
-
[Setup/Toolbars] Added an overview of every customizable toolbar in Setup » Developers » Toolbars, listing sections by toolbar and by managing workflow. Clicking a toolbar item opens its associated automation or behavior editor.
-
[Platform/Cerb UI/Developers] Added a 'UI Reference' section to Setup » Developers showcasing the full Cerb UI component library with interactive examples, variants, code snippets, and documentation, navigable from a sidebar.
-
[Setup/Developers] Added an Icon Builder for authoring
cerb-iconsglyphs. Draw the SVG geometry and see the glyph rendered everywhere it appears in the interface, so a new icon can be checked in context before it ships. It has an agent pane too, so an agent can look up how a comparable icon is drawn and hand back a new one.
-
[Setup/Developers] Added a visual Sheet Builder. Sheet layouts and columns are built against a sample dataset with a live preview, instead of writing the KATA blind and reloading to see the result. It's also available from the Form Builder's sheet element, where it loads the existing sheet and writes your changes back.
-
[Setup/Developers] Added an Agent Filesystem Terminal for browsing and editing agent volumes with the same commands an agent uses, so what a worker sees and what an agent sees can't drift apart.
-
[Setup/Developers] Added a Schema KATA export to the Database Schema page. Previously the page could tell you the database had drifted, but only as a table of columns to read by eye – getting the schema back out meant transcribing it. The export opens the live schema as copyable text, showing the differences alone: tables the reference doesn't have appear whole, and tables that merely drifted are narrowed to just the changed columns, so what you paste is the delta. Toggling it off gives every table.
-
[Setup/Developers] Reworked the LLM Agent Transcripts browser. Transcripts are listed in a sidebar with Active and Archived filtering and per-transcript actions.
-
[Setup/Developers] An LLM transcript can be forked onto another provider, or switched to one mid-conversation, so the same conversation can be replayed on a different model for comparison.
-
[Setup/Developers] An LLM agent transcript now shows the filesystem volumes its session mounted. Until now a transcript told you the agent had a filesystem tool but never which volumes it reached – the Tools panel listed
agent_terminaland that was the whole story, so two sessions with completely different volumes read identically. A Filesystem Mounts panel now sits directly below Tools, naming each mountpoint, the volume behind it with a link to the record, that volume's file count and size, and whether the agent had read-only or read-write access./tmpis listed too, since an agent reads and writes it like any other mount – as per-run scratch rather than a stored volume, it's the one row with no record to link and no file count. A mount whose volume has since been deleted or disabled is called out as Missing or Disabled rather than left off the list: those silently don't mount at run time, so a session can have had fewer volumes than its automation asked for, and this panel is the only place that says so.
-
[Setup/Developers/Icons] In the 'Icon' component of Setup » Developers » UI Reference, icons can be filtered by name, a 'Show labels' toggle switches between a compact grid and named icons, and clicking an icon copies its HTML fragment to the clipboard.
-
[Scheduler/Setup] Completely redesigned the Setup » Configure » Scheduler page with Cerb UI components. Deployment options are separated into 'Development' and 'Production': the Development section has an 'Auto-Run' option that runs jobs entirely in the browser, with a countdown ring showing the next interval, and the Production section carries an example
/cronrequest using service tokens. Per-job metrics for runs and duration are displayed with a range toggle, and the 'edit' and 'run now' actions open in a popup.
-
[Setup/Storage] Redesigned Setup » Storage to better visualize storage lifecycles and usage. The active and archive storage profile for each storage schema are clearly marked, and a distribution bar makes it easy to compare the number or size of objects stored.
-
[Setup/Storage] In Setup » Storage, a new button starts a migration of objects between storage profiles. Previously objects only moved from the active to the archive profile via the
cron.storagescheduler job, with no way to consolidate fragmented storage or to move long-term storage such as S3 back to local. Migrations run as parallel background queue jobs. -
[Setup/Avatars] Removed the avatar configuration page in Setup. It previously toggled between default monograms and silhouettes for contact and worker records; monograms with locked colors are now always used, since they can be generated in the browser.
-
[Setup/Storage] In Setup » Storage, removed the 'Objects' page. It was exactly the same as Search » Attachments.
-
[Setup/Records] In Setup » Records, removed the 'Snippets' page. It was exactly the same as Search » Snippets.
-
[Setup/AI] Setup has a new AI menu gathering the records an installation's AI is built from: Agents, Models, Files, Filesystems, Tools, Surfaces, and Transcripts. The first six open as tabs on a single page, so moving between the agents you've defined and the models, files, filesystems, and tools they're composed from doesn't mean leaving the page. Transcripts is the seventh menu entry but its own page, not a tab.
Agents is a worker worklist locked to AI accounts, reading its filter from the same manifest the global search menu's Agents entry uses, so the two can't drift into meaning different subsets.
Surfaces is a reference rather than a worklist – one card per place an agent can run, listing what that surface hands an agent: the tools it contributes and their parameters, any values pinned on a tool that the model never sees and can't override, the reading it points the agent at – and only when the volume holding it is actually mounted – and the orientation prompt it supplies. It reads from the surface catalog itself, so it's never a second, hand-written description of the same facts.
The tab set remembers where you left it, so it reopens on the tab you were last using rather than back at Agents – which is the first tab, not a home the page returns to. The page is a tab set rather than a row of menu entries because AI settings will get a Configure tab in front of these when there are any.
Worklists
-
[Worklists/UX] When customizing a worklist, configuring which columns are visible has been drastically improved. A searchable menu of available fields is displayed, segmented by custom fieldset, and selecting a field dims it in the menu but leaves it in place so you keep your bearings. Icon type hints give a visual cue for each field's data type, and the selected fields sort in a single column. Previously every available field was displayed in one giant grid and reordering was unintuitive.
-
[Worklists/Sparklines] Added generic 'Sparklines' and 'Distbar' columns to worklists, along with parameterized metrics filters. Sparkline columns show a mini-chart of recent activity per row with a 2h/1d/30d range toggle in the column header, and load asynchronously per page only when the column is visible, so they cost nothing on worklists where they aren't shown. Because the underlying metrics are queryable, each one also brings a matching quick search filter that accepts
since:anduntil:– the sections below list what each record type exposes.
-
[Worklists/Automations] Added a 'Usage' sparklines column to automation worklists showing runs, errors, and duration, plus a
usage:quick search filter backed by thecerb.automation.invocationsandcerb.automation.durationmetrics. For instance,usage:(runs:>0 since:today)returns automations that ran at least once today, andusage:(runs:>1000)returns those that have run more than a thousand times. -
[Worklists/Automations/Events] Added a 'Usage' sparklines column and a
usage:quick search filter to automation event worklists, using the same runs, errors, and duration series – which makes it easy to spot an event that started failing or slowed down. -
[Worklists/Bots/Behaviors] Added a 'Usage' sparklines column and a
usage:quick search filter to bot behavior worklists, showing runs and duration from thecerb.behavior.invocationsandcerb.behavior.durationmetrics. -
[Worklists/Mailboxes] Added a 'Usage' sparklines column and a
usage:quick search filter to mailbox worklists, showing messages received and protocol errors. For instance,usage:(errors:>0)returns mailboxes that have ever had an error, andusage:(received:>0 since:today)returns those that pulled mail today. -
[Worklists/Mail Routing Rules] Added a 'Usage' sparklines column and a
usage:quick search filter to mail routing rule worklists, which makes it obvious at a glance which rules are actually matching mail and which have gone stale. -
[Worklists/Mail Transports] Added a 'Usage' sparklines column and a
usage:quick search filter to mail transport worklists, showing deliveries and failures. -
[Worklists/Queues] Added an 'Activity' sparklines column and an
activity:quick search filter to queue worklists, showing done, failed, and open message counts so a backing-up queue is visible without opening it. -
[Worklists/Search Index] Added a 'Records' sparklines column and a
records:quick search filter to search index worklists, tracking indexed record counts over time. -
[Worklists/Service Tokens] Added a 'Usage' sparklines column and a
usage:quick search filter to service token worklists, tracking authentications – which surfaces both unused tokens and unexpected spikes. -
[Worklists/Snippets] Added a 'Usage' sparklines column and a
usage:quick search filter to snippet worklists, tracking how often each snippet is used. -
[Worklists/Webhooks] Added a 'Usage' sparklines column and a
usage:quick search filter to webhook listener worklists, tracking invocations. - [Worklists/Metrics] Added a 'Dataset' sparklines column to metric worklists displaying min, max, average, sum, and count. It adapts automatically to counters versus gauges, so each metric is charted the way it's meant to be read.
-
[Worklists/Import] Records can be imported into worklists using JSONL files. Previously this only supported CSV.
-
[Worklists/Import/UX] When importing CSV/JSONL files into worklists, the import field mapping is now saved as a per-worker preference for that record type, file format, and column schema. This saves a lot of tedious remapping for routine file imports.
-
[Worklists/Search] Added a
comments:worklist filter for every record type that supports comments. Previously this was only available on about a dozen of the nearly 80 record types. The filter is now backed by the new search index record type. -
[Worklists/Sessions/Subtotals] Added subtotals to the user sessions worklist: worker, ip address, and user agent.
-
[Worklists/Bulk/Comments] Added bulk commenting to the worklist 'Bulk Update' popup for every record type that supports comments (tickets, tasks, organizations, opportunities, time tracking, domains, servers, calls). [#1602]
- [Worklists/Comments] Added bulk delete to comment worklists. Admins can delete any comment; non-admins are limited to their own via per-record ACL. [#857]
Metrics
-
[Metrics] A retention policy can now be configured per metric. This applies to the 1-day sample aggregations, which are retained forever by default. The 5-min samples are still retained for 24 hours, and 1-hour samples for 2 weeks. This is particularly useful for metrics with high dimensionality and low long-term value, like rate-limiters. [#1554]
-
[Data Queries/Metrics] Added a
metrics.subtotalsdata query type for flat metric subtotals read directly from samples. This is the inverse projection ofmetrics.timeseries– instead of values over time it returns the raw dimension values, which can then be composed into other queries such as subquery worklist filters. -
[Data Queries/Metrics] In
metrics.timeseriesdata queries, added support for negating dimension filters – for instance, on thecerb.tickets.openmetric, every ticket sample wheregroup_id != 1. This makes proportional stacked bar charts straightforward: one dimension versus all the others. -
[Data Queries/Tickets] In
worklist.subtotalsdata queries,of:ticketnow supportsby:sender.firstandby:sender.last. Autocompletion had been suggesting these keys, but using one returned an "Unknown by: field" error. -
[Metrics/Mailboxes] Added a
cerb.mail.mailbox.receivedcounter metric tracking downloaded messages per mailbox. -
[Metrics/Mailboxes] Added a
cerb.mail.mailbox.errorscounter metric tracking protocol errors per mailbox, with a status dimension that breaks errors down by status code – invalid authentication, for instance. -
[AI/Agents/Metrics] Every agent turn is now recorded as metrics, so token spend, latency, and provider failures can be charted from a dashboard widget or filtered on in a worklist like any other metric.
Six counters are written. Four count tokens –
cerb.agent.model.tokens.input,.output,.cache_read, and.cache_write– dimensioned by the agent model that ran the turn, the AI worker it ran as, and the worker it ran for, which is what answers how much each model costs, per model, per agent, or per person. The other two,cerb.agent.model.turnsandcerb.agent.model.turns.duration, count turns and accumulate their duration in milliseconds, dimensioned by model, agent, and the provider's response status – so rate limits (429), overloaded providers (529), and endpoints that never answered at all (recorded as0– a DNS failure, a refused connection, a timeout) form their own series beside the successful turns, and dividing duration by turns gives an average response time per model.Duration measures the provider request itself and not time spent waiting in the queue, so a busy queue never reads as a slow model. Interactive and background turns are both counted, as is the compaction pass that summarizes a long conversation, so the totals reconcile with a transcript's own token accounting. Reasoning tokens aren't counted separately, because providers report them as part of the output total and a second series would double-count any bare sum.
Two things go uncounted on purpose: a turn that failed still records its status and latency but not its tokens, since a failed request's usage never comes back; and
llm.chat:and the model connection test have no agent behind them, so they'd have nothing to dimension by. Samples are kept indefinitely unless you set a retention period on the metric.
Workspaces
-
[Workspaces] Added auto-refresh to worklist and widget tabs. A dashboard left open previously showed data from whenever it was last loaded, with nothing indicating how stale it had become. Switch auto-refresh on for a tab and a countdown ring appears showing when the next refresh is due; click the ring to choose an interval of one, five, or fifteen minutes. Worklist and widget tabs offer it; other tab types don't. It stays on for as long as you're on the tab rather than being remembered as a preference, so a tab always opens with it off.
An auto-refreshing page that reloads while you're reading it is worse than a stale one, so the countdown holds rather than drains whenever a refresh would interrupt: while the browser tab is in the background, while a popup is open in front of you, and while the countdown ring itself is scrolled out of view. That last case matters most – it means a refresh only ever lands at the top-of-tab glance view, and never yanks you upward mid-read further down the page. A minimized or docked popup doesn't count as interrupting, so a parked peek won't freeze the timer indefinitely. When the countdown resumes it picks up where it held plus a short grace period, so dismissing a dialog or returning to the tab never triggers a refresh the same instant.
-
[Widgets/Worklists] On profiles and workspaces, 'Worklist' widget configuration simplifies selecting columns from custom fieldsets. Fieldset fields are split into their own sections with an all/none toggle, instead of one long undifferentiated list.
-
[Workspaces/Worklists] When a workspace widget's configuration changes, its worker-level cache is now cleared. This keeps workflow-managed worklists from diverging – previously a worker could keep seeing the old columns or query after the managing workflow had updated the widget.
-
[Workflows] When a workflow manages a record, editing that record from the interface now warns you first. This applies to automations, automation event listeners, custom fields, custom fieldsets, custom records, mail routing rules, and toolbar sections – the record types most likely to be silently overwritten the next time the workflow synchronizes.
-
[Workflows/UX] Revamped the
cerb.quickstartonboarding workflow for 12.0, replacing the checklist approach with a guided setup tour, streamlining step navigation, and improving contextual guidance. The 'Guided Setup' interaction is now reachable from the global menu. -
[Workflows/Tutorial/UX] Updated the built-in tutorial workflow to cover new 12.0 features. Each section gained an icon as a visual cue, the heavy SVG illustrations were removed from a few sections, and the worklist header switched to a neutral color that reads well in both light and dark mode.
-
[Workflows/Config] Workflow templates can now prompt for a search query in their configuration, with a
query/<name>:field that takes arecord_type:and autocompletes that record type's query filter syntax as you fill it in. A value an admin supplies is then written in Cerb's own search vocabulary rather than as free text, which is how a workflow sets a boundary – scoping which agent models an agent workflow may use, for instance. -
[Workflows/Tutorial] Added an 'AI & Agents' tab to the built-in tutorial workspace. The tutorial workspace ships with every installation as a guided tour of what Cerb does, and AI agents are the largest addition in 12.0, so they get a section of their own rather than a paragraph inside Automations. It sits second, right after the welcome page, and starts from the decision everything else follows from: an AI agent is an ordinary worker record with an
is_aiflag, so it owns tickets, is@mentioned, and joins groups like anyone else.From there it walks the pieces behind one – agent model records and where their credentials come from, the capabilities and ratings that let an automation describe the model it needs instead of naming one, the model pools that resolve a search rather than a hand-maintained list, the
llm.chat:,llm.agent:,llm.router:andllm.embed:commands, agent filesystems and how an automation mounts them, the file commands an agent and a worker both use, thecerbcommand line an agent asks Cerb about itself with, the volumes that ship with Cerb, where an agent chat can open, theagentPromptandllmTranscriptform elements that make one visible, and what happens to a conversation that outgrows its context window. Worklists of your own agent models, filesystems, and AI workers sit alongside the prose, so the tab reads against a real installation rather than an example.The rest of the workspace is brought up to date for 12.0 as well: Search covers facets, Worklists the redesigned column picker and remembered import mappings, Workspaces the dashboard auto-refresh, Automations the starter templates, simulated initial state, generated policies, form builder, and batch record updates, and Queues the retry policy and claim window. The welcome page gains a 'Developer Tools' widget listing what lives under Setup » Configure » Developers.
Service tokens
-
[Service Tokens] Added 'service tokens' for anonymous privileged endpoint access (/cron, /debug, /update). This deprecates IP-based allowlists. A service token can be included in an
Authorization: BearerHTTP header or an_authorizationHTTP POST parameter; for instance, from a cronjob or monitoring tool. Tokens can be restricted with scopes to specific endpoints. When viewing a protected endpoint in the browser, a token can be entered to continue. A master service token can be added to the config file usingAPP_SERVICE_TOKEN; which is particularly useful for/updatesince logins would be blocked until the update has finished. Service tokens are managed from Setup » Configure » Security.
-
[Service Tokens/Metrics] Added a
cerb.service.token.usesmetric counter for tracking service token authentications. -
[Security/Service Tokens] When the
APP_SECURITY_ALLOW_ADMIN_SESSION_TOKENconfig option istrue(default), existing admin sessions can be used to authenticate/updateand/cron. Disabling the option always requires a service token.
Scheduler
-
[Scheduler/Background] Added a new
Background Queuescheduler job to process events asynchronously. -
[Scheduler/Parallel] Scheduler jobs can now be flagged as
parallelin their extension manifest (e.g. the Background Queue). These jobs limit by concurrency rather than locking during operation, which enables more real-time background functionality compared to traditional cron jobs. -
[Metrics/Scheduler] Added a new metric
cerb.scheduler.invocationsto count scheduler runs by job. -
[Metrics/Scheduler] Added a new metric
cerb.scheduler.durationto track scheduler execution duration by job.
Security
-
[Security/Workers/MFA] Added backup codes to worker accounts when multifactor authentication (MFA) is enabled.
Plugins
-
[Setup/Plugins/UX] Revamped plugin configuration in Setup » Configure » Plugins. Plugins can now be enabled or disabled in a single click directly from the worklist. Separate worklists are provided for modern features, legacy features, and third-party plugins, and the row icons visually distinguish each category (disabled plugins render in grayscale via CSS rather than the previous dated bitmap decoration, which also applies to disabled worker profile images). The 'Link' column is shown by default for quick access to per-plugin documentation, plugins are sorted alphabetically, and plugin names are rendered at a larger font size. A tip points to workflows as the preferred path for most user-level extensibility.
Miscellaneous
-
[Storage/Profiles/S3] The 'S3' storage profile now accepts a host with an optional
https://orhttp://protocol and a non-standard port. This makes it possible to use S3-compatible object storage like MinIO (in self-hosted, local network, or development environments), Google Cloud Storage, and others rather than requiring Amazon S3.
-
[Connected Accounts/AWS] In 'Amazon Web Services' connected accounts, added an "Allow requests to non-AWS endpoints" option. This permits the credentials to be used with AWS/S3-compatible services like Google Cloud Storage or MinIO. By default, credentials are restricted to AWS services.
-
[Notifications/UX] The unread notifications bell in the top right now occasionally plays a silent 'ring' animation to draw attention to waiting notifications.
-
[Records/Workers] Added a
created_atfield toworkerrecords. -
[Groups/Activity Log] The activity log now records when group members are added or removed. [#246]
-
[Workers/Activity Log] The activity log now records when workers are activated or deactivated. [#824]
-
[Sheets/Code/KATA] Added syntax highlighting for
kataincodecolumns of sheets. -
[Installer/UX] Added default card and profile widgets to several record types that previously lacked them: automation, automation event, automation event listener, automation timer, gpg_private_key, gpg_public_key, queue, resource, search index, service token, toolbar, and toolbar section records.
-
[Records/Icons] Added a default icon to every record type, displayed on card popups and profile pages so a record's type is identifiable at a glance even when it has no profile image.
-
[Records/Widgets/Icons] Added default icons to all card, profile, and workspace widget types. This gives a visual cue in configuration menus and on dashboards, and the icon can be overridden per widget.
-
[Cerb UI] Added icons to package library records. Packages now show a glyph in the library and in choosers instead of a generic placeholder.
-
[Records/Autocomplete] Implemented autocompletion when searching many more record types: connected accounts, custom fieldsets, email signatures, groups, mail templates, mail transports, metrics, organizations, PGP private keys, project boards, queues, roles, task projects, time tracking activities, and workspace pages. Previously most of these required typing an exact match or opening a chooser popup.
-
[Comments/Notifications] When replying to a comment, the original author's
@mentionis prefilled automatically, so a reply notifies the person you're replying to without having to remember to tag them. -
[Bots/Behaviors] Legacy bot behaviors now track a change history the way automations do. From a behavior tree, click the event and choose 'Edit History' to compare any two revisions in the diff viewer.
-
[Data/Records] Added an
only_customfilter to complementexclude_custominrecord.typesdata queries. -
[Connected Services/UI] Added profile images to connected service records. The image is displayed on the login form's SSO buttons and in the connected service profile and worklists.
-
[Connected Services/Packages] Added a "Microsoft Entra ID" package that sets up the OAuth2 connected service Microsoft 365 mailboxes need for SMTP, POP3, and IMAP. Microsoft has been retiring legacy passwords for Office 365 mail, and building that service by hand meant knowing the Entra authorize and token URLs, remembering to include your directory ID in both, and getting the scopes right. Choose the package from the library when adding a connected service and enter the client ID, client secret, and directory ID from your app registration; the scope is prefilled with the recommended set for mail, including the
offline_accessscope that lets Cerb refresh the access token every hour. The package instructions cover the two most common setup mistakes: a callback URL in Entra that doesn't match Cerb exactly, and client secrets quietly expiring after six months. -
[Connected Services/Packages] Added a "Microsoft Foundry" package that sets up an API key connected account for Foundry or Azure OpenAI in one step, rather than creating a connected service with the right token provider and then an account to hold the key. Paste a key from your resource's Keys and Endpoint page and give the account an alias. The instructions cover the part that trips people up afterward: when you add an agent model, the endpoint is your resource's OpenAI base URL and the model is your deployment name rather than the underlying model name.
-
[Knowledgebase/Worklists] Knowledgebase article worklists now support bulk update, so a batch of articles can be recategorized or edited in one action rather than one at a time.
-
[Records/Workers] On worker editors, 'Save & Continue' saves without closing the editor, so a run of small adjustments doesn't cost your place in the tab each time. This makes it much easier to tweak an agent while it's running.
-
[Packages/Library] A package's
imagecan now name the colors its icon is painted in, so a package tile in the library, and in the Add-tab and Add-widget choosers, can match the thing it installs. Alibrary.imageis either an embeddeddata:image or a space-delimited art spec whose first word is a Cerb icon name and whose optional second and third are the background and glyph colors as#rgbor#rrggbb. Writingtodogives the icon on a neutral gray,todo #7f7f7fsets the background, andtodo #7f7f7f #ffffffsets both. Either color can be alight:darkpair, sotodo #eeeeee:#333333paints one color in the light theme and the other in dark, and a color with no pair is used in both. The wordautoholds a slot at its default, which is how you set a foreground without pinning a background:todo auto #d62728. Anything unusable falls back on its own slot's default, so an unrecognized icon name still lets your colors through and a malformed color still lets your icon through. -
[Connected Services/Packages] Added an "OpenRouter" package to the connected services library, so the connected account an OpenRouter agent model authenticates with is one choice from the library rather than a service built by hand.
-
[Records/Roles] A role's profile image can now be set through the records API, packages, and automations. Roles could already be given an image in the role editor; now anything that writes records can set one, so a role's avatar can arrive from a package or a
record.create:rather than only from someone uploading it.imagetakes a base64 PNG data URI exactly as groups and workers do, with the same limits – PNG, between 50x50 and 500x500, and under 1MB. A role also exposes_image_urlnow, so its avatar turns up wherever Cerb draws a record's image: placeholders, mention chips, custom field forms, comments, and role autocomplete entries.
Changed
-
[Setup/Agents] The LLM Agent Transcripts page has moved out of Setup » Developers and into the new Setup » AI menu, where it's listed as Transcripts and titled Agent Transcripts. Reading back what an agent did belongs beside the records that agent is made of, rather than in with the schema browser and the token generator. Nothing about the page itself changed, and its address is unchanged.
-
[Automations/KATA] In KATA, a
type/name:name that becomes a dictionary key must now be a valid variable name – letters, numbers, and underscores only. Previously a name containing a dash silently rendered as0wherever it was read back, because{{a-b}}is the subtractiona - brather than a variable reference. This caused quiet failures like a worklist export column full of zeros, or a snippet placeholder that evaluated to nothing. The check is applied where a name actually lands in a dictionary –inputs:,await:form:elements:, snippet and dashboard prompts, worklist export columns, and LLM toolparameters:– and is available declaratively asnameFormat: variable. The parser still allows the wider character set elsewhere, since most names are labels or handles that are never read back as placeholders. -
[UI/Tabs] Tweaked the style of tab sets throughout the interface.
-
[Cerb UI] Ported popups from jQuery UI dialogs to
CerbUI.Dialog. Every AJAX popup in the interface is now a Cerb component, which is what makes the shared minimize tray, cascading restore, and unsaved-changes guard work consistently everywhere. -
[Cerb UI] Converted record peeks and bulk update forms to Cerb UI. Every edit popup now uses the same field, label, and section structure, so editing a ticket and editing a custom record look and behave alike.
-
[Records/Cards/UI] All record card popups now use the new Cerb UI components and styles. Records without a profile image show a default record type icon, and popup titles include both the record type and the record name – which makes them easy to tell apart in the minimized popup tray.
-
[Cards/Cerb UI] Restyled card editors with Cerb UI components for all record types and card widget types. Every widget type has a default icon based on its type, displayed next to the title as a visual cue, and the icon can be overridden per widget in the editor.
-
[Profiles/Cerb UI] Restyled profile widget editors with Cerb UI components for all record types and profile widget types, with the same per-type default icons and per-widget override.
-
[Cerb UI/Automations] Rebuilt the automation editor on Cerb UI. The script and policy panes are now Cerb's own KATA editor instead of Ace, and the template picker, form builder, and agent chat are reachable for the first time.
-
[Cerb UI] Ported chart widgets to
CerbUI.Chart. Charts now render through Cerb's own components instead of c3.js and d3.js. -
[Cerb UI] Ported calendars to
CerbUI.Calendar. Calendar widgets render in day, week, month, and year views through Cerb's own component. Previously they were month-only, and prev/today/next each cost a full server round-trip; now switching views and moving between periods is instant. A multi-day event renders as a single strip spanning the days it covers – keeping its track as it wraps from week to week – rather than as a separate chip repeated on each day. Several event sources can be shown at once with a legend, the year view marks active days with pips you can drill into, and each widget chooses which view it opens on. The availability calendar widget is unchanged, since it builds its events differently.
-
[Cerb UI] Converted the worker interaction panel to Cerb UI, and made a failed submit recoverable. A dropped request no longer strands the interaction behind a spinner that never clears.
-
[Cerb UI/Search] Refactored the global search menu to use modern Cerb UI components, added record-type icons, and improved keyboard filtering.
-
[Widgets/Fields/Cerb UI] Improved the display and configuration of 'Record Fields' card, profile, and workspace widgets. The 'Fields' tab shows a preview grid matching the actual output, custom fieldsets are collapsible sections with all/none shortcuts, and search buttons and record links use the new badge count toolbar style.
-
[Setup/UI] Redesigned the Setup pages on the new Cerb UI design system, across both the Configure sections (authentication, branding, cache, license, localization, security, team) and the mail and Developers sections.
-
[Platform/Debug] Removed the
DEVELOPMENT_MODE_ALLOW_DEBUGconfig option. This is replaced by service tokens. -
[Platform/Auth] Deprecated the
AUTHORIZED_IPS_DEFAULTSIP allowlist for/cron,/debug, and/update. This is replaced by service tokens, which work regardless of where the request originates. -
[Docker] Updated the Docker examples to use
CERB_SERVICE_TOKENrather than client IP validation. -
[Docker] Updated the Docker examples with automatic configuration from
.env. -
[Docker] Added support for customizable environment and port variables in Docker Compose.
-
[Docker] Bumped the PHP-FPM
pm.max_childrendefault from5to10. This makes it easier to handle parallel queue processing at full capacity. -
[Search/Automations] Converted the automations full-text index to a search index record so it can be fully customized or removed.
-
[Search/Comments] Converted the
comments:filter on record worklists and thenotes:filter on message records into customizable search index records. Previously these were hard-coded MySQL FULLTEXT indexes. -
[Search/Contacts] Converted the contacts full-text index to a search index record so it can be fully customized or removed.
-
[Records/Email Address] On email address record dictionaries, added the
aliaseskey to access that data from functionality like sheets and automations. -
[Search/Knowledgebase] Converted the knowledgebase articles full-text index to a customizable search index record so it can be fully customized or removed.
-
[Search/Mail/Content] Converted the message content full-text index to a customizable search index record. Previously this was a hard-coded MySQL FULLTEXT index, which made it difficult to scale on installations with large amounts of historical message content. The search index extension supports sparse strategies (e.g. only outgoing replies from the past year) for dramatically smaller search spaces.
-
[Search/Mail/Headers] Converted the message email header filters (
header.cc:,header.cerbMailbox:,header.deliveredTo:,header.from:,header.to:,header.mailer:,header.forwardedTo:) into customizable search index records. Each environment can add additional header filters or drop ones they don't use. New installations no longer create them by default. Previously these were indexed in MySQL/InnoDB much less efficiently. -
[Search/Orgs] Converted the organizations full-text index to a search index record so it can be fully customized or removed.
-
[Search/Snippets] Converted the snippets full-text index to a search index record so it can be fully customized or removed.
-
[Search/Tickets] On ticket worklists, the default search filter is now
messages(backed by the new search index for message content). This behaves like the previous full-text default but no longer requires backend code – admins can fully customize what's indexed. -
[Search/Workers] Converted the workers full-text index to a search index record so it can be fully customized or removed.
-
[Mail/Parser/Filters] In the inbound email parser, mail.filter automations now run dependently rather than independently. Previously, a change in one mail filter was not reflected in later mail filters (e.g. changing an email header). The state is now updated between each invoked filter automation.
-
[Records/Contacts] On contact record dictionaries, added the
aliaseskey to access that data from functionality like sheets and automations. -
[Records/Workers] On worker record dictionaries, added the
aliaseskey to access that data from functionality like sheets and automations. -
[Installer/UX] Modernized the style of the web-based installer.
-
[Installer/UX] In the guided installer, the first input on each step is automatically focused to improve usability.
-
[Login/UI/UX] Restyled the login flow to match the modern dark-mode aesthetic.
-
[UI/Styles] Replaced the old bitmap icons on
emailandpasswordtext inputs with modern inline SVGs. -
[Worklists/Search] In worklist search queries, when specifying a
sort:filter, the current column sort is reset before parsing. Previously, an invalid sort column was ignored and the previous sort order was retained without warning. -
[Mail/Compose/UX] Added validation for invalid email addresses in
to,cc, andbccfields when composing a new email message from ticket worklists. -
[Mail/Reply/UX] Added extra validation for invalid email addresses in
to,cc, andbccfields when replying to a ticket. This catches common errors likecustomer@example,com. -
[Automations/Interactions] An agent turn running in the background now makes far fewer requests while you wait for it. Three separate requests watch a running turn – one to advance the queue, one to check whether the turn has landed, and one to refresh the transcript – and together they ran about twice a second for the whole turn whether or not anything was changing. The transcript refresh was the expensive one, rebuilding the entire conversation on the server once a second only to discover it was identical to the previous second. The server now paces the wait itself, stretching its check as a turn runs long and telling the browser when no queue worker is needed because the turn is already being processed elsewhere. The transcript refresh runs at full speed only while an answer is actively being written; while a tool runs, on providers that don't stream, and in a background tab it slows right down, and when nothing has changed the server answers from a single row instead of rebuilding the conversation. Also fixed: a stopped or expired interaction could keep polling for as long as its tab stayed open.
-
[UI/Performance] Optimized the generation of deeply nested placeholder menus.
-
[Maint/Workers/Watchers] Nightly maintenance now clears watcher links on deactivated workers. [#823]
-
[Branding/UI] The user-defined logo and stylesheet are now served from new
/branding/logo,/branding/logo-dark, and/branding/stylesheetendpoints instead of/resource/cerberusweb.core/css/*. This lets CDNs and caching layers treat/resource/*as immutable non-shared static content. [#1135] -
[Records/Custom Fields] 'Text block' custom fields now support 4-byte UTF-8 characters (e.g. emoji). The underlying
custom_field_clobvalue.field_valuecolumn was migrated toutf8mb4with collationutf8mb4_unicode_ci. -
[UI/UX] In dark mode, the
color-schemeCSS property is now set so the browser renders native UI components (scrollbars, form controls, dropdowns) with a matching dark palette. -
[Automations/Scripting] In automation scripting, the
|csvfilter will now attempt to convert objects/dictionaries to arrays. Previously, this always failed if the input wasn't already an array. -
[Automations/Queues] In automations, the
output:parameter ofqueue.push:is now optional. It's only needed when you want to capture the resulting queue message IDs. -
[Storage/S3/Performance] The 'S3' storage engine now uses the batch deletion endpoint to remove up to 1,000 objects in a single request. Previously, this engine inefficiently issued one DELETE request per object.
-
[Security/Storage/AWS] Secrets in 'S3' storage profiles are now stored in encrypted connected accounts rather than in the storage profile configuration. Existing profiles are migrated automatically on upgrade.
-
[LLM/Providers/HuggingFace] The Hugging Face LLM provider now uses the OpenAI-compatible serverless
routerendpoint rather thanapi-inference.
Deprecated
-
[Plugins/Files/Bundles] Moved 'File Bundle' records into an optional
cerb.file_bundlesplugin that is disabled by default. On upgrade, the plugin is enabled automatically only if you actually have file bundle records; otherwise the empty table is dropped and the plugin recreates it if an admin enables it later. Installations that never used file bundles stay clean. -
[Bots/Behaviors/Deprecations] Moved legacy bot behavior functionality into an optional
cerb.behaviors.legacyplugin. This will be retired in 12.0. On upgrade, the plugin is automatically enabled only if active behaviors exist, so installations without legacy behaviors stay clean.
Removed
-
[Portals/Support Center/Templates] Removed custom HTML templates from community portals like the Support Center. These templates constantly drifted and caused problems on every upgrade (for instance, the 12.0 migration from the Glyphicons font to built-in Cerb SVG icons), and they increased the attack surface by relying on the Smarty sandbox. Customization should now be done through the portal stylesheet, theme, and widget configuration. The
APP_OPT_DEPRECATED_PORTAL_CUSTOM_TEMPLATESconfig option was also removed. -
[Worklists] Removed the 'copy' option from legacy worklists, which duplicated a worklist to a new workspace. The modern export/import functionality should be used instead.
-
[Workspaces/UI] Removed the 'Display tabs as' option on workspace pages.
-
[Scheduler/Migrations] Retired the 'cron.migrations' scheduler job since it was only used once in 10.4 (Feb 2022).
-
[Scheduler/Metrics] Retired the standalone 'cron.metrics' scheduler job since this is now handled by
cron.background_queue. -
[Platform/Plugins] Removed the
cerberusweb.plugin.setupextension point and theExtension_PluginSetupparent class. No remaining plugins implemented setup steps, and removing the extra step enables single-click plugin enable/disable from the Setup » Configure » Plugins worklist. The 'uninstall' action was also removed from the GUI – plugin removal is now a filesystem operation. -
[Setup/Search] Removed the 'Search' page from Setup » Configure. Search engines and schemas are now configured directly on search index records, where each index can be independently tuned, paused, and re-indexed.
-
[Platform/Search] Removed the
devblocks.search.engineextension point and theExtension_DevblocksSearchEngineparent class (along with the bundledDevblocksSearchEngineMysqlFulltextandDevblocksSearchEngineElasticSearchengines). Custom search backends are now implemented as search index extensions, which integrate directly with worklist filters and the background queue. -
[Platform/Search] Removed the
devblocks.search.schemaextension point and theExtension_DevblocksSearchSchemaparent class. Per-record full-text schemas are now expressed as search index records and can be created or modified without code changes. -
[Activity Log/Metrics] Removed the
worker.seat_expiredactivity log entry and its notification, along with thecerb.sessions.seat.kicksandcerb.sessions.seat.kicks.durationmetrics. All four existed to report a worker session that had been ended to free a license seat, and with seat enforcement gone no session is ever ended for that reason. Historical samples on the two metrics are left in place; nothing new is written to them.
Fixed
-
[Devblocks/Registry] Fixed an issue in the Devblocks
registryservice where a key wasn't deleted from backend storage if it hadn't been loaded. -
[Profiles] Fixed an issue on profile pages where a newly created tab couldn't be selected until the page had been reloaded.
-
[Mailboxes] Fixed an issue where a mailbox's consecutive failure count could exceed the field's range of 0–127.
-
[Worklists/Search] In worklist search autocomplete, fixed an issue with hybrid text and parameterized filters such as
created:, where accepting a suggestion broke any further suggestions on that filter. -
[Data Queries] Fixed an issue with
worklist.subtotalsdata queries when grouping time-series data by@quarteror@quarterofyearagainst a 'date' custom field. -
[Bots/Behaviors] Fixed an issue where the same bot behavior tree shown in more than one place – a profile page and a popup, for instance – was only editable in one of them.
-
[Resources/Storage] Fixed a bug in Setup » Developers » Platform where reloading resource records that used a non-local storage profile, such as S3, reset the storage engine to 'database' without also resetting the profile ID.
-
[Storage/Schemas] Fixed an extension ID inconsistency between the
Storage_RecordChangesetclass and its plugin manifest. -
[Automations/UX] Updated
cerb.reply.isBannedDefunctto ignore senders on outgoing messages. -
[Time Tracking/Roles] Workers without the time tracking
updateprivilege can no longer close entries from the worklist. [#901] -
[Calendars] Fixed a long-standing bug where all-day events could appear to span two days for half the year. Times were handled as a fixed offset captured at one point in the year, so once daylight saving shifted, every all-day event's end rolled into the following day. Timezones are now resolved by name, so an event sits on the day it belongs to regardless of season.
-
[Workspace/Widgets/Calendar] Fixed exporting CSV/JSON from calendar workspace widgets.
-
[Workspace/Widgets/TimeBlocks] Fixed CSV exports in Time Blocks workspace widgets.
-
[Workspaces/Widgets] Fixed URL datasource caching for legacy workspace widgets. Previously, the cache was always disabled and didn't invalidate immediately if the URL changed.
-
[Sheets/Export] Fixed an issue with sheets when exporting CSV data from workspace widgets for unknown column types or null values.
-
[UI/Styles] Fixed the forced white background on IFRAMEs in the attachment viewer. This now falls back to the OS light/dark preference. Previously in OS dark mode it was showing white-on-white text.
-
[Login/UX] Fixed a dead-end error page when the CSRF token expired on the login form. The worker is now redirected back to the login form with an error message instead.
-
[Support Center/Profiles] Fixed an issue in the Support Center portal where the stricter Content-Security-Policy prevented the avatar image editor from working.
-
[Platform/Worklists] A failed worklist refresh no longer blanks the list. Creating or editing a record from a chooser popup could leave that popup's worklist empty until you closed and reopened it, and any refresh that failed behaved the same way – the list was replaced with nothing and no error was shown, so there was no indication anything had gone wrong. A failed refresh now leaves the existing rows in place and reports what happened, including server errors that previously went entirely unreported, while requests that were merely cancelled by navigating away or closing a popup mid-request are ignored rather than reported as failures. Chooser popups also keep their search text after a refresh.
-
[Integrations/AWS] Fixed the "Get pre-signed URL" bot action failing with a fatal error. It called a Guzzle 6 function that Guzzle 7 removed, so every invocation crashed instead of returning a URL.
-
[LLM/Providers] Fixed AWS Bedrock rejecting a turn when a model called several tools at once. Parallel tool results are now sent as the single conversation turn the API expects, so agents on models that batch their tool calls no longer fail mid-loop.
-
[LLM/Providers] AWS Bedrock no longer fails a turn when one of its tools has no description. Its Converse API treats a description as optional but rejects an empty one, and refuses the entire request rather than just the offending tool – so one undescribed tool disabled every tool on that turn. Cerb now leaves the description out instead of sending it empty.
-
[LLM/Providers] DeepSeek models on AWS Bedrock no longer leak their internal tool-call markup into replies. The structured tool call is unaffected; only the duplicate markup in the visible text is removed.
-
[Automations/Policies] Fixed an issue in automation policies where annotations on
settings:keys –time_limit_ms@int, for instance – weren't handled properly. -
[Data Queries/Platform] Fixed
platform.extensionsandplatform.extension.pointsdata queries drifting from the installation they describe. Both lists are now built from the actual platform registry and enriched from documentation, rather than from a hand-maintained array that live extension points had gone missing from. -
[KATA] In KATA, the
@floatannotation now accepts a multiple-line indented value like the other annotations. Following a@floatkey with an indented block was a syntax error, and leaving its value blank produced an empty object rather than a number. -
[Automations/Scripting] The scripting editors no longer suggest a
{% filter %}command that can't run.{% filter %}was removed from the template engine years ago in favor of{% apply %}, but it stayed in the editor's autocomplete, so it was offered to authors and then rejected with a parse error when the script ran. Typing{% fnow offersforandapply.
Platform
-
[Platform/PHP] Updated the minimum required PHP version to 8.5.
-
[Docker] Updated the Dockerfile to Ubuntu 26.04 LTS and PHP 8.5.
-
[Platform/Dependencies] Updated the
css-to-inline-styleslibrary from v2.2.4 to v2.3.0. -
[Platform/Dependencies] Updated the
twiglibrary from v3.8.0 to v3.26.0. -
[Platform/Composer/Twig] Replaced our forked Twig dependency with the official
twig/twigpackage. Rather than maintaining a patch against their source for ourDevblocksDictionaryDelegatefallback on undefined variables, we now overrideEnvironmentandContextVariableand implement a_DevblocksUndefinedVariableNodeVisitor. A new test suite catches regressions. This makes it much easier to follow Twig's official updates and security notices. -
[Platform/Services/AWS] Added an
awsservice to the platform that consolidates AWS functionality like request signature generation. It's used by connected accounts forhttp.request:and by storage profiles for S3-compatible storage backends. -
[Platform/Dependencies] Removed the legacy 'Amazon S3 PHP Class' library. This functionality is now handled entirely within Cerb by the new
awsservice. -
[Platform/Dependencies] Removed the Glyphicons icon font in favor of the new custom Cerb SVG icon set.
-
[Platform/Dependencies] Removed the
async.jslibrary in favor of the new Cerb UI async utilities. -
[Platform/Dependencies] Removed jQuery UI. Cerb now implements its own component library. (jQuery itself is still present – only jQuery UI was retired.) An earlier step in this release updated jQuery UI from v1.13.1 to v1.13.3 and dropped its Accordion, Datepicker, and Tooltip components as Cerb UI replaced them.
-
[Platform/Dependencies] Removed Ace.js and ace-diff. Cerb now implements its own code editors and diff viewer in Cerb UI.
-
[Platform/Dependencies] Removed the D3.js library. It was used for visualizations that
CerbUI.Chartnow handles. -
[Platform/Dependencies] Removed the
c3charting library, along with the legacyjquery.devblocksChartsplugin. Cerb UI now handles this functionality natively. -
[Platform/Dependencies] Removed the TopoJSON library.
CerbUI.Mapdecodes and projects TopoJSON itself. -
[Platform/Dependencies] Removed the Dagre-D3 library. It was used for the directed graphs in the automation editor's 'Visualization' tab, which the new
CerbUI.NodeEditorand node graph components now provide. -
[Platform/Dependencies] Removed the miniColors jQuery plugin, replaced by
CerbUI.ColorPicker. -
[Platform/Cerb UI/Developers] Removed the legacy jQuery plugins and shims that Cerb UI supersedes, including
.cerbCodeEditor,.cerbPropertyGrid,.cerbQueryTrigger,.cerbChooserTrigger, thecerbTextEditorfamily,jquery.qrcode,Devblocks.tooltip, andDevblocks.selectValue. If a third-party plugin calls any of these, it will need updating to the Cerb UI equivalents. -
[Platform/XML] Added a
DevblocksPlatform::parseXml()helper that suppresses errors while parsing XML. -
[Platform/Dependencies] Upgraded the
lcobucci/jwtlibrary to v5.6.0 and theleague/oauth2-serverlibrary to v9.3.0 for PHP 8.5 compatibility. -
[Platform/Dependencies] Upgraded the Horde IMAP library from v2.30.6 to v2.34.1 for PHP 8.x support.
-
[Platform/Dependencies] Added a PorterStemmer class for stemming tokens in search indexes. Credit to Richard Heyes (phpguru.org).
-
[Platform/Search/Developers] Added a
searchservice to the Devblocks platform. This handles common functionality like tokenization, stop words, hashing, stemming, and indexing. -
[Platform/Developers/Tests] Upgraded phpUnit from 9.6.34 to 12.5.33.
-
[Docker] In the reference Docker environment, queue and scheduler drains now run on their own PHP-FPM pool in their own container, so a long agent turn or queue job can no longer occupy a worker that page loads needed.
/queueand/cronroute to aphp-fpm-backgroundcontainer running a pool tuned for long holds, whilephp-fpmserves everything else – one request can hold a child for the length of an agent turn, so keeping the two apart is what stops a busy queue from making the interface feel slow. Scale the drain capacity withdocker compose up --scale php-fpm-background=3.CERB_WEB_CHILDRENandCERB_BACKGROUND_CHILDRENsize the two pools, defaulting to 6 and 4 – the same ten children the image previously ran as one shared pool. Size the background pool on memory and database connections rather than cores: a drain worker burns almost no CPU while it waits on a provider, but holds roughly 50-60MB and one MySQL connection for the length of the drain. This is a reference environment rather than a production topology – backpressure stays in the app, where a drain that can't get a concurrency slot answers529with aRetry-After, so it works the same however your own edge is built.
Security
-
[Support Center/Security] In the Support Center portal, added a stricter Content-Security-Policy that requires nonce tokens for scripts and removes
unsafe-inline. -
[Security/Markdown] When parsing Markdown with HTML allowed (safe mode disabled), the following HTML tags are still disallowed: title, textarea, style, xmp, iframe, noembed, noframes, script, and plaintext.
-
[Security] HMAC-signed all cache engine payloads (disk, Redis, Memcached) to prevent deserialization of attacker-controlled data.
-
[Security/Composer] Pinned Composer dependencies to audited versions.
-
[Queues/Jobs/Security] Tightened permissions on queue jobs so only the initiating worker and admins can view a job's details or its file attachments.
-
[Security/Dependencies] Updated
symfony/mailerto v7.4.12 in response to a security advisory. -
[Security/Dependencies] Upgraded
guzzlehttp/guzzleto v7.15.3 in response to security advisories. -
[Security/Dependencies] Upgraded
phpseclib/phpseclibto v3.0.54 in response to a security advisory. -
[Security/Dependencies] Upgraded
smarty/smartyto v4.5.7 in response to a security advisory. -
[Security/Dependencies] Upgraded
league/commonmarkto v2.10.0 in response to a security advisory. -
[Security/Dependencies] Upgraded
phpunit/phpunitto v12.5.33 in response to a security advisory. -
[OAuth/Security] OAuth2 consent is now bound to the app and the scopes it was granted for. Login state is reused for 20 minutes, and in the OAuth flow it was only cleared once an authorization request ran to completion – so a flow abandoned after the consent screen, where a worker approves an app but the browser never makes it back to
/oauth/authorize, left a granted consent sitting in the session. Within that window, following an authorization link for a second app issued it an authorization code with no consent screen shown at all, and the same gap let an app that had already been approved widen its scopes silently. The login state now records theclient_idand the scope set that were actually displayed on the consent screen, and/oauth/authorizerequires both to match the request in hand. Scope comparison ignores order and duplicates, so a reordered scope list doesn't force a redundant re-prompt. A declined request still counts as answered and continues to returnaccess_deniedto the client rather than looping back to the consent screen, and a POST to/login/consentwith no authorization request pending now grants nothing. -
[OAuth Apps/Security] The OAuth2 authorization flow now returns to the hostname in
APP_HOSTNAMEafter a worker signs in or grants consent. When an authorization request reached/oauth/authorizeand the worker still had to sign in or approve the requested scopes, Cerb saved the return address using the hostname from the incoming request instead of the configured one. Where a proxy or load balancer presents a hostname other than the one workers browse, the round trip came back on the wrong origin – and since session cookies are scoped to a single hostname, no session existed there, so the flow either looped back to the sign-in form or stopped with an access denied error. On Cerb Cloud this showed up as a custom domain falling back to the instance's*.cerb.meaddress partway through. -
[Security/LDAP/SSO] Added CA certificate and certificate-verification options for LDAPS. A directory using a self-signed or private CA can now be connected by supplying its CA certificate to Cerb, rather than modifying the system-wide TLS configuration.
-
[Security/Dependencies] Upgraded
twigto v3.26.0 in response to a security advisory. -
[Security/SSO/OIDC] Hardened the OpenID Connect SSO callback to validate the OAuth
stateparameter against a per-request value stored at the start of the authorization request. Previously an attacker could deliver a?code=...&state=...URL to a victim and force their browser through the callback, signing them into the attacker's identity (login CSRF / session injection). Mismatches now abort withauth.failed. -
[Security/SSO/OIDC] Hardened the OpenID Connect SSO flow by sending and verifying a per-request
nonce(OIDC Core §3.1.2.1, §3.1.3.7). The authorization request now includes a 128-bit random nonce that is persisted in the session and constant-time compared against the ID token'snonceclaim. This prevents ID-token replay across sessions and devices and brings the implementation into spec compliance for the authorization code flow. -
[Security/SSO/OIDC] Hardened the OpenID Connect SSO flow by rejecting ID tokens whose
email_verifiedclaim is present and not strictlytrue(OIDC Core §5.1). Without this check, an IdP that allows users to set arbitrary unverified email addresses could let an attacker impersonate any worker by claiming their email. The check passes through when the claim is absent to preserve compatibility with custom IdPs that don't emit it. -
[Security/SSO/OIDC] Hardened OpenID Connect ID-token signature verification when the IdP omits the
kid(Key ID) header. Previously the code validated against only the first JWK in the JWKS, which silently failed if the JWKS contained multiple keys (e.g. during key rotation) and the signer wasn't first. The kid-less branch now iterates through every JWK and uses the one whoseSignedWithconstraint validates the token; if none match, a specific "did not validate against any JWKS key" error is surfaced. IdPs that emitkidcontinue to use the exact-key lookup unchanged. -
[Security/SSO/SAML] Closed an open-redirect path through SAML SSO. The callback was pushing
RelayStatedirectly into the post-login redirect stack without origin validation, so a malicious or compromised IdP (or a forged callback POST) could pin an absolute attacker URL and hijack the user post-auth. Redirect URIs are now restricted to relative paths or absolute URLs whose scheme, host, and port match the configured base URL. Same-origin absolute URLs (e.g. the OAuth2 server/oauth/authorizeround-trip) continue to work. -
[Security/Auth] Switched the remaining secret-vs-secret comparisons across the codebase to
hash_equals()so comparisons are constant-time and don't leak secrets one byte at a time. Sites updated: the centralized CSRF token check, the Support Center CSRF check, the REST API HMAC signature compare, the OAuth2 serverclient_secretverification, the worker password recovery code and secret-question answer compares, theAPP_SERVICE_TOKENcompare, and the portal image HMAC compare. -
[Security/Auth] Tightened login-state hygiene so abandoned authentication flows can't bleed into the next attempt.
clearAuthState()now also resetsmotdso a previous attempt'sworker.authenticatedautomation result doesn't surface on the next login form. The redirect URI stack is cleared at the two true abandonment points: when the MFA retry limit is exceeded, and when password recovery wrong-guesses are exhausted. -
[Security/Recovery] When a worker completes the password recovery flow, all of their existing sessions are now evicted immediately after the password is rewritten. Previously a stolen session cookie would keep working until natural expiration even after the legitimate user reset their password specifically to evict the attacker. The eviction fires only after every gate of the recovery flow has passed (email control via the recovery code, second factor via TOTP or secret questions, and the password rewrite).