From 031e1e778ca12b99f74d999763d9b32bbcc6d16f Mon Sep 17 00:00:00 2001 From: TristonianJones Date: Tue, 10 Feb 2026 16:49:28 -0800 Subject: [PATCH 1/4] Initial draft at AI attributes and policy function helper declarations --- doc/langdef.md | 4 +- doc/policy/ai/README.md | 238 ++++++++++ doc/policy/ai/agent_env.yaml | 0 doc/policy/ai/common_env.yaml | 730 +++++++++++++++++++++++++++++++ doc/policy/ai/tool_call_env.yaml | 85 ++++ 5 files changed, 1056 insertions(+), 1 deletion(-) create mode 100644 doc/policy/ai/README.md create mode 100644 doc/policy/ai/agent_env.yaml create mode 100644 doc/policy/ai/common_env.yaml create mode 100644 doc/policy/ai/tool_call_env.yaml diff --git a/doc/langdef.md b/doc/langdef.md index efa677c8..6d22352c 100644 --- a/doc/langdef.md +++ b/doc/langdef.md @@ -1,7 +1,9 @@ # Language Definition This page constitutes the reference for CEL. For a gentle introduction, see -[Intro](intro.md). +[Intro](intro.md). For specific guidance regarding CEL in policy +see the `doc/policy` folder contains best practices, such as attribute +specification and example environments for [AI Policy](doc/policy/ai/README.md) ## Overview diff --git a/doc/policy/ai/README.md b/doc/policy/ai/README.md new file mode 100644 index 00000000..ea550a4f --- /dev/null +++ b/doc/policy/ai/README.md @@ -0,0 +1,238 @@ +# **AI Policy Attributes** + +This document outlines a schema designed for governing AI agents, providing a standardized way to track identity, security context, and the flow of information between users, agents, tools, and models. + +These message definitions should be accompanied by the function +implementations declared in the [common_env.yaml](common_env.yaml), +and provide attributes for either an [agent policy](agent_env.yaml) +or [tool call policy](tool_call_env.yaml) or some combination thereof +which most closely matches the needs of the use case. + +## **Core Definitions** + +### **Message: Agent** + +The top-level container representing the AI System. It encapsulates both static configuration (Manifests, Identity) and dynamic runtime state (Context, Inputs, Outputs). + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| name | string | The unique resource name of the agent (e.g. agents/finance-helper). | +| description | string | Human-readable description of the agent's purpose. | +| version | string | The semantic version of the agent definition. | +| model | Model | The underlying model family backing this agent. | +| provider | AgentProvider | The provider or vendor responsible for hosting/managing this agent. | +| auth | AgentAuth | Identity of the Agent itself (Service Account / Principal). | +| context | AgentContext | The accumulated security context (Trust, Sensitivity, Data Sources). | +| input | AgentMessage | The current turn's input (Prompt \+ Attachments). | +| output | AgentMessage | The pending response (if evaluating egress/output policies). | + +### **Message: AgentAuth** + +Represents the identity of the Agent itself. This is independent of the end-user credentials. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| principal | string | The principal of the agent, preferably in [SPIFFE format](https://spiffe.io/docs/latest/spiffe/concepts/#spiffe-identifiers). | +| claims | google.protobuf.Struct | Map of structured claims about the agent (e.g. issuer, audience, expiration) typically found in [JWT tokens (RFC 7519\)](https://datatracker.ietf.org/doc/html/rfc7519). | +| oauth\_scopes | repeated string | The OAuth scopes granted to the agent. | + +## **Security & Trust Context** + +### **Message: AgentContext** + +Represents the aggregate security and data governance state of the agent's context window. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| trust | TrustLevel | Aggregated trust level associated with relevant data in the window. | +| sources | repeated DataSource | Origin and lineage tracking for data included in the context. | +| prompt | string | The flattened text content of the current prompt. | + +#### **Nested Message: DataSource** + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| id | string | Unique id describing the originating data source (e.g. bigquery:sales\_table). | +| provenance | string | The category of origin for this data (e.g. UserPrompt, PublicWeb). | + +### **Message: TrustLevel** + +Describes the integrity or veracity of the data. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| level | string | The trust level of the data (e.g. untrusted, trusted). | +| findings | repeated Finding | Findings which support or are associated with this trust level. | + +### **Message: ClassificationLabel** + +Describes the classification of data within the context, such as sensitivity or safety hints. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| name | string | Common labels (e.g. pii, internal, child\_safety). | +| category | Category (Enum) | The category of the label (SENSITIVITY, SAFETY, THREAT). | +| findings | repeated Finding | Findings associated with this specific label. | + +### **Message: Finding** + +Describes confidence measures and reasoning associated with a label or trust level. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| value | string | The name of the confidence measure (e.g. picc\_score). | +| confidence | double | The confidence score between 0 and 1\. | +| explanation | string | An optional explanation for the confidence score. | + +## **Messaging & Content** + +### **Message: AgentMessage** + +Represents a single turn in the conversation, acting as a container for multimodal content. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| role | string | The actor who constructed the message (e.g. user, model, tool). | +| parts | repeated Part | The ordered sequence of content parts. | +| metadata | google.protobuf.Struct | Arbitrary metadata associated with the message turn. | +| time | google.protobuf.Timestamp | Message creation time. | + +#### **Nested Message: Part (oneof)** + +| Field | Type | Purpose | +| :---- | :---- | :---- | +| prompt | ContentPart | User or System text input. | +| tool\_call | ToolCall | A request to execute a specific tool. | +| attachment | ContentPart | A file or multimodal object (Image, PDF). | +| error | ErrorPart | An error that occurred during processing. | + +### **Message: ContentPart** + +A catch-all message type for encapsulating multimodal or structured content. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| id | string | Unique identifier for this content part. | +| type | string | The type of content (e.g. text, file, json). | +| mime\_type | string | The MIME type of the content (e.g. image/png). | +| content | string | String serialized representation of the content. | +| data | bytes | Binary representation of the content. | +| structured\_content | google.protobuf.Struct | JSON object representation of the content. | +| time | google.protobuf.Timestamp | Timestamp associated with the content part. | + +## **Tooling & Execution** + +### **Message: Tool** + +Describes a specific function or capability available to the agent. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| name | string | The unique name of the tool (e.g. weather\_lookup). | +| description | string | Human readable description of what the tool does. | +| input\_schema | google.protobuf.Struct | JSON Schema defining the expected arguments. | +| output\_schema | google.protobuf.Struct | JSON Schema defining the expected output. | +| annotations | ToolAnnotations | Behavioral hints about the tool. | + +### **Message: ToolAnnotations** + +Hints describing a tool's behavior, informed by conventions like the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) spec. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| read\_only | bool | If true, the tool does not modify its environment. | +| destructive | bool | If true, the tool may perform destructive updates. | +| idempotent | bool | If true, repeated calls with same args have no additional effect. | +| open\_world | bool | If true, tool interacts with an "open world" of external entities. | +| async | bool | If true, this tool is intended to be called asynchronously. | +| output\_trust | TrustLevel | The trust level of the tool's output. | + +### **Message: ToolCall** + +Represents a specific invocation of a tool by the agent. + +| Attribute | Type | Purpose | +| :---- | :---- | :---- | +| id | string | Unique identifier used to correlate call with results/errors. | +| name | string | The name of the tool being called. | +| params | google.protobuf.Struct | The arguments provided to the tool call. | +| result | ContentPart | The successful output of the tool (if status is result). | +| error | ErrorPart | The error encountered during execution (if status is error). | +| user\_confirmed | bool | Indicates if the user explicitly confirmed this action (HITL). | + +## **Policy Expressions & Use Cases** + +Policy authors use Common Expression Language (CEL) to inspect the agent's state and enforce governance rules. Below are common use cases and examples based on the cel.expr.ai environment. + +### **1\. Data Sensitivity & PII Governance** + +Ensure that sensitive data (like PII) is handled correctly across different parts of the agent context. + +* **Retrieving Sensitivity Labels:** + + // Get optional list of sensitivity findings for the 'pii' label. + agent.context.sensitivityFindings("pii") + +* **Restricting High-Confidence PII in Tool Calls:** + + // Fail if any PII finding has a confidence score greater than 0.5. + tool.call.sensitivityFindings("pii").orValue(\[\]) + .all(finding, finding.confidence \<= 0.5) + +* **Checking for Specific Data Types:** + + // Returns true if the tool call contains both 'phone\_number' and 'ssn' findings. + tool.call.sensitivityFindings("pii").hasAll(\["phone\_number", "ssn"\]) + +### **2\. Safety & Threat Mitigation** + +Identify and block malicious inputs or unsafe model outputs. + +* **Detecting Prompt Injections or Jailbreaks:** + + // Returns true if any specified threats are detected with high confidence. + agent.context.threatFindings().hasAll(\["injection", "jailbreak", "malicious\_uri"\]) + +* **Filtering Output for Responsible AI Safety:** + + // Check if the model output contains hate speech or sexually explicit content. + agent.output.safetyFindings("responsible\_ai") + .hasAll(\["hate\_speech", "sexually\_explicit"\]) + +### **3\. Context & History Analysis** + +Analyze previous turns in the conversation to enforce policies over time. + +* **Filtering History by Role and Time:** + + // Check for threats in user messages from the last 5 minutes. + agent.history + .role("user") + .after(now \- duration('5m')) + .threatFindings().hasAll(\["injection", "jailbreak"\]) + +* **Inspecting Tool History:** + + // Find all JSON-based tool results in the agent's history. + agent.history.role("agent").toolCalls("get\_weather").resultType("json") + +### **4\. Advanced Tool Governance** + +Control how tools interact with the world based on their definitions. + +* **The "Lethal Trifecta" Check:** + + // Block tool calls that are destructive, interact with the open world, and produce untrusted output. + agent.input.parts.exists(part, + has(part.tool\_call) && + \!part.tool\_call.spec().annotations.output\_trust.level in \['trusted', 'trusted\_1p'\] + ) + +### **5\. Utility Functions** + +* **Creating Manual Findings:** `ai.finding("picc_score", 0.5)` +* **Casting Content:** `agent.input.parts[0].asType(bigquery.QueryRequest)` +* **Union of Findings:** Combine findings from context and tool calls, keeping the highest confidence score: + + agent.context.sensitivityFindings("pii") + .union(tool.call.sensitivityFindings("pii")) diff --git a/doc/policy/ai/agent_env.yaml b/doc/policy/ai/agent_env.yaml new file mode 100644 index 00000000..e69de29b diff --git a/doc/policy/ai/common_env.yaml b/doc/policy/ai/common_env.yaml new file mode 100644 index 00000000..c40e868c --- /dev/null +++ b/doc/policy/ai/common_env.yaml @@ -0,0 +1,730 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "ai.common_env" + +extensions: + - name: "encoders" + - name: "lists" + - name: "math" + - name: "regex" + - name: "sets" + - name: "strings" + version: "latest" + - name: "two-var-comprehensions" + +variables: + - name: "now" + type_name: "google.protobuf.Timestamp" + description: | + The current time. + +functions: + - name: "ai.finding" + description: | + Returns a cel.expr.ai.Finding with the given value and confidence score. + overloads: + - id: "ai.finding_string_double" + examples: + - | + // Returns a finding with the name 'picc_score' and confidence score 0.5. + ai.finding("picc_score", 0.5) + args: + - type_name: string + - type_name: double + return: + type_name: cel.expr.ai.Finding + + - name: "sensitivityFindings" + description: | + Returns an optional set of findings from sensitivity labels computed over the input. + The labels are identified by the label key, e.g. 'pii' and the output type from the + call is optional_type(list(string)) to allow for chaining with other optional valued + computations. + overloads: + - id: "AgentContext_sensitivityFindings_string" + examples: + - | + // Returns the optional list of sensitivity label values for the 'pii' label name. + agent.context.sensitivityFindings("pii") + target: + type_name: cel.expr.ai.AgentContext + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - id: "AgentMessage_sensitivityFindings_string" + examples: + - | + // Returns the optional list of sensitivity findings for the 'pii' label name + // from the agent input message parts. + agent.input.sensitivityFindings("pii") + - | + // Returns the optional list of sensitivity findings for the 'pii' label name + // from the agent output message parts. + agent.output.sensitivityFindings("pii") + target: + type_name: cel.expr.ai.AgentMessage + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - id: "AgentMessageSet_sensitivityFindings_string" + examples: + - | + // Returns the optional list of sensitivity findings for the 'pii' label name + // from the agent message set. + agent.history.sensitivityFindings("pii") + - | + // Returns the optional list of sensitivity findings for the 'pii' label name + // from the user messages within 5 minutes. + agent.history + .after(now - duration('5m')) + .sensitivityFindings("pii") + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - id: "ToolCall_sensitivityFindings_string" + examples: + - | + // Returns the optional list of findings for the 'pii' sensitivity label name. + tool.call.sensitivityFindings("pii") + - | + // Validates that there is no finding which has a confidence value greater than 0.5. + tool.call.sensitivityFindings("pii").orValue([]) + .all(finding, finding.confidence <= 0.5) + target: + type_name: cel.expr.ai.ToolCall + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - name: "hasAll" + description: | + Returns true if all values in the list are present in the optional list of findings. + overloads: + - id: "optional_type(list(Finding))_hasAll_list(string)" + examples: + - | + // Returns true if the tool call has all the 'pii' sensitivity findings: + // 'phone_number' and 'ssn'. + tool.call.sensitivityFindings("pii").hasAll(["phone_number", "ssn"]) + target: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: list + params: + - type_name: string + return: + type_name: bool + + - id: "optional_type(list(Finding))_hasAll_list(Finding)" + examples: + - | + // Returns true if the tool call has all of the 'pii' sensitivity findings: + // 'phone_number' and 'ssn'. + tool.call.sensitivityFindings("pii").hasAll([ + ai.finding("phone_number", 0.5), + ai.finding("ssn", 0.5) + ]) + target: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: bool + + - id: "optional_type(list(Finding))_hasAll_optional_type(list(Finding))" + examples: + - | + // Returns true if the agent context has all of the 'pii' sensitivity findings with + // greater or equal confidence scores associated with the tool call. + agent.context.sensitivityFindings("pii").hasAll(tool.call.sensitivityFindings("pii")) + target: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: bool + - id: "list(Finding)_hasAll_list(Finding)" + examples: + - | + // Returns true if the agent context has all of the 'pii' sensitivity findings with + // greater or equal confidence scores associated with the tool call. + [ai.finding("phone_number", 0.5), ai.finding("ssn", 0.5)].hasAll([ai.finding("phone_number", 0.5)]) + target: + type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: bool + - id: "list(Finding)_hasAll_list(string)" + examples: + - | + // Returns true if the agent context has all of the 'pii' sensitivity findings with + // greater or equal confidence scores associated with the tool call. + [ai.finding("phone_number", 0.5), ai.finding("ssn", 0.5)].hasAll(["phone_number"]) + target: + type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: list + params: + - type_name: string + return: + type_name: bool + + - name: "union" + description: | + Returns the union of two optional lists of values. + overloads: + - id: "optional_type(list(Finding))_union_optional_type(list(Finding))" + examples: + - | + // Returns the union of the 'pii' sensitivity labels from the agent context + // and tool call. For findings with the same value, the aggregated finding will + // have the highest confidence score. + agent.context.sensitivityFindings("pii").union(tool.call.sensitivityFindings("pii")) + target: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "optional_type(list(Finding))_union_list(Finding)" + examples: + - | + // Returns the union of the 'pii' sensitivity labels from the agent context + // and tool call. For findings with the same value, the aggregated finding will + // have the highest confidence score. + agent.context.sensitivityFindings("pii").union([ai.finding("ssn", 0.5)]) + - | + // Returns the union of the findings, picking the highest confidence score for findings + // with the same name. + optional.of([ai.finding("ssn", 0.6)]).union([ai.finding("ssn", 0.5)]) + .hasAll([ai.finding("ssn", 0.6)]) + target: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "list(Finding)_union_optional_type(list(Finding))" + examples: + - | + // Returns the union of the 'pii' sensitivity labels from the agent context + // and tool call. For findings with the same value, the aggregated finding will + // have the highest confidence score. + [ai.finding("ssn", 0.5)].union(tool.call.sensitivityLabel("pii")) + target: + type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "list(Finding)_union_list(Finding)" + examples: + - | + // Returns the union of the 'pii' sensitivity labels from the agent context + // and tool call. For findings with the same value, the aggregated finding will + // have the highest confidence score. + [ai.finding("ssn", 0.5)].union([ai.finding("phone_number", 0.75)]) + target: + type_name: list + params: + - type_name: cel.expr.ai.Finding + args: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + return: + type_name: list + params: + - type_name: cel.expr.ai.Finding + + - name: "threatFindings" + description: | + Returns a list of potential threads associated with the input. + overloads: + - id: "AgentContext_threatFindings" + examples: + - | + // Returns the potential threats associated with the agent context which includes + // the agent prompt, history, and current input. + // + // The operation return true if any of the specified threats are present with + // confidence level above 0.5. + agent.context.threatFindings().hasAll(["injection", "jailbreak", "malicious_uri"]) + target: + type_name: cel.expr.ai.AgentContext + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - id: "ToolCall_threatFindings" + examples: + - | + // Returns the potential threats associated with the agent context which includes + // the agent prompt, history, and current input. + // + // The operation return true if any of the specified threats are present with + // confidence level above 0.5. + tool.call.threatFindings().hasAll(["injection", "jailbreak", "malicious_uri"]) + target: + type_name: cel.expr.ai.ToolCall + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "AgentMessage_threatFindings" + examples: + - | + // Returns the potential threats associated with the agent message. + // + // The operation return true if any of the specified threats are present with + // confidence level above 0.5. + agent.input.threatFindings().hasAll(["injection", "jailbreak", "malicious_uri"]) + - | + // Returns the potential threats associated with the agent output. + // + // The operation return true if any of the specified threats are present with + // confidence level above 0.5. + agent.output.threatFindings().hasAll(["injection", "jailbreak", "malicious_uri"]) + target: + type_name: cel.expr.ai.AgentMessage + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "AgentMessageSet_threatFindings" + examples: + - | + // Returns the potential threats associated with the agent message set. + // + // The operation return true if any of the specified threats are present with + // confidence level above 0.5. + agent.history.threatFindings().hasAll(["injection", "jailbreak", "malicious_uri"]) + - | + // Returns the potential threats associated with the user messages within 5 minutes. + // + // The operation return true if any of the specified threats are present with + // confidence level above 0.5. + agent.history + .after(now - duration('5m')) + .threatFindings().hasAll(["injection", "jailbreak", "malicious_uri"]) + target: + type_name: cel.expr.ai.AgentMessageSet + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - name: "safetyFindings" + description: | + Returns a list of potential safety labels associated with the input. + overloads: + - id: "AgentContext_safetyFindings_string" + examples: + - | + // Returns true if the agent context has the hate_speech and sexually_explicit + // findings confidence level above 0.5. + agent.context.safetyFindings("responsible_ai").hasAll(["hate_speech", "sexually_explicit"]) + - | + // Returns true if the agent context has a violence label with high confidence. + agent.context.safetyFindings("responsible_ai").hasAll([ai.finding("violence", 0.9)]) + - | + // Returns true if the agent context has child safety and abuse findings + // with even low confidence. + agent.context.safetyFindings("child_safety").hasValue() + target: + type_name: cel.expr.ai.AgentContext + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - id: "ToolCall_safetyFindings_string" + examples: + - | + // Returns true if the tool call has the hate_speech and sexually_explicit + // findings with confidence level above 0.5. + tool.call.safetyFindings("responsible_ai").hasAll(["hate_speech", "sexually_explicit"]) + - | + // Returns true if the tool call has a violence label with high confidence. + tool.call.safetyFindings("responsible_ai").hasAll([ai.finding("violence", 0.9)]) + - | + // Returns true if the tool call has child safety and abuse findings + // with even low confidence. + tool.call.safetyFindings("child_safety").hasValue() + target: + type_name: cel.expr.ai.ToolCall + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "AgentMessage_safetyFindings_string" + examples: + - | + // Returns true if the message has the hate_speech and sexually_explicit + // findings with confidence level above 0.5. + agent.input.safetyFindings("responsible_ai") + .hasAll(["hate_speech", "sexually_explicit"]) + - | + // Returns true if the output has the hate_speech and sexually_explicit + // findings with confidence level above 0.5. + agent.output.safetyFindings("responsible_ai") + .hasAll(["hate_speech", "sexually_explicit"]) + target: + type_name: cel.expr.ai.AgentMessage + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + - id: "AgentMessageSet_safetyFindings_string" + examples: + - | + // Returns true if the message set has the hate_speech and sexually_explicit + // findings with confidence level above 0.5. + agent.history.safetyFindings("responsible_ai").hasAll(["hate_speech", "sexually_explicit"]) + - | + // Returns true if the user messages within 5 minutes have a violence label with + // high confidence. + agent.history + .role("user").after(now - duration('5m')) + .safetyFindings("responsible_ai").hasAll([ai.finding("violence", 0.9)]) + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: string + return: + type_name: optional_type + params: + - type_name: list + params: + - type_name: cel.expr.ai.Finding + + - name: "role" + description: | + Adds a constraint to the AgentMessageSet to match messages from the specified role, either + 'user' or 'agent'. + overloads: + - id: "AgentMessageSet_role_string" + examples: + - | + // Filters the history to only include messages from the agent. + agent.history.resultType("json").role("agent") + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: string + return: + type_name: cel.expr.ai.AgentMessageSet + + - name: "toolCalls" + description: | + Adds a constraint to the AgentMessageSet to match messages with a tool call that matches the + given name. + overloads: + - id: "AgentMessageSet_toolCalls_string" + examples: + - | + // Returns a filter that matches messages with the tool_call field set + // where the call name matches the given name. + agent.history.role("agent").toolCalls("get_weather") + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: string + return: + type_name: cel.expr.ai.AgentMessageSet + + - id: "AgentMessage_toolCalls_string" + examples: + - | + // Returns a filter that matches message parts where the tool call field is set + // and the call name matches the given name. + agent.output.toolCalls("get_weather") + target: + type_name: cel.expr.ai.AgentMessage + args: + - type_name: string + return: + type_name: cel.expr.ai.AgentMessageSet + + - name: "resultType" + description: | + Adds a constraint to the AgentMessageSet to match messages with a tool call result of the given + type, such as 'json' or 'text'. + overloads: + - id: "AgentMessageSet_resultType_string" + examples: + - | + // Returns a filter that matches messages where the content type or tool call + // result type is 'json'. + agent.history.role("agent").resultType("json") + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: string + return: + type_name: cel.expr.ai.AgentMessageSet + + - id: "AgentMessage_resultType_string" + examples: + - | + // Returns a filter that matches message parts where the content type or tool call + // result type is 'json'. + agent.output.resultType("json") + target: + type_name: cel.expr.ai.AgentMessage + args: + - type_name: string + return: + type_name: cel.expr.ai.AgentMessageSet + + - name: "before" + description: | + Adds a constraint to the AgentMessageSet to match messages with a timestamp less than or equal + to than the given timestamp. + overloads: + - id: "AgentMessageSet_before_timestamp" + examples: + - | + // Returns a filter that matches messages with a timestamp before the given value. + agent.history.before(timestamp("2025-01-01T00:00:00Z")) + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: google.protobuf.Timestamp + return: + type_name: cel.expr.ai.AgentMessageSet + + - id: "AgentMessage_before_timestamp" + examples: + - | + // Returns a filter that matches message parts before the given timestamp. + agent.output.before(timestamp("2025-01-01T00:00:00Z")) + target: + type_name: cel.expr.ai.AgentMessage + args: + - type_name: google.protobuf.Timestamp + return: + type_name: cel.expr.ai.AgentMessageSet + + - name: "after" + description: | + Adds a constraint to the AgentMessageSet to match messages with a timestamp greater than or + equal to the given timestamp. + overloads: + - id: "AgentMessageSet_after_timestamp" + examples: + - | + // Limits the history to messages after the given timestamp. + agent.history.after(timestamp("2025-01-01T00:00:00Z")) + target: + type_name: cel.expr.ai.AgentMessageSet + args: + - type_name: google.protobuf.Timestamp + return: + type_name: cel.expr.ai.AgentMessageSet + + - id: "AgentMessage_after_timestamp" + examples: + - | + // Returns a filter that matches message parts after the given timestamp. + agent.output.after(timestamp("2025-01-01T00:00:00Z")) + target: + type_name: cel.expr.ai.AgentMessage + args: + - type_name: google.protobuf.Timestamp + return: + type_name: cel.expr.ai.AgentMessageSet + + - name: "prompts" + description: | + Returns a filtered view of message parts corresponding to user prompts. + overloads: + - id: "AgentMessageSet_prompts" + examples: + - | + // Returns a filter that matches messages that are prompts. + agent.history.role("user").prompts() + target: + type_name: cel.expr.ai.AgentMessageSet + return: + type_name: cel.expr.ai.AgentMessageSet + - id: "AgentMessage_prompts" + examples: + - | + // Returns a filter that matches messages that are prompts. + agent.input.prompts() + target: + type_name: cel.expr.ai.AgentMessage + return: + type_name: cel.expr.ai.AgentMessageSet + + - name: "parts" + description: | + Returns the ordered list of AgentMessage.Part entries for all messages in the message set. + overloads: + - id: "AgentMessageSet_parts" + examples: + - | + // Returns the ordered list of message parts in the message set. + agent.history.parts() + target: + type_name: cel.expr.ai.AgentMessageSet + return: + type_name: list + params: + - type_name: cel.expr.ai.AgentMessage.Part + + - name: "spec" + description: | + Returns the specification for the tool. + overloads: + - id: "ToolCall_spec" + examples: + - | + // Returns true if the tool call was defined with metadata indicating that + // the tool posseses the lethal trifecta: open world, destructive, and produces + // untrusted output. + agent.input.parts.exists(part, + has(part.tool_call) && + !part.tool_call.spec().annotations.output_trust.level in [ + 'trusted', 'trusted_1p' + ]) + target: + type_name: cel.expr.ai.ToolCall + return: + type_name: cel.expr.ai.Tool + + - name: "asType" + description: | + Casts the message part to the specified type. + overloads: + - id: "ContentPart_asType_type(T)" + examples: + - | + // Returns the message part as type bigquery.QueryRequest + agent.input.parts[0].asType(bigquery.QueryRequest) + target: + type_name: cel.expr.ai.ContentPart + args: + - type_name: type + params: + - type_name: T + is_type_param: true + return: + type_name: T + is_type_param: true \ No newline at end of file diff --git a/doc/policy/ai/tool_call_env.yaml b/doc/policy/ai/tool_call_env.yaml new file mode 100644 index 00000000..c746613e --- /dev/null +++ b/doc/policy/ai/tool_call_env.yaml @@ -0,0 +1,85 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Inherits from the common_agent_env.yaml and extends it with additional +# variables for tool call support. + +name: "ai.tool_call_env" + +variables: + - name: "tool.name" + type_name: "string" + description: | + The name of the tool. + - name: "tool.description" + type_name: "string" + description: | + A description of the tool. + - name: "tool.input_schema" + type_name: "google.protobuf.Struct" + description: | + The JSON schema for the tool input parameters. + - name: "tool.output_schema" + type_name: "google.protobuf.Struct" + description: | + The JSON schema for the tool output. + - name: "tool.annotations" + type_name: "cel.expr.ai.ToolAnnotations" + description: | + Well-defined annotations about tool behavior. + Valid tool properties include: + - tool.annotations.read_only: Whether the tool is read-only. + - tool.annotations.destructive: Whether the tool is destructive. + - tool.annotations.idempotent: Whether the tool is idempotent. + - tool.annotations.open_world: Whether the tool interacts with the public internet. + - tool.annotations.async: Whether the tool is asynchronous. + - tool.annotations.output_trust.level: String representation of the output trust level. + - name: "tool.metadata" + type_name: "google.protobuf.Struct" + description: | + Dynamic metadata about the tool referenced within the policy. + + - name: "tool.call" + type_name: "cel.expr.ai.ToolCall" + description: | + Information about the tool call parameters, and if the call has completed, the result. + Valid tool call properties include: + - tool.call.params: JSON representation of the arguments passed to the tool where the key + names correspond to properties in the tool.input_schema. + - tool.call.time: The RFC3339 timestamp when the tool call was made. + - tool.call.user_confirmed: Whether the user confirmed the tool call. + - tool.call.result: The result of the tool call of type 'ContentPart' + - tool.call.error: The error, if any, of the tool call. + + When interacting with a complete tool call e.g. `has(tool.call.result)`, the + `tool.call.result` field will contain the following fields: + - tool.call.result.type: The type of the result, e.g. 'text', 'json', 'file'. + - tool.call.result.mime_type: The MIME type of the result. + e.g. 'text/plain', 'application/json', 'image/png'. + - tool.call.result.content: The textual content of the result if the result.type is 'text'. + - tool.call.result.data: The binary data of the result, present if the result.type is + non-textual data such as when mime_type is 'image/png' is 'image/png'. + - tool.call.result.structured_content: The JSON representation of the result if the + result.type is 'json'. + + - name: "tool.provider" + type_name: "cel.expr.ai.ToolProvider" + description: | + The endpoint which provides the tool. + Valid tool provider properties include: + - tool.provider.url: The URL from which these tools were sourced. + - tool.provider.organization: The organization that provides the tool. + - tool.provider.authorization_server_url: The URL of the authorization server for the + tool provider to which a scoped authentication credential should be requested. + - tool.provider.supported_scopes: The scopes supported by the tool provider. From 957be73d7bce45b32c489da0f21dcdc0218d6ef5 Mon Sep 17 00:00:00 2001 From: TristonianJones Date: Tue, 10 Feb 2026 16:53:52 -0800 Subject: [PATCH 2/4] Fix the AI policy link --- doc/langdef.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langdef.md b/doc/langdef.md index 6d22352c..7caae995 100644 --- a/doc/langdef.md +++ b/doc/langdef.md @@ -3,7 +3,7 @@ This page constitutes the reference for CEL. For a gentle introduction, see [Intro](intro.md). For specific guidance regarding CEL in policy see the `doc/policy` folder contains best practices, such as attribute -specification and example environments for [AI Policy](doc/policy/ai/README.md) +specification and example environments for [AI Policy](policy/ai/README.md). ## Overview From 6f260c6e419ec5cb0f496f8ea905201fec60fa5d Mon Sep 17 00:00:00 2001 From: TristonianJones Date: Tue, 10 Feb 2026 16:55:02 -0800 Subject: [PATCH 3/4] Add agent_env.yaml --- doc/policy/ai/agent_env.yaml | 105 +++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/doc/policy/ai/agent_env.yaml b/doc/policy/ai/agent_env.yaml index e69de29b..19b261ab 100644 --- a/doc/policy/ai/agent_env.yaml +++ b/doc/policy/ai/agent_env.yaml @@ -0,0 +1,105 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Inherits from the common_agent_env.yaml and extends it with additional +# variables for tool call support. + +name: "ai.agent_env" + +variables: + - name: "agent.name" + type_name: "string" + description: | + The name of the agent. + - name: "agent.description" + type_name: "string" + description: | + A description of the agent. + - name: "agent.auth" + type_name: "cel.expr.ai.AgentAuth" + description: | + The authentication information for the agent. + Valid agent auth properties include: + - agent.auth.principal: The principal associated with the agent. + - agent.auth.claims: The claims asserted by the agent. + - agent.auth.oauth_scopes: The scopes required by the agent. + - name: "agent.context" + type_name: "cel.expr.ai.AgentContext" + description: | + The context of the agent which includes the user input, history, and other relevant data. + Valid agent context properties include: + - agent.context.prompt: string-typed representation of the prompt provided to the LLM, + combining system instructions, context, and user input. + - agent.context.trust.level: string-typed trust level of the agent context. + - agent.context.trust.findings: list of cel.expr.ai.Finding values which which contribute + to the trust level. + - agent.context.sources: list of sources referenced in the agent context where each source + contains a 'name' and 'value'. The name might be a resource name, the value could either + be a string, base64-encoded bytes, or uri if present. + - name: "agent.model" + type_name: "cel.expr.ai.Model" + description: | + The model used by the agent. + Valid agent model properties include: + - agent.model.name: The name of the model. + - name: "agent.provider" + type_name: "cel.expr.ai.AgentProvider" + description: | + The provider of the model used by the agent. + Valid agent provider properties include: + - agent.provider.url: The url where the agent can be found, either a service endpoint or + an agent card uri: "https:////.well-known/agent-card.json". + - agent.provider.organization: The organization which maintains the agent. + + - name: "agent.input" + type_name: "cel.expr.ai.AgentMessage" + description: | + The input to the agent, represented as a cel.expr.ai.AgentMessage. + Valid agent message properties include: + - agent.input.role: string role of the message, either 'user' or 'agent'. + - agent.input.metadata: dynamic metadata associated with the input. + - agent.input.time: The timestamp of the message. + - agent.input.parts: list of message parts provided in the input. + Each message part is a cel.expr.ai.AgentMessage.Part. + + To inspect the message parts use helper methods: + - agent.input.safetyFindings(): Returns the safety findings associated with the + parts with the given label name. + - agent.input.sensitivityFindings(): Returns the sensitivity findings + associated with the label for all parts of the message. + - agent.input.threatFindings(): Returns the threat findings associated with the + parts of the message. + parts with the given label name. + + - name: "agent.output" + type_name: "cel.expr.ai.AgentMessage" + description: | + The output to respond with from the agent, represented as a cel.expr.ai.AgentMessage. + Valid agent message properties include: + - agent.output.role: string role of the message, either 'user' or 'agent'. + - agent.output.metadata: dynamic metadata associated with the input. + - agent.output.time: The timestamp of the message. + - agent.output.parts: list of message parts provided in the input. + Each message part is a cel.expr.ai.AgentMessage.Part. + + To inpect the findings in the message parts use helper methods: + - agent.output.safetyFindings(): Returns the safety findings associated with the + parts with the given label name. + - agent.output.sensitivityFindings(): Returns the sensitivity findings + associated with the label for all parts of the message. + - agent.output.threatFindings(): Returns the threat findings associated with the + parts of the message. + + - name: "agent.history" + type_name: "cel.expr.ai.AgentMessageSet" \ No newline at end of file From 5a0b82d3511f9eacd0406a370ed6fa81c9493100 Mon Sep 17 00:00:00 2001 From: TristonianJones Date: Tue, 10 Feb 2026 16:56:36 -0800 Subject: [PATCH 4/4] Minor formatting change --- doc/policy/ai/README.md | 47 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/doc/policy/ai/README.md b/doc/policy/ai/README.md index ea550a4f..090c2a86 100644 --- a/doc/policy/ai/README.md +++ b/doc/policy/ai/README.md @@ -170,19 +170,19 @@ Ensure that sensitive data (like PII) is handled correctly across different part * **Retrieving Sensitivity Labels:** - // Get optional list of sensitivity findings for the 'pii' label. - agent.context.sensitivityFindings("pii") + // Get optional list of sensitivity findings for the 'pii' label. + agent.context.sensitivityFindings("pii") * **Restricting High-Confidence PII in Tool Calls:** - // Fail if any PII finding has a confidence score greater than 0.5. - tool.call.sensitivityFindings("pii").orValue(\[\]) + // Fail if any PII finding has a confidence score greater than 0.5. + tool.call.sensitivityFindings("pii").orValue(\[\]) .all(finding, finding.confidence \<= 0.5) * **Checking for Specific Data Types:** - // Returns true if the tool call contains both 'phone\_number' and 'ssn' findings. - tool.call.sensitivityFindings("pii").hasAll(\["phone\_number", "ssn"\]) + // Returns true if the tool call contains both 'phone\_number' and 'ssn' findings. + tool.call.sensitivityFindings("pii").hasAll(\["phone\_number", "ssn"\]) ### **2\. Safety & Threat Mitigation** @@ -190,13 +190,13 @@ Identify and block malicious inputs or unsafe model outputs. * **Detecting Prompt Injections or Jailbreaks:** - // Returns true if any specified threats are detected with high confidence. - agent.context.threatFindings().hasAll(\["injection", "jailbreak", "malicious\_uri"\]) + // Returns true if any specified threats are detected with high confidence. + agent.context.threatFindings().hasAll(\["injection", "jailbreak", "malicious\_uri"\]) * **Filtering Output for Responsible AI Safety:** - // Check if the model output contains hate speech or sexually explicit content. - agent.output.safetyFindings("responsible\_ai") + // Check if the model output contains hate speech or sexually explicit content. + agent.output.safetyFindings("responsible\_ai") .hasAll(\["hate\_speech", "sexually\_explicit"\]) ### **3\. Context & History Analysis** @@ -205,16 +205,16 @@ Analyze previous turns in the conversation to enforce policies over time. * **Filtering History by Role and Time:** - // Check for threats in user messages from the last 5 minutes. - agent.history - .role("user") - .after(now \- duration('5m')) - .threatFindings().hasAll(\["injection", "jailbreak"\]) + // Check for threats in user messages from the last 5 minutes. + agent.history + .role("user") + .after(now \- duration('5m')) + .threatFindings().hasAll(\["injection", "jailbreak"\]) * **Inspecting Tool History:** - // Find all JSON-based tool results in the agent's history. - agent.history.role("agent").toolCalls("get\_weather").resultType("json") + // Find all JSON-based tool results in the agent's history. + agent.history.role("agent").toolCalls("get\_weather").resultType("json") ### **4\. Advanced Tool Governance** @@ -222,17 +222,18 @@ Control how tools interact with the world based on their definitions. * **The "Lethal Trifecta" Check:** - // Block tool calls that are destructive, interact with the open world, and produce untrusted output. - agent.input.parts.exists(part, + // Block tool calls that are destructive, interact with the open world, and produce untrusted output. + agent.input.parts.exists(part, has(part.tool\_call) && \!part.tool\_call.spec().annotations.output\_trust.level in \['trusted', 'trusted\_1p'\] - ) + ) ### **5\. Utility Functions** * **Creating Manual Findings:** `ai.finding("picc_score", 0.5)` * **Casting Content:** `agent.input.parts[0].asType(bigquery.QueryRequest)` -* **Union of Findings:** Combine findings from context and tool calls, keeping the highest confidence score: +* **Union of Findings:** +Combine findings from context and tool calls, keeping the highest confidence score: - agent.context.sensitivityFindings("pii") - .union(tool.call.sensitivityFindings("pii")) + agent.context.sensitivityFindings("pii") + .union(tool.call.sensitivityFindings("pii"))