Execution Primitives
Shared utility operations used by both entry points and evaluation. SDKs MUST implement these and SHOULD expose them in the public API for use by consuming tools.
5.1 Path Resolution
Section titled “5.1 Path Resolution”OATF defines two path variants with different capabilities, matching the format specification (§5.4):
5.1.1 Simple Dot-Path
Section titled “5.1.1 Simple Dot-Path”Used for: MatchPredicate keys (§2.10), {{request.*}} and {{response.*}} template references (§5.5), expression.variables values (§2.14).
resolve_simple_path(path: String, value: Value) → Optional<Value>Resolves a simple dot-path against a value tree. Returns the single value at the path, or nothing if any segment fails to resolve.
Path syntax:
| Segment | Meaning | Example |
|---|---|---|
field_name | Access named field on object | arguments.command |
. | Segment separator | capabilities.tools |
Segments consist of alphanumeric characters, underscores, and hyphens ([a-zA-Z0-9_-]+). No wildcard or index support. Resolution proceeds left to right: if any segment encounters a non-object, a missing key, or an array, resolution fails and returns nothing.
Empty path: When path is the empty string "", returns the root value itself. This is the canonical representation for targeting the entire message.
5.1.2 Wildcard Dot-Path
Section titled “5.1.2 Wildcard Dot-Path”Used for: pattern.target (§2.13), semantic.target (§2.15).
resolve_wildcard_path(path: String, value: Value) → List<Value>Resolves a wildcard dot-path against a value tree. Returns all values that match, potentially expanding across array elements. Returns an empty list if the path does not match.
Path syntax:
| Segment | Meaning | Example |
|---|---|---|
field_name | Access named field on object | capabilities |
[*] | Wildcard: all elements of array | tools[*] |
. | Segment separator | tools[*].description |
Segments consist of alphanumeric characters, underscores, and hyphens, with optional [*] suffix. Numeric indexing ([0], [1]) is not supported; use CEL expressions for positional access.
Behavior:
- Split
pathon.segment boundaries (respecting[*]as atomic suffixes). - Starting from
valueas the root, traverse each segment:- For a field name: if the current value is an object, access the named field. If the field is absent or the current value is not an object, produce no results for this branch.
- For
[*]: if the current value is an array, fan out to all elements. Each element continues independently through remaining segments. If the current value is not an array, produce no results for this branch (not an error).
- Collect all terminal values reached after processing all segments.
Empty path: When path is the empty string "", returns the root value itself as a single-element list.
Examples:
resolve_wildcard_path("tools[*].description", {"tools": [{"description": "A"}, {"description": "B"}]})→["A", "B"]resolve_wildcard_path("capabilities.tools", {"capabilities": {"tools": {"listChanged": true}}})→[{"listChanged": true}]resolve_wildcard_path("missing.path", {"other": 1})→[]
SDKs SHOULD enforce a maximum traversal depth to prevent stack overflow on pathological inputs. A depth limit of 64 is RECOMMENDED.
Limitation: Dot-path syntax does not support escaping literal dots within field names. A JSON object key containing a dot (for example, {"content.type": "text"}) cannot be addressed because the path content.type is always interpreted as two segments. This is an intentional simplification; protocol messages in MCP, A2A, and AG-UI do not use dotted key names. Authors MUST use CEL expressions (format specification §6.3) to match fields with dots, brackets, or other special characters in their names.
5.2 parse_duration
Section titled “5.2 parse_duration”parse_duration(input: String) → Result<Duration, ParseError>Parses a duration string in either shorthand or ISO 8601 format.
Accepted formats:
| Format | Example | Meaning |
|---|---|---|
{N}s | 30s | 30 seconds |
{N}m | 5m | 5 minutes |
{N}h | 1h | 1 hour |
{N}d | 2d | 2 days |
PT{N}S | PT30S | 30 seconds (ISO 8601) |
PT{N}M | PT5M | 5 minutes (ISO 8601) |
PT{N}H | PT1H | 1 hour (ISO 8601) |
P{N}D | P2D | 2 days (ISO 8601) |
PT{N}M{N}S | PT5M30S | 5 minutes 30 seconds (ISO 8601 composite) |
PT{N}H{N}M | PT1H30M | 1 hour 30 minutes (ISO 8601 composite) |
PT{N}H{N}M{N}S | PT1H30M15S | 1 hour 30 minutes 15 seconds (ISO 8601 composite) |
P{N}DT{...} | P1DT12H | 1 day 12 hours (ISO 8601 composite) |
N is a non-negative integer (≥ 0); 0s is a valid duration meaning zero elapsed time. Negative values (e.g., -5s, PT-30S) are invalid and MUST produce a ParseError. Fractional values are not supported. Any ISO 8601 duration composed of integer D, H, M, and S components is accepted; the components must appear in descending order (days → hours → minutes → seconds) and the T separator is required before any time components.
5.3 evaluate_condition
Section titled “5.3 evaluate_condition”evaluate_condition(condition: Condition, value: Value) → BooleanEvaluates a condition against a resolved value. If condition is a bare value (string, number, boolean, array), performs deep equality comparison. If condition is a MatchCondition object, evaluates each present operator. When multiple operators are present, all must match (AND logic). Returns true only if every present operator is satisfied.
Behavior by operator:
| Operator | Value Type | Returns True When |
|---|---|---|
contains | Any (coerced) | value contains contains as a substring. Case-sensitive. Non-strings are coerced to compact JSON. |
starts_with | Any (coerced) | value starts with the specified prefix. Case-sensitive. Non-strings are coerced to compact JSON. |
ends_with | Any (coerced) | value ends with the specified suffix. Case-sensitive. Non-strings are coerced to compact JSON. |
regex | Any (coerced) | value matches the RE2 regular expression. Non-strings are coerced to compact JSON. |
any_of | Any | value equals any element in the list (deep equality). |
gt | Number | value > operand. |
lt | Number | value < operand. |
gte | Number | value >= operand. |
lte | Number | value <= operand. |
exists | Boolean | See §5.4. exists is evaluated during predicate resolution, not by evaluate_condition. |
| (equality) | Any | value equals the operand (deep equality). Used when the MatchEntry is a scalar, not a MatchCondition. |
Type coercion for string operators: When a string operator (contains, starts_with, ends_with, regex) encounters a non-string value (object, array, number, boolean, or null), the value is first serialized to its compact JSON representation (no extra whitespace, keys sorted lexicographically), and the operator is applied to the resulting string. This ensures that patterns targeting structured values (e.g., regex on tool arguments that resolve to a JSON object) match against the serialized form rather than silently returning false.
Numeric type mismatches: If a numeric operator (gt, lt, gte, lte) is applied to a non-numeric value, the condition evaluates to false. Numeric type mismatches are not errors.
Deep equality: The any_of and scalar equality operators use deep equality with the following rules: numeric values compare by mathematical value (integer 42 equals float 42.0); object key order is irrelevant; NaN is not equal to any value including itself; null equals only null; arrays compare element-wise by position and length.
Regex: Patterns MUST be compiled with RE2 semantics (linear-time guarantee). SDKs MUST reject patterns with features outside the RE2 subset during validate. The regex is evaluated as a partial match: the pattern may match any substring of the value. To require a full-string match, the pattern MUST include ^ and $ anchors. This matches the default behavior of RE2 libraries across languages (Go’s regexp.MatchString, Rust’s regex::Regex::is_match, Python’s re2.search).
The exists operator: Unlike all other operators, exists does not inspect the resolved value; it inspects whether resolution succeeded. exists is evaluated during evaluate_predicate (§5.4) at the path-resolution step, before evaluate_condition is called. When exists is the only operator in a MatchCondition, evaluate_condition is not called at all (for exists: true, the path having resolved is sufficient; for exists: false, the path not having resolved is sufficient). When exists is combined with other operators, exists: true is redundant (all other operators already require a resolved value), and exists: false combined with any value-inspecting operator is always false (there is no value to inspect). These are natural consequences of AND logic, not special cases.
5.4 evaluate_predicate
Section titled “5.4 evaluate_predicate”evaluate_predicate(predicate: MatchPredicate, value: Value) → BooleanEvaluates a match predicate (a set of dot-path → condition entries) against a value. All entries are combined with AND logic.
Behavior:
- For each entry
(path, condition)in the predicate map: a. Resolve the dot-path key againstvalueusingresolve_simple_path(§5.1.1). b. If the path does not resolve (returns nothing):- If
conditionis aMatchConditionwithexists: false(and no other operators), the entry evaluates totrue. - Otherwise, the entry evaluates to
false. c. If the path resolves to a value: - If
conditionis aMatchConditionwithexists: false, the entry evaluates tofalse(regardless of other operators, since AND with a falseexistsis false). - Otherwise, evaluate the remaining condition operators against the resolved value. The entry is
trueif the value satisfies the condition.
- If
- Return
trueif all entries aretrue. Returnfalseif any entry isfalse.
5.5 interpolate_template
Section titled “5.5 interpolate_template”interpolate_template( template: String, extractors: Map<String, String>, request: Optional<Value>, response: Optional<Value>) → (String, List<Diagnostic>)Resolves template expressions in a string. Returns the interpolated string and any diagnostics (e.g., undefined references).
Template syntax:
{{extractor_name}}→ replaced with the value of the named extractor (current actor scope).{{actor_name.extractor_name}}→ replaced with the value of a cross-actor extractor reference.{{request.field.path}}→ replaced with the value at the dot-path in the current request.{{response.field.path}}→ replaced with the value at the dot-path in the current response.\{{→ replaced with a literal{{(escape sequence).
The extractors map is populated by the calling runtime with both local names (unqualified, from the current actor) and qualified names (actor_name.extractor_name, from all actors). The function itself performs simple key lookup; cross-actor resolution is a runtime responsibility.
Behavior:
- Replace all
\{{escape sequences with a placeholder. - Find all
{{...}}expressions intemplate. - For each expression:
a. If the name matches a key in
extractors, replace with the extractor value. b. If the name starts withrequest.andrequestis present, resolve the remaining path againstrequestusingresolve_simple_path. Replace with the resolved value, serialized to string. If the path does not resolve, replace with empty string and emit a warning diagnostic (W-004). c. If the name starts withresponse.andresponseis present, resolve the remaining path againstresponseusingresolve_simple_path. Replace with the resolved value, serialized to string. If the path does not resolve, replace with empty string and emit a warning diagnostic (W-004). d. If neither matches, replace with empty string and emit a warning diagnostic (W-004). - Restore all placeholders to literal
{{. - Return the interpolated string and accumulated diagnostics.
5.5a interpolate_value
Section titled “5.5a interpolate_value”interpolate_value( value: Value, extractors: Map<String, String>, request: Optional<Value>, response: Optional<Value>) → (Value, List<Diagnostic>)Recursively walks a Value tree and interpolates all template expressions found in string values. Non-string scalars are returned unchanged. This function is the entry point for interpolating structured values (e.g., action parameters, state objects) that may contain template expressions at any depth.
Behavior:
- If
valueis a string containing{{, callinterpolate_template(value, extractors, request, response). Return the interpolated string and its diagnostics. - If
valueis a string without{{, return the string unchanged with no diagnostics. - If
valueis an object (map), recurse into each value (not keys). Return the object with interpolated values and the union of all diagnostics. - If
valueis an array, recurse into each element. Return the array with interpolated elements and the union of all diagnostics. - If
valueis any other scalar (null, boolean, number), return it unchanged with no diagnostics.
No re-expansion: Interpolation results are not re-scanned for template expressions. If an extractor value contains {{, it is treated as literal text in the output. This prevents injection of template expressions through extracted values.
5.6 evaluate_extractor
Section titled “5.6 evaluate_extractor”evaluate_extractor( extractor: Extractor, message: Value, direction: ExtractorSource) → Optional<String>Applies an extractor to a message, capturing a value. The direction parameter indicates whether message is a request or response, enabling the function to filter extractors by their declared source.
Behavior:
- If
extractor.source≠direction, returnNone. This extractor targets a different message direction and should not be applied. - Apply the extractor by type:
json_path: Evaluate the JSONPath expression againstmessage. If the expression matches one or more nodes, return the first match in document order (per RFC 9535 §2.6) serialized to its compact JSON string representation. If no match, returnNone.regex: Convertmessageto its string representation, evaluate the regular expression. If the regex matches and has at least one capture group, return the first capture group’s value. If no match, returnNone.
None means “no match” (the extractor did not find the targeted content). Some("") is a valid result when the extractor matched but the captured value is genuinely an empty string. Downstream template interpolation treats None as an undefined extractor (triggering W-004 warnings), while Some("") substitutes the empty string silently.
When the extracted value is a non-scalar (object or array), it MUST be serialized to its compact JSON string representation.
5.7 select_response
Section titled “5.7 select_response”select_response( entries: List<ResponseEntry>, request: Value) → Optional<ResponseEntry>Selects the first matching response entry from an ordered list, using when predicates for conditional dispatch. The request parameter is the protocol message payload that triggered the dispatch — for server-mode dispatch lists (responses, sampling_responses, elicitation_responses, task_responses) this is the incoming request; for client-mode dispatch lists (tool_responses) this is the outgoing request that the response corresponds to.
Behavior:
- Iterate
entriesin order. - For each entry that has a
whenpredicate, evaluate the predicate againstrequestusingevaluate_predicate(§5.4). If the predicate matches, return that entry. - If no predicate-bearing entry matches and an entry without
whenexists (the default entry), return it. - If no entry matches, return
None.
First-match-wins: the first entry whose when predicate matches is returned, regardless of subsequent entries. The default entry (no when) is only considered as a fallback after all predicate-bearing entries have been tried.
5.8 evaluate_trigger
Section titled “5.8 evaluate_trigger”evaluate_trigger( trigger: Trigger, event: Optional<ProtocolEvent>, elapsed: Duration, state: TriggerState) → TriggerResultEvaluates whether a trigger condition is satisfied for phase advancement. The function manages event counting internally via the mutable state parameter (§2.8c), which the caller persists across calls but does not inspect or modify.
Behavior:
- If
trigger.afteris present andelapsed≥trigger.after, returnTriggerResult::Advanced { reason: timeout }. - If
trigger.eventis present andeventis present: a. Base match: Ifevent.event_type≠trigger.event, returnTriggerResult::NotAdvanced. b. Predicate check: Iftrigger.matchis present, evaluate the match predicate againstevent.contentusingevaluate_predicate(§5.4). If the predicate does not match, returnTriggerResult::NotAdvanced. c. Count increment: Incrementstate.event_countby 1. This increment occurs only after base event and predicate have all passed. d. Count check: Ifstate.event_count≥trigger.count(resolved, default1), returnTriggerResult::Advanced { reason: event_matched }. - Return
TriggerResult::NotAdvanced.
TriggerResult and AdvanceReason are defined in §2.8b. TriggerState is defined in §2.8c.
5.9 extract_protocol
Section titled “5.9 extract_protocol”extract_protocol(mode: String) → StringExtracts the protocol identifier from a mode string by stripping the _server or _client suffix.
Behavior:
- If
modeends with_server, return the prefix before_server. - If
modeends with_client, return the prefix before_client. - Otherwise, return
modeunchanged (this case should not occur for valid modes per V-034).
Examples:
"mcp_server"→"mcp""a2a_client"→"a2a""ag_ui_client"→"ag_ui"
5.10 compute_effective_state
Section titled “5.10 compute_effective_state”compute_effective_state(phases: List<Phase>, phase_index: Integer) → ValueComputes the effective state at a given phase by applying state inheritance.
Behavior:
- Starting from phase 0, walk forward through phases up to
phase_index. - If a phase defines
state, that becomes the current effective state (full replacement). - If a phase omits
state, the effective state carries forward from the preceding phase unchanged. - Return the effective state at
phase_index.
This operation supports both adversarial tools (which need to know what state to present at each phase) and evaluation tools (which need to know what state to expect at each phase).