Compile rules
The compile rules decide what an expression may contain: for each of the ten binary operators, whether an expression may use it as Python does, may not use it at all, or has it 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 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 UI 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 command that holds 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, install the package on every agent server, and put the function on the allowlist under the name you enter.
The function keeps the operator's shape: two arguments, the left operand and the right operand. When the settings are saved, the agent server checks each named function: it must be on the allowlist, it must accept two arguments, and it must return a result for two sample numbers. If a function fails the check, the save is refused and the reason is shown. For matrix multiplication, @, only the two-argument shape is checked.
Rewriting is how an expensive operation is kept within limits. 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 limits 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, validation 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 it was published under. A changed rule reaches that agent only when a developer creates a draft from it and publishes the draft again.

