Compile rules
The compile rules decide what an expression may contain: for each of the ten binary operators, whether an expression may use the operator as Python does, may not use the operator at all, or has the operator replaced by a function of your own; how deep a comprehension may go; and the shapes of expression that are never allowed. Search2o Cloud applies the rules only when a draft is validated or published. A changed rule does not touch an agent that is already published. Developers edit the rules in the GUI under Guardrails.
Python operators
The operators are +, -, *, /, //, %, **, <<, >> and @. Each is set to one of three options.
- Allow. The operator works as Python defines it. This is the default for every operator.
- Deny. An expression that uses the operator fails validation, with the path of the expression.
- Rewrite. The operator is replaced by a call to a function you name. Choosing rewrite asks for the function name.
Rewriting an operator
Every use of a rewritten operator becomes a call to the named function with the operator's two operands: a ** b becomes bounded_pow(a, b). Write the function yourself, and put the function on the allowlist under the name you enter, on every agent server.
The function must keep the operator's shape: two arguments, the left operand and the right operand, both numbers. A function that takes one argument cannot stand in for an operator. When the settings are saved, the agent server checks each named function: the function must be on the allowlist, and must accept two numbers. A function that fails the check refuses the save, and the reason is shown. Matrix multiplication, @, is not checked.
Rewriting is how an expensive operation is kept within bounds. A function that stands in for ** can refuse an exponent above a limit, and a function that stands in for * can refuse a result that would not fit in 64 bits.
Comprehensions
A comprehension may be nested only so deep, and a single comprehension may have only so many for clauses. Both bounds are settings below.
| Field | Type | Default | Description |
|---|---|---|---|
operators | OperatorMappingModel | Which Python operators agent expressions may use, and how each is evaluated. | |
maxComprehensionDepth | integer | 4 | How deeply comprehensions may be nested in an expression. |
maxGeneratorsInComprehension | integer | 3 | How many 'for' clauses a single comprehension may have. |
What validation refuses
Besides a denied operator, the validation pass refuses these in any expression:
- any character outside ASCII letters, digits, punctuation and the space;
import, in any form;- any name that starts with an underscore, whether a variable, an attribute, a function or a keyword argument — so no private or dunder members;
.formatand.format_mapon any value: the format-string mini-language reads attributes at run time, where the checks above cannot see them. An f-string does the same job and is checked like any expression. The check is on the attribute name alone and is made before anything runs, so a class of your own with a legitimateformatmethod is refused too. To make such a method callable, bind it to a module-level name in your package, for examplerender_report = Report.format, add that name to the allowlist, and callrender_report(report, ...);lambda;- the assignment expression
:=; - a call to anything other than a name or an attribute, such as
(f)()orfs[0](); - a comprehension deeper, or with more
forclauses, than the settings allow.
Each refusal is reported with the path of the command that holds the expression.
When a rule changes
The rules are applied to a draft, at validation and at publishing, and at no other time. A published agent keeps the rules the agent was published under. A changed rule reaches that agent only when a developer creates a draft from the agent and publishes the draft again.

