Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9f1791a
add new lluna client
namrataghadi-galileo May 6, 2026
8d2227d
fix the url
namrataghadi-galileo May 7, 2026
db02db8
Merge branch 'main' into feature/64546-add-new-luna-client
namrataghadi-galileo May 11, 2026
0cce0bf
feat(galileo): support internal scorer auth
namrataghadi-galileo May 12, 2026
dd252be
add auth and update schema
namrataghadi-galileo May 12, 2026
74fcbeb
fix(galileo): align luna scorer response schema
namrataghadi-galileo May 12, 2026
7b0a15d
update the schemas and corresponding tests
namrataghadi-galileo May 13, 2026
523524d
update the schemas for scorer
namrataghadi-galileo May 14, 2026
34f430d
update luna client schemas
namrataghadi-galileo May 14, 2026
ad0b2dc
fix tests
namrataghadi-galileo May 14, 2026
81cea04
remove unwanted fields
namrataghadi-galileo May 14, 2026
f3cf8f7
remove project_id from evaluator config
namrataghadi-galileo May 14, 2026
9cf463a
Merge branch 'main' into feature/64546-add-new-luna-client
namrataghadi-galileo May 15, 2026
025f96f
add evaluator context
namrataghadi-galileo May 15, 2026
15e7a01
remove evaluation context
namrataghadi-galileo May 15, 2026
a06d3f1
add tests for coverage
namrataghadi-galileo May 15, 2026
c8662f7
coverage
namrataghadi-galileo May 16, 2026
9a94bba
move coervagera
namrataghadi-galileo May 16, 2026
f0d11b7
failing test
namrataghadi-galileo May 16, 2026
f683dda
add input text that goes into controls evaluators
namrataghadi-galileo May 18, 2026
7210fc1
add docstring
namrataghadi-galileo May 18, 2026
81a5f1d
Merge branch 'main' into feature/64546-add-new-luna-client
namrataghadi-galileo May 19, 2026
2c1b488
Merge branch 'main' into feature/64546-add-new-luna-client
namrataghadi-galileo May 22, 2026
f513dac
address comments
namrataghadi-galileo May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 127 additions & 1 deletion engine/src/agent_control_engine/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,108 @@
# Max concurrent evaluations (limits task spawning overhead for large policies)
MAX_CONCURRENT_EVALUATIONS = int(os.environ.get("MAX_CONCURRENT_EVALUATIONS", "3"))

SELECTED_DATA_PREVIEW_MAX_CHARS = int(
os.environ.get("AGENT_CONTROL_SELECTED_DATA_PREVIEW_MAX_CHARS", "500")
)
SELECTED_DATA_PREVIEW_MAX_ITEMS = int(
os.environ.get("AGENT_CONTROL_SELECTED_DATA_PREVIEW_MAX_ITEMS", "20")
)
SELECTED_DATA_PREVIEW_MAX_DEPTH = int(
os.environ.get("AGENT_CONTROL_SELECTED_DATA_PREVIEW_MAX_DEPTH", "3")
)
_SENSITIVE_KEY_PARTS = (
"api_key",
"apikey",
"authorization",
"credential",
"password",
"secret",
"token",
)


def _env_flag(name: str, *, default: bool = False) -> bool:
"""Read a boolean environment flag."""
value = os.environ.get(name)
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on"}


def _is_sensitive_key(key: object) -> bool:
"""Return whether a mapping key is likely to contain a secret."""
normalized = str(key).lower()
return any(part in normalized for part in _SENSITIVE_KEY_PARTS)


def _truncate_string(value: str, max_chars: int) -> tuple[str, bool]:
"""Return a bounded string preview and whether it was truncated."""
if len(value) <= max_chars:
return value, False
if max_chars <= 3:
return value[:max_chars], True
return f"{value[: max_chars - 3]}...", True


def _selected_data_preview_value(
value: Any,
*,
depth: int = 0,
) -> tuple[Any, bool]:
"""Build a bounded, redacted preview of selected data."""
if depth >= SELECTED_DATA_PREVIEW_MAX_DEPTH:
return "<max depth reached>", True

if value is None or isinstance(value, bool | int | float):
return value, False

if isinstance(value, str):
return _truncate_string(value, SELECTED_DATA_PREVIEW_MAX_CHARS)

if isinstance(value, dict):
preview: dict[str, Any] = {}
truncated = len(value) > SELECTED_DATA_PREVIEW_MAX_ITEMS
for index, (key, item) in enumerate(value.items()):
if index >= SELECTED_DATA_PREVIEW_MAX_ITEMS:
break
preview_key = str(key)
if _is_sensitive_key(key):
preview[preview_key] = "<redacted>"
truncated = True
continue
preview_item, item_truncated = _selected_data_preview_value(
item,
depth=depth + 1,
)
preview[preview_key] = preview_item
truncated = truncated or item_truncated
return preview, truncated

if isinstance(value, list | tuple):
preview_items: list[Any] = []
truncated = len(value) > SELECTED_DATA_PREVIEW_MAX_ITEMS
for item in value[:SELECTED_DATA_PREVIEW_MAX_ITEMS]:
preview_item, item_truncated = _selected_data_preview_value(
item,
depth=depth + 1,
)
preview_items.append(preview_item)
truncated = truncated or item_truncated
return preview_items, truncated

text_preview, truncated = _truncate_string(str(value), SELECTED_DATA_PREVIEW_MAX_CHARS)
return text_preview, truncated


def _selected_data_preview(value: Any) -> dict[str, Any]:
"""Return UI-safe selector output details for evaluator-level inspection."""
preview, truncated = _selected_data_preview_value(value)
return {
"type": type(value).__name__,
"value": preview,
"truncated": truncated,
}


@functools.lru_cache(maxsize=256)
def _compile_regex(pattern: str) -> Any:
Expand Down Expand Up @@ -102,9 +204,16 @@ def __init__(
self,
controls: Sequence[ControlWithIdentity],
context: Literal["sdk", "server"] = "server",
*,
include_raw_selected_data: bool | None = None,
):
self.controls = controls
self.context = context
self.include_raw_selected_data = (
_env_flag("AGENT_CONTROL_INCLUDE_RAW_SELECTED_DATA")
if include_raw_selected_data is None
else include_raw_selected_data
)

@staticmethod
def _truncated_message(message: str | None) -> str | None:
Expand Down Expand Up @@ -224,6 +333,9 @@ async def _evaluate_leaf(
"message": self._truncated_message(result.message),
}
metadata = dict(result.metadata or {})
if self.include_raw_selected_data:
metadata["engine_selected_data"] = data
metadata["engine_selected_data_preview"] = _selected_data_preview(data)
metadata["condition_trace"] = trace
Comment on lines 335 to 339
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata = dict(result.metadata or {}) then metadata["selected_data"] = data unconditionally overwrites whatever the evaluator deliberately put under selected_data. Evaluators do return that key (see the mock in engine/tests/test_core.py:160), and an evaluator might want to ship a sanitized or transformed version. The contract is not currently clear about who owns the key — consider namespacing the engine-injected value (e.g. engine_selected_data) so it cannot collide with evaluator-supplied metadata.

return _ConditionEvaluation(
result=result.model_copy(update={"metadata": metadata}),
Expand Down Expand Up @@ -269,7 +381,21 @@ def _composite_metadata(
*,
matched: bool,
) -> dict[str, Any] | None:
"""Select stable child metadata to preserve on composite results."""
"""Select stable child metadata to preserve on composite results.

The engine_selected_data_preview value in this metadata is not all
evaluator inputs. It is the bounded selected value preview from the leaf
metadata the engine preserves for the final composite result:
- or where one child matches: engine_selected_data_preview comes from the
matching child.
- and where one child fails: engine_selected_data_preview comes from the
failing child.
- and where all children match: engine_selected_data_preview comes from the
first matching child, usually the first leaf.
- or where no children match: engine_selected_data_preview comes from the
first evaluated child.
- not: engine_selected_data_preview comes from its child.
"""
source_result: EvaluatorResult | None = None
if matched:
source_result = next(
Expand Down
Loading
Loading