sondera-harness 0.7.0__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/PKG-INFO +1 -2
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/pyproject.toml +2 -2
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/__init__.py +2 -2
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/cedar/harness.py +57 -34
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/sondera/_grpc.py +5 -6
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/sondera/harness.py +0 -24
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violation_panel.py +12 -10
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violations_list.py +7 -9
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/types.py +8 -9
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/PKG-INFO +1 -2
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/requires.txt +0 -1
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/LICENSE +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/README.md +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/setup.cfg +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/__main__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/adk/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/adk/analyze.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/adk/plugin.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/cli.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/exceptions.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/abc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/cedar/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/cedar/schema.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/harness/sondera/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/langgraph/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/langgraph/analyze.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/langgraph/exceptions.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/langgraph/graph.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/langgraph/middleware.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.pyi +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2_grpc.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/py.typed +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/settings.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/strands/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/strands/analyze.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/strands/harness.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/app.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/app.tcss +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/screens/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/screens/adjudication.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/screens/agent.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/screens/trajectory.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/__init__.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/agent_card.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/agent_list.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_adjudications.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_trajectories.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/summary.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/tool_card.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violations_summary.py +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/SOURCES.txt +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/dependency_links.txt +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/entry_points.txt +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/top_level.txt +0 -0
- {sondera_harness-0.7.0 → sondera_harness-0.7.1}/tests/test_harness.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sondera-harness
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Sondera Harness SDK for Python - Agent governance and policy enforcement
|
|
5
5
|
Author-email: Sondera AI <sdk@sondera.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,7 +27,6 @@ Requires-Dist: cedar-python>=0.1.1
|
|
|
27
27
|
Requires-Dist: click>=8.0.0
|
|
28
28
|
Requires-Dist: click-default-group>=1.2.4
|
|
29
29
|
Requires-Dist: grpcio>=1.76.0
|
|
30
|
-
Requires-Dist: grpcio-tools>=1.76.0
|
|
31
30
|
Requires-Dist: httpx>=0.27.0
|
|
32
31
|
Requires-Dist: pydantic>=2.12.0
|
|
33
32
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sondera-harness"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.1"
|
|
8
8
|
description = "Sondera Harness SDK for Python - Agent governance and policy enforcement"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12,<3.15"
|
|
@@ -30,7 +30,6 @@ dependencies = [
|
|
|
30
30
|
"click>=8.0.0",
|
|
31
31
|
"click-default-group>=1.2.4",
|
|
32
32
|
"grpcio>=1.76.0",
|
|
33
|
-
"grpcio-tools>=1.76.0",
|
|
34
33
|
"httpx>=0.27.0",
|
|
35
34
|
"pydantic>=2.12.0",
|
|
36
35
|
"pydantic-settings>=2.12.0",
|
|
@@ -71,6 +70,7 @@ dev = [
|
|
|
71
70
|
"textual-dev>=1.8.0",
|
|
72
71
|
"ruff>=0.8.0",
|
|
73
72
|
"pyright",
|
|
73
|
+
"grpcio-tools>=1.76.0",
|
|
74
74
|
]
|
|
75
75
|
examples = [
|
|
76
76
|
"sondera-examples-adk",
|
|
@@ -68,8 +68,8 @@ from sondera.types import (
|
|
|
68
68
|
Content,
|
|
69
69
|
Decision,
|
|
70
70
|
Parameter,
|
|
71
|
-
PolicyAnnotation,
|
|
72
71
|
PolicyEngineMode,
|
|
72
|
+
PolicyMetadata,
|
|
73
73
|
PromptContent,
|
|
74
74
|
Role,
|
|
75
75
|
SourceCode,
|
|
@@ -112,7 +112,7 @@ __all__ = [
|
|
|
112
112
|
"AdjudicatedStep",
|
|
113
113
|
"AdjudicatedTrajectory",
|
|
114
114
|
"AdjudicationRecord",
|
|
115
|
-
"
|
|
115
|
+
"PolicyMetadata",
|
|
116
116
|
"Decision",
|
|
117
117
|
# Exceptions
|
|
118
118
|
"SonderaError",
|
|
@@ -15,6 +15,7 @@ from cedar import (
|
|
|
15
15
|
EntityUid,
|
|
16
16
|
PolicySet,
|
|
17
17
|
Request,
|
|
18
|
+
Response,
|
|
18
19
|
Schema,
|
|
19
20
|
)
|
|
20
21
|
from sondera.harness.abc import Harness as AbstractHarness
|
|
@@ -23,7 +24,7 @@ from sondera.types import (
|
|
|
23
24
|
Agent,
|
|
24
25
|
Content,
|
|
25
26
|
Decision,
|
|
26
|
-
|
|
27
|
+
PolicyMetadata,
|
|
27
28
|
PromptContent,
|
|
28
29
|
Role,
|
|
29
30
|
Stage,
|
|
@@ -95,7 +96,6 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
95
96
|
if policy_set is None:
|
|
96
97
|
raise ValueError("policy_set is required")
|
|
97
98
|
|
|
98
|
-
self._cedar_schema = schema
|
|
99
99
|
# Exclude None values when serializing to JSON for Cedar compatibility
|
|
100
100
|
self._schema = Schema.from_json(schema.model_dump_json(exclude_none=True))
|
|
101
101
|
|
|
@@ -105,10 +105,18 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
105
105
|
else:
|
|
106
106
|
self._policy_set = policy_set
|
|
107
107
|
|
|
108
|
+
seen_ids: set[str] = set()
|
|
108
109
|
for policy in self._policy_set.policies():
|
|
109
110
|
annotations = policy.annotations()
|
|
111
|
+
if "id" not in annotations:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Policy '{policy.id()}' is missing required @id annotation."
|
|
114
|
+
)
|
|
115
|
+
policy_id = annotations["id"]
|
|
116
|
+
if policy_id in seen_ids:
|
|
117
|
+
self._logger.warning(f"Duplicate policy @id: '{policy_id}'")
|
|
118
|
+
seen_ids.add(policy_id)
|
|
110
119
|
if "escalate" in annotations and str(policy.effect()) != "Forbid":
|
|
111
|
-
policy_id = annotations.get("id", policy.id())
|
|
112
120
|
raise ValueError(
|
|
113
121
|
f"Policy '{policy_id}' has @escalate but is not a forbid policy. "
|
|
114
122
|
"@escalate is only valid on forbid policies."
|
|
@@ -270,50 +278,65 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
270
278
|
decision=Decision.ALLOW,
|
|
271
279
|
reason="Non-tool content allowed by default",
|
|
272
280
|
)
|
|
273
|
-
assert request is not None, "Unexpected none request"
|
|
274
281
|
response = self._authorizer.is_authorized(request, self._policy_set)
|
|
275
|
-
|
|
276
|
-
return Adjudication(
|
|
277
|
-
decision=Decision.ALLOW,
|
|
278
|
-
reason=f"Allowed by policies: {response.reason}",
|
|
279
|
-
)
|
|
282
|
+
return self._convert_cedar_response_to_adjudication(response)
|
|
280
283
|
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
def _convert_cedar_response_to_adjudication(
|
|
285
|
+
self, response: Response
|
|
286
|
+
) -> Adjudication:
|
|
287
|
+
escalate_policies = []
|
|
288
|
+
non_escalate_policies = []
|
|
283
289
|
for internal_id in response.reason:
|
|
284
290
|
policy = self._policy_set.policy(internal_id)
|
|
285
291
|
if policy is None:
|
|
286
292
|
raise RuntimeError(f"Policy '{internal_id}' not found in policy set")
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
293
|
+
cedar_policy_annotations = policy.annotations()
|
|
294
|
+
non_default_annotations = {
|
|
295
|
+
k: v
|
|
296
|
+
for k, v in cedar_policy_annotations.items()
|
|
297
|
+
if k not in ("id", "reason", "escalate")
|
|
298
|
+
}
|
|
299
|
+
is_escalate = "escalate" in cedar_policy_annotations
|
|
300
|
+
policy_metadata = PolicyMetadata(
|
|
301
|
+
id=cedar_policy_annotations["id"],
|
|
302
|
+
description=cedar_policy_annotations.get("reason", ""),
|
|
303
|
+
escalate=is_escalate,
|
|
304
|
+
escalate_arg=cedar_policy_annotations.get("escalate", ""),
|
|
305
|
+
custom=non_default_annotations,
|
|
306
|
+
)
|
|
307
|
+
if is_escalate:
|
|
308
|
+
escalate_policies.append(policy_metadata)
|
|
290
309
|
else:
|
|
291
|
-
|
|
292
|
-
k: v
|
|
293
|
-
for k, v in policy_annotations.items()
|
|
294
|
-
if k not in ("id", "reason", "escalate")
|
|
295
|
-
}
|
|
296
|
-
annotations.append(
|
|
297
|
-
PolicyAnnotation(
|
|
298
|
-
id=policy_annotations.get("id", internal_id),
|
|
299
|
-
description=policy_annotations.get("reason", ""),
|
|
300
|
-
escalate=True,
|
|
301
|
-
escalate_arg=policy_annotations["escalate"],
|
|
302
|
-
custom=custom,
|
|
303
|
-
)
|
|
304
|
-
)
|
|
310
|
+
non_escalate_policies.append(policy_metadata)
|
|
305
311
|
|
|
306
|
-
if
|
|
312
|
+
if str(response.decision) == "Allow":
|
|
313
|
+
# The @escalate annotation is only valid for `forbid` Cedar policies, so we can
|
|
314
|
+
# just return the non-escalate policies here:
|
|
307
315
|
return Adjudication(
|
|
308
|
-
decision=Decision.
|
|
309
|
-
reason=
|
|
310
|
-
|
|
316
|
+
decision=Decision.ALLOW,
|
|
317
|
+
reason="Allowed by all policies",
|
|
318
|
+
policies=non_escalate_policies,
|
|
311
319
|
)
|
|
312
|
-
|
|
320
|
+
# At this point we know the Cedar decision was DENY, so we just need to figure out if the
|
|
321
|
+
# final decision should be a hard DENY or else ESCALATE.
|
|
322
|
+
if non_escalate_policies:
|
|
313
323
|
return Adjudication(
|
|
314
324
|
decision=Decision.DENY,
|
|
315
|
-
reason=
|
|
325
|
+
reason="Denied by policies",
|
|
326
|
+
policies=non_escalate_policies,
|
|
316
327
|
)
|
|
328
|
+
if escalate_policies:
|
|
329
|
+
return Adjudication(
|
|
330
|
+
decision=Decision.ESCALATE,
|
|
331
|
+
reason="Escalated by policies",
|
|
332
|
+
policies=escalate_policies,
|
|
333
|
+
)
|
|
334
|
+
# Default deny because no policies matched (neither permit nor forbid/escalate)
|
|
335
|
+
return Adjudication(
|
|
336
|
+
decision=Decision.DENY,
|
|
337
|
+
reason="No matching permit policy",
|
|
338
|
+
policies=[],
|
|
339
|
+
)
|
|
317
340
|
|
|
318
341
|
def _message_request(
|
|
319
342
|
self,
|
|
@@ -16,8 +16,8 @@ from sondera.types import (
|
|
|
16
16
|
Decision,
|
|
17
17
|
GuardrailContext,
|
|
18
18
|
Parameter,
|
|
19
|
-
PolicyAnnotation,
|
|
20
19
|
PolicyEngineMode,
|
|
20
|
+
PolicyMetadata,
|
|
21
21
|
PromptContent,
|
|
22
22
|
Role,
|
|
23
23
|
SourceCode,
|
|
@@ -127,9 +127,9 @@ def _convert_pb_adjudication_to_sdk(
|
|
|
127
127
|
primitives_pb2.DECISION_ESCALATE: Decision.ESCALATE,
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
# Convert annotations
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
# Convert annotations to PolicyMetadata
|
|
131
|
+
policies = [
|
|
132
|
+
PolicyMetadata(
|
|
133
133
|
id=ann.id if ann.HasField("id") else "",
|
|
134
134
|
description=ann.description if ann.HasField("description") else "",
|
|
135
135
|
custom=dict(ann.custom),
|
|
@@ -140,8 +140,7 @@ def _convert_pb_adjudication_to_sdk(
|
|
|
140
140
|
return Adjudication(
|
|
141
141
|
decision=decision_map[adjudication.decision],
|
|
142
142
|
reason=adjudication.reason,
|
|
143
|
-
|
|
144
|
-
annotations=annotations,
|
|
143
|
+
policies=policies,
|
|
145
144
|
)
|
|
146
145
|
|
|
147
146
|
|
|
@@ -562,30 +562,6 @@ class SonderaRemoteHarness(AbstractHarness):
|
|
|
562
562
|
)
|
|
563
563
|
raise
|
|
564
564
|
|
|
565
|
-
async def _list_trajectory_steps(
|
|
566
|
-
self, trajectory_id: str
|
|
567
|
-
) -> list[primitives_pb2.AdjudicatedStep]:
|
|
568
|
-
"""List trajectory steps internally."""
|
|
569
|
-
request = harness_pb2.GetTrajectoryRequest(
|
|
570
|
-
trajectory_id=trajectory_id,
|
|
571
|
-
)
|
|
572
|
-
await self._ensure_connected()
|
|
573
|
-
assert self._stub is not None, "Client not connected"
|
|
574
|
-
|
|
575
|
-
# Inject organization_id and auth metadata
|
|
576
|
-
metadata = self._get_metadata()
|
|
577
|
-
|
|
578
|
-
try:
|
|
579
|
-
response = await self._stub.GetTrajectory(request, metadata=metadata)
|
|
580
|
-
return list(response.steps)
|
|
581
|
-
except grpc.aio.AioRpcError as e:
|
|
582
|
-
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
583
|
-
return []
|
|
584
|
-
logging.error(
|
|
585
|
-
f"Failed to list trajectory steps for {trajectory_id}: {e.code()} - {e.details()}"
|
|
586
|
-
)
|
|
587
|
-
raise
|
|
588
|
-
|
|
589
565
|
async def _list_agents(
|
|
590
566
|
self,
|
|
591
567
|
provider_id: str | None = None,
|
|
@@ -53,20 +53,22 @@ class ViolationPanel(Widget):
|
|
|
53
53
|
with Container(classes="reason-container"):
|
|
54
54
|
yield Markdown(record.adjudication.reason, classes="reason-text")
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
yield Static("[bold]
|
|
56
|
+
# Policies section
|
|
57
|
+
policies = record.adjudication.policies
|
|
58
|
+
if policies:
|
|
59
|
+
yield Static("[bold]Policies[/bold]", classes="section-header")
|
|
60
60
|
with VerticalScroll(classes="annotations-container"):
|
|
61
|
-
for
|
|
61
|
+
for policy in policies:
|
|
62
62
|
with Container(classes="annotation-card"):
|
|
63
|
-
yield Static(
|
|
64
|
-
|
|
63
|
+
yield Static(
|
|
64
|
+
f"[bold]{policy.id}[/bold]", classes="annotation-id"
|
|
65
|
+
)
|
|
66
|
+
if policy.description:
|
|
65
67
|
yield Static(
|
|
66
|
-
|
|
68
|
+
policy.description, classes="annotation-description"
|
|
67
69
|
)
|
|
68
|
-
if
|
|
70
|
+
if policy.custom:
|
|
69
71
|
with Grid(classes="annotation-custom-grid"):
|
|
70
|
-
for key, value in
|
|
72
|
+
for key, value in policy.custom.items():
|
|
71
73
|
yield Static(f"{key}:", classes="label")
|
|
72
74
|
yield Static(value, classes="value")
|
|
@@ -44,15 +44,13 @@ class ViolationsList(Widget):
|
|
|
44
44
|
reason = record.adjudication.reason
|
|
45
45
|
reason_display = reason[:40] + "..." if len(reason) > 43 else reason
|
|
46
46
|
|
|
47
|
-
# Format
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if len(annotations_display) > 30:
|
|
53
|
-
annotations_display = annotations_display[:27] + "..."
|
|
47
|
+
# Format policies as comma-separated policy IDs
|
|
48
|
+
if record.adjudication.policies:
|
|
49
|
+
policies_display = ", ".join(p.id for p in record.adjudication.policies)
|
|
50
|
+
if len(policies_display) > 30:
|
|
51
|
+
policies_display = policies_display[:27] + "..."
|
|
54
52
|
else:
|
|
55
|
-
|
|
53
|
+
policies_display = "-"
|
|
56
54
|
|
|
57
55
|
table.add_row(
|
|
58
56
|
decision_display,
|
|
@@ -66,7 +64,7 @@ class ViolationsList(Widget):
|
|
|
66
64
|
if len(record.step_id) > 11
|
|
67
65
|
else record.step_id,
|
|
68
66
|
reason_display,
|
|
69
|
-
|
|
67
|
+
policies_display,
|
|
70
68
|
)
|
|
71
69
|
|
|
72
70
|
def get_selected_adjudication(self) -> AdjudicationRecord | None:
|
|
@@ -231,19 +231,19 @@ class GuardrailContext(Model):
|
|
|
231
231
|
"""Map of check name to check result."""
|
|
232
232
|
|
|
233
233
|
|
|
234
|
-
class
|
|
235
|
-
"""
|
|
234
|
+
class PolicyMetadata(Model):
|
|
235
|
+
"""Metadata about a policy that contributed to an adjudication decision."""
|
|
236
236
|
|
|
237
237
|
id: str
|
|
238
|
-
"""Unique identifier of the policy
|
|
238
|
+
"""Unique identifier of the policy."""
|
|
239
239
|
description: str
|
|
240
|
-
"""Human-readable description
|
|
240
|
+
"""Human-readable description from the policy's @reason annotation."""
|
|
241
241
|
escalate: bool = False
|
|
242
242
|
"""Whether this policy requires escalation to a human or other oracle to decide the final verdict."""
|
|
243
243
|
escalate_arg: str = ""
|
|
244
244
|
"""The argument passed to @escalate, if any."""
|
|
245
245
|
custom: dict[str, str] = Field(default_factory=dict)
|
|
246
|
-
"""Custom key-value metadata from the policy."""
|
|
246
|
+
"""Custom key-value metadata from the policy's annotations."""
|
|
247
247
|
|
|
248
248
|
|
|
249
249
|
class Adjudication(Model):
|
|
@@ -253,10 +253,9 @@ class Adjudication(Model):
|
|
|
253
253
|
"""Whether the input is allowed."""
|
|
254
254
|
reason: str
|
|
255
255
|
"""Reason for the adjudication decision."""
|
|
256
|
-
|
|
257
|
-
"""
|
|
258
|
-
|
|
259
|
-
"""Annotations from policy evaluations."""
|
|
256
|
+
policies: list[PolicyMetadata] = Field(default_factory=list)
|
|
257
|
+
"""Policies that determined this decision. Each entry contains the policy's
|
|
258
|
+
id, description (from @reason annotation), escalation info, and custom metadata."""
|
|
260
259
|
|
|
261
260
|
|
|
262
261
|
class AdjudicatedStep(Model):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sondera-harness
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Sondera Harness SDK for Python - Agent governance and policy enforcement
|
|
5
5
|
Author-email: Sondera AI <sdk@sondera.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,7 +27,6 @@ Requires-Dist: cedar-python>=0.1.1
|
|
|
27
27
|
Requires-Dist: click>=8.0.0
|
|
28
28
|
Requires-Dist: click-default-group>=1.2.4
|
|
29
29
|
Requires-Dist: grpcio>=1.76.0
|
|
30
|
-
Requires-Dist: grpcio-tools>=1.76.0
|
|
31
30
|
Requires-Dist: httpx>=0.27.0
|
|
32
31
|
Requires-Dist: pydantic>=2.12.0
|
|
33
32
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.pyi
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2_grpc.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.pyi
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2_grpc.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.pyi
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2_grpc.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/__init__.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/__init__.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/__init__.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_adjudications.py
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_trajectories.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violations_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{sondera_harness-0.7.0 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|