Compare commits

...

3 Commits

Author SHA1 Message Date
Tomu Hirata 279b7ad95c fix: downgrade cwsandbox dependency to version 0.24.0
Updated the `pyproject.toml` and `uv.lock` files to reflect the change in the `cwsandbox` dependency version from 0.26.0 to 0.24.0. This ensures compatibility with other dependencies and resolves potential issues related to the newer version.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-06-16 18:00:12 +09:00
Tomu Hirata 85d6a52d02 style: fix E402 and reformat POLICY_REGISTRY assignment
Co-authored-by: Isaac
2026-06-16 17:47:56 +09:00
Tomu Hirata e9562f39ea fix: handle missing cel-expr-python on Linux aarch64 (#300)
`cel-expr-python` has no manylinux_aarch64 wheel, so `pip install
omnigent` fails on ARM64 Linux (Graviton, Cobalt, RPi, etc.) even
when the user never uses CEL policies.

- Add `platform_machine != "aarch64"` marker so the dependency is
  skipped on Linux ARM64 (macOS arm64 is unaffected — different tag).
- Lazy-import `cel_expr_python` so the module loads without it.
- Empty `POLICY_REGISTRY` when the library is absent so CEL policies
  are not advertised.
- `pytest.importorskip` in tests so the suite passes on ARM64.

Closes #300

Co-authored-by: Isaac
2026-06-16 17:45:46 +09:00
4 changed files with 75 additions and 58 deletions
+62 -49
View File
@@ -31,7 +31,10 @@ from __future__ import annotations
import logging
from typing import Any
from cel_expr_python import cel as _cel
try:
from cel_expr_python import cel as _cel
except ImportError:
_cel = None # type: ignore[assignment]
from omnigent.policies.schema import PolicyCallable, PolicyEvent, PolicyResponse
@@ -66,6 +69,12 @@ def cel_policy(
:class:`PolicyCallable` contract.
:raises ValueError: If the expression has CEL syntax errors.
"""
if _cel is None:
raise ImportError(
"cel-expr-python is required for CEL policies but is not installed. "
"Install it with: pip install cel-expr-python"
)
env = _cel.NewEnv(variables={"event": _cel.Type.DYN})
try:
compiled = env.compile(expression)
@@ -120,54 +129,58 @@ def cel_policy(
# ── Registry ─────────────────────────────────────────────────────────────────
POLICY_REGISTRY: list[dict[str, Any]] = [
{
"handler": "omnigent.policies.builtins.cel.cel_policy",
"kind": "factory",
"name": "CEL Expression Policy",
"description": (
"Evaluate a CEL (Common Expression Language) expression against "
"every policy event. The expression receives the full event as "
'`event` and must return a map with `result` ("DENY", "ASK", or '
'"ALLOW") and optional `reason` keys. '
"CEL is non-Turing-complete and side-effect-free."
),
"params_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": (
"CEL expression. The `event` variable holds the PolicyEvent dict. "
"Must return a map: "
'{"result": "DENY"|"ASK"|"ALLOW", "reason": "..."}. '
"Event fields: "
'event.type ("request"|"tool_call"|"tool_result"|'
'"response"|"llm_request"|"llm_response"|"output_logged"); '
"event.target (tool name on tool_call/tool_result, null otherwise); "
"event.data (phase-specific: string for request/response, "
'{"name": str, "arguments": map} for tool_call, '
'{"result": any} for tool_result, '
'{"model": str, "messages_count": int, "tools_count": int,'
' "system_prompt_preview": str, "last_user_message": str}'
" for llm_request); "
"event.context.actor.run_as (user email); "
"event.context.usage.total_cost_usd (session spend). "
"Example: "
'event.type == "tool_call" && event.data.name == "sys_os_shell" '
'? {"result": "DENY", "reason": "Shell blocked."} '
': {"result": "ALLOW"}'
),
},
"reason": {
"type": "string",
"description": (
"Fallback reason for DENY/ASK when the map omits a reason key."
),
"default": "Denied by policy.",
POLICY_REGISTRY: list[dict[str, Any]] = (
[]
if _cel is None
else [
{
"handler": "omnigent.policies.builtins.cel.cel_policy",
"kind": "factory",
"name": "CEL Expression Policy",
"description": (
"Evaluate a CEL (Common Expression Language) expression against "
"every policy event. The expression receives the full event as "
'`event` and must return a map with `result` ("DENY", "ASK", or '
'"ALLOW") and optional `reason` keys. '
"CEL is non-Turing-complete and side-effect-free."
),
"params_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": (
"CEL expression. The `event` variable holds the PolicyEvent dict. "
"Must return a map: "
'{"result": "DENY"|"ASK"|"ALLOW", "reason": "..."}. '
"Event fields: "
'event.type ("request"|"tool_call"|"tool_result"|'
'"response"|"llm_request"|"llm_response"|"output_logged"); '
"event.target (tool name on tool_call/tool_result, null otherwise); "
"event.data (phase-specific: string for request/response, "
'{"name": str, "arguments": map} for tool_call, '
'{"result": any} for tool_result, '
'{"model": str, "messages_count": int, "tools_count": int,'
' "system_prompt_preview": str, "last_user_message": str}'
" for llm_request); "
"event.context.actor.run_as (user email); "
"event.context.usage.total_cost_usd (session spend). "
"Example: "
'event.type == "tool_call" && event.data.name == "sys_os_shell" '
'? {"result": "DENY", "reason": "Shell blocked."} '
': {"result": "ALLOW"}'
),
},
"reason": {
"type": "string",
"description": (
"Fallback reason for DENY/ASK when the map omits a reason key."
),
"default": "Denied by policy.",
},
},
"required": ["expression"],
},
"required": ["expression"],
},
},
]
]
)
+4 -2
View File
@@ -42,7 +42,9 @@ dependencies = [
"psutil>=5.9,<8",
# CEL (Common Expression Language) for inline policy evaluation.
# Non-Turing-complete, side-effect-free, guaranteed to terminate.
"cel-expr-python>=0.1",
# No ARM64 Linux wheel; excluded on aarch64 so installation succeeds.
# The CEL policy module degrades gracefully when the library is absent.
'cel-expr-python>=0.1; platform_machine != "aarch64"',
# OS keychain for storing model-provider API keys (OSS model
# selection). Used by omnigent/onboarding/secrets.py; falls back to
# a 0600 file when no keyring backend is available (e.g. headless).
@@ -113,7 +115,7 @@ daytona = ["daytona>=0.180,<1"]
# CoreWeave Sandbox launcher (`omnigent sandbox --provider cwsandbox` and
# server-managed `sandbox.provider: cwsandbox`).
# Same lazy-import posture as above. Pre-1.0 SDK: pin minor.
cwsandbox = ["cwsandbox>=0.26,<1"]
cwsandbox = ["cwsandbox>=0.24,<1"]
# MLflow tracing is opt-in: tracing is disabled by default and the
# server degrades gracefully when mlflow is absent, so the (heavy) mlflow
# dependency only installs for users who want it (`omnigent[tracing]`).
+3 -1
View File
@@ -4,7 +4,9 @@ from __future__ import annotations
import pytest
from omnigent.policies.builtins.cel import cel_policy
cel = pytest.importorskip("cel_expr_python", reason="cel-expr-python not installed")
from omnigent.policies.builtins.cel import cel_policy # noqa: E402
# ── Map return: DENY ────────────────────────────────────────────
Generated
+6 -6
View File
@@ -831,16 +831,16 @@ wheels = [
[[package]]
name = "cwsandbox"
version = "0.26.0"
version = "0.24.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "googleapis-common-protos" },
{ name = "grpcio" },
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ef/d9/540f20685c878f86f8588bff841618e1a125bfb13c8ba4071fe9e041477d/cwsandbox-0.26.0.tar.gz", hash = "sha256:541fbef6fd5cf7b70e2692bf3f35a2ae8f2a577e5caf29162dececb42e973616", size = 489383, upload-time = "2026-06-11T13:09:00.329Z" }
sdist = { url = "https://files.pythonhosted.org/packages/26/84/b77bd2e551809251f9cd39d9aac6c50410202566ff51f9bf416ced462d57/cwsandbox-0.24.0.tar.gz", hash = "sha256:067a95696d8236197675e3127e75813b10ddad0fbff5e63c7a96c274e7f4b4cc", size = 429780, upload-time = "2026-05-26T19:31:28.397Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/67/61/ac962e3c59d3c349046d204a67c5f301b10ebab4c413b079862fd234a7d5/cwsandbox-0.26.0-py3-none-any.whl", hash = "sha256:88143a5959a93f1a0145a7897d57caabe9c488051e287f185da1a83451896105", size = 178221, upload-time = "2026-06-11T13:08:58.751Z" },
{ url = "https://files.pythonhosted.org/packages/cd/2e/ce3063c8e0b3f3c6a9e8a81bc39ff77ae38e353ad396cb299afc7c05e5a0/cwsandbox-0.24.0-py3-none-any.whl", hash = "sha256:81fb174c8fe692ef46c9884e58d8062c0f0d2a56783790fb99a8ff4b31d84328", size = 148297, upload-time = "2026-05-26T19:31:26.534Z" },
]
[[package]]
@@ -2640,7 +2640,7 @@ dependencies = [
{ name = "anyio" },
{ name = "argon2-cffi" },
{ name = "cachetools" },
{ name = "cel-expr-python" },
{ name = "cel-expr-python", marker = "platform_machine != 'aarch64'" },
{ name = "claude-agent-sdk" },
{ name = "click" },
{ name = "cursor-sdk" },
@@ -2736,11 +2736,11 @@ requires-dist = [
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.30,<2" },
{ name = "botocore", marker = "extra == 'bedrock'", specifier = ">=1.30,<2" },
{ name = "cachetools", specifier = ">=5.0,<7" },
{ name = "cel-expr-python", specifier = ">=0.1" },
{ name = "cel-expr-python", marker = "platform_machine != 'aarch64'", specifier = ">=0.1" },
{ name = "claude-agent-sdk", specifier = ">=0.1.62" },
{ name = "click", specifier = ">=8.0,<8.2" },
{ name = "cursor-sdk", specifier = ">=0.1.7" },
{ name = "cwsandbox", marker = "extra == 'cwsandbox'", specifier = ">=0.26,<1" },
{ name = "cwsandbox", marker = "extra == 'cwsandbox'", specifier = ">=0.24,<1" },
{ name = "databricks-mcp", marker = "extra == 'databricks'", specifier = ">=0.1.0" },
{ name = "databricks-sdk", marker = "extra == 'all'", specifier = ">=0.56.0,<1" },
{ name = "databricks-sdk", marker = "extra == 'databricks'", specifier = ">=0.56.0,<1" },