sondera-harness 0.6.3__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.6.3 → sondera_harness-0.7.1}/PKG-INFO +7 -11
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/README.md +6 -9
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/pyproject.toml +2 -2
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/__init__.py +2 -2
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/adk/plugin.py +6 -5
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/cedar/harness.py +57 -34
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/sondera/_grpc.py +5 -6
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/sondera/harness.py +0 -24
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/langgraph/middleware.py +6 -5
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/strands/harness.py +5 -4
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violation_panel.py +12 -10
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violations_list.py +7 -9
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/types.py +8 -45
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/PKG-INFO +7 -11
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/requires.txt +0 -1
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/LICENSE +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/setup.cfg +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/__main__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/adk/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/adk/analyze.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/cli.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/exceptions.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/abc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/cedar/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/cedar/schema.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/harness/sondera/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/langgraph/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/langgraph/analyze.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/langgraph/exceptions.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/langgraph/graph.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.pyi +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2_grpc.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/py.typed +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/settings.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/strands/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/strands/analyze.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/app.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/app.tcss +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/screens/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/screens/adjudication.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/screens/agent.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/screens/trajectory.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/__init__.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/agent_card.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/agent_list.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_adjudications.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_trajectories.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/summary.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/tool_card.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violations_summary.py +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/SOURCES.txt +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/dependency_links.txt +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/entry_points.txt +0 -0
- {sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/top_level.txt +0 -0
- {sondera_harness-0.6.3 → 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.
|
|
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
|
|
@@ -47,11 +46,6 @@ Requires-Dist: strands-agents>=1.21.0; extra == "all"
|
|
|
47
46
|
Dynamic: license-file
|
|
48
47
|
|
|
49
48
|
<div align="center">
|
|
50
|
-
<picture>
|
|
51
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-dark.svg">
|
|
52
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-light.svg">
|
|
53
|
-
<img alt="Sondera" src="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-light.svg" height="60">
|
|
54
|
-
</picture>
|
|
55
49
|
|
|
56
50
|
<h1>Sondera Harness</h1>
|
|
57
51
|
|
|
@@ -66,7 +60,7 @@ Dynamic: license-file
|
|
|
66
60
|
·
|
|
67
61
|
<a href="https://github.com/sondera-ai/sondera-harness-python/tree/main/examples">Examples</a>
|
|
68
62
|
·
|
|
69
|
-
<a href="https://
|
|
63
|
+
<a href="https://join.slack.com/t/sonderacommunity/shared_invite/zt-3onw10qhj-5UNQ7EMuAbPk0nTwh_sNcw">Slack</a>
|
|
70
64
|
</p>
|
|
71
65
|
|
|
72
66
|
<p>
|
|
@@ -94,12 +88,16 @@ This policy stops your agent from running `rm -rf`, every time.
|
|
|
94
88
|
|
|
95
89
|
## Quickstart
|
|
96
90
|
|
|
91
|
+
> **Try it now:** [](https://colab.research.google.com/github/sondera-ai/sondera-harness-python/blob/main/docs/src/notebooks/quickstart.ipynb) - no install required.
|
|
92
|
+
|
|
97
93
|
### 1. Install
|
|
98
94
|
|
|
99
95
|
```bash
|
|
100
96
|
uv add "sondera-harness[langgraph]" # or: pip install "sondera-harness[langgraph]"
|
|
101
97
|
```
|
|
102
98
|
|
|
99
|
+
Works with [LangChain/LangGraph](https://docs.sondera.ai/integrations/langgraph/), [Google ADK](https://docs.sondera.ai/integrations/adk/), [Strands](https://docs.sondera.ai/integrations/strands/), and [custom agents](https://docs.sondera.ai/integrations/custom/).
|
|
100
|
+
|
|
103
101
|
### 2. Add to Your Agent (LangGraph)
|
|
104
102
|
|
|
105
103
|
```python
|
|
@@ -132,8 +130,6 @@ agent = create_agent(
|
|
|
132
130
|
)
|
|
133
131
|
```
|
|
134
132
|
|
|
135
|
-
Also supports [Google ADK](https://docs.sondera.ai/integrations/adk/), [Strands](https://docs.sondera.ai/integrations/strands/), and [custom integrations](https://docs.sondera.ai/integrations/custom/).
|
|
136
|
-
|
|
137
133
|
> [!NOTE]
|
|
138
134
|
> This example uses Sondera Platform ([free account](https://sondera.ai)), which also enables the TUI below. For local-only development, see [CedarPolicyHarness](https://docs.sondera.ai/integrations/custom/).
|
|
139
135
|
|
|
@@ -163,7 +159,7 @@ uv run sondera # or: sondera (if installed via pip)
|
|
|
163
159
|
|
|
164
160
|
## Community
|
|
165
161
|
|
|
166
|
-
- [
|
|
162
|
+
- [Slack](https://join.slack.com/t/sonderacommunity/shared_invite/zt-3onw10qhj-5UNQ7EMuAbPk0nTwh_sNcw) for questions and feedback
|
|
167
163
|
- [GitHub Issues](https://github.com/sondera-ai/sondera-harness-python/issues) for bugs
|
|
168
164
|
- [Contributing](CONTRIBUTING.md) for development setup
|
|
169
165
|
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-dark.svg">
|
|
4
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-light.svg">
|
|
5
|
-
<img alt="Sondera" src="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-light.svg" height="60">
|
|
6
|
-
</picture>
|
|
7
2
|
|
|
8
3
|
<h1>Sondera Harness</h1>
|
|
9
4
|
|
|
@@ -18,7 +13,7 @@
|
|
|
18
13
|
·
|
|
19
14
|
<a href="https://github.com/sondera-ai/sondera-harness-python/tree/main/examples">Examples</a>
|
|
20
15
|
·
|
|
21
|
-
<a href="https://
|
|
16
|
+
<a href="https://join.slack.com/t/sonderacommunity/shared_invite/zt-3onw10qhj-5UNQ7EMuAbPk0nTwh_sNcw">Slack</a>
|
|
22
17
|
</p>
|
|
23
18
|
|
|
24
19
|
<p>
|
|
@@ -46,12 +41,16 @@ This policy stops your agent from running `rm -rf`, every time.
|
|
|
46
41
|
|
|
47
42
|
## Quickstart
|
|
48
43
|
|
|
44
|
+
> **Try it now:** [](https://colab.research.google.com/github/sondera-ai/sondera-harness-python/blob/main/docs/src/notebooks/quickstart.ipynb) - no install required.
|
|
45
|
+
|
|
49
46
|
### 1. Install
|
|
50
47
|
|
|
51
48
|
```bash
|
|
52
49
|
uv add "sondera-harness[langgraph]" # or: pip install "sondera-harness[langgraph]"
|
|
53
50
|
```
|
|
54
51
|
|
|
52
|
+
Works with [LangChain/LangGraph](https://docs.sondera.ai/integrations/langgraph/), [Google ADK](https://docs.sondera.ai/integrations/adk/), [Strands](https://docs.sondera.ai/integrations/strands/), and [custom agents](https://docs.sondera.ai/integrations/custom/).
|
|
53
|
+
|
|
55
54
|
### 2. Add to Your Agent (LangGraph)
|
|
56
55
|
|
|
57
56
|
```python
|
|
@@ -84,8 +83,6 @@ agent = create_agent(
|
|
|
84
83
|
)
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
Also supports [Google ADK](https://docs.sondera.ai/integrations/adk/), [Strands](https://docs.sondera.ai/integrations/strands/), and [custom integrations](https://docs.sondera.ai/integrations/custom/).
|
|
88
|
-
|
|
89
86
|
> [!NOTE]
|
|
90
87
|
> This example uses Sondera Platform ([free account](https://sondera.ai)), which also enables the TUI below. For local-only development, see [CedarPolicyHarness](https://docs.sondera.ai/integrations/custom/).
|
|
91
88
|
|
|
@@ -115,7 +112,7 @@ uv run sondera # or: sondera (if installed via pip)
|
|
|
115
112
|
|
|
116
113
|
## Community
|
|
117
114
|
|
|
118
|
-
- [
|
|
115
|
+
- [Slack](https://join.slack.com/t/sonderacommunity/shared_invite/zt-3onw10qhj-5UNQ7EMuAbPk0nTwh_sNcw) for questions and feedback
|
|
119
116
|
- [GitHub Issues](https://github.com/sondera-ai/sondera-harness-python/issues) for bugs
|
|
120
117
|
- [Contributing](CONTRIBUTING.md) for development setup
|
|
121
118
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sondera-harness"
|
|
7
|
-
version = "0.
|
|
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",
|
|
@@ -23,6 +23,7 @@ from google.genai import types as genai_types
|
|
|
23
23
|
from sondera.adk.analyze import format
|
|
24
24
|
from sondera.harness import Harness
|
|
25
25
|
from sondera.types import (
|
|
26
|
+
Decision,
|
|
26
27
|
PromptContent,
|
|
27
28
|
Role,
|
|
28
29
|
Stage,
|
|
@@ -135,7 +136,7 @@ class SonderaHarnessPlugin(BasePlugin):
|
|
|
135
136
|
f"[SonderaHarness] User message adjudication for trajectory {self._harness.trajectory_id}"
|
|
136
137
|
)
|
|
137
138
|
|
|
138
|
-
if adjudication.
|
|
139
|
+
if adjudication.decision == Decision.DENY:
|
|
139
140
|
return genai_types.Content(
|
|
140
141
|
parts=[genai_types.Part(text=adjudication.reason)]
|
|
141
142
|
)
|
|
@@ -212,7 +213,7 @@ class SonderaHarnessPlugin(BasePlugin):
|
|
|
212
213
|
f"[SonderaHarness] Before model adjudication for trajectory {self._harness.trajectory_id}"
|
|
213
214
|
)
|
|
214
215
|
|
|
215
|
-
if adjudication.
|
|
216
|
+
if adjudication.decision == Decision.DENY:
|
|
216
217
|
return LlmResponse(
|
|
217
218
|
content=genai_types.Content(
|
|
218
219
|
parts=[genai_types.Part(text=adjudication.reason)]
|
|
@@ -254,7 +255,7 @@ class SonderaHarnessPlugin(BasePlugin):
|
|
|
254
255
|
f"[SonderaHarness] After model adjudication for trajectory {self._harness.trajectory_id}"
|
|
255
256
|
)
|
|
256
257
|
|
|
257
|
-
if adjudication.
|
|
258
|
+
if adjudication.decision == Decision.DENY:
|
|
258
259
|
return LlmResponse(
|
|
259
260
|
content=genai_types.Content(
|
|
260
261
|
parts=[genai_types.Part(text=adjudication.reason)]
|
|
@@ -296,7 +297,7 @@ class SonderaHarnessPlugin(BasePlugin):
|
|
|
296
297
|
f"[SonderaHarness] Before tool adjudication for trajectory {self._harness.trajectory_id}"
|
|
297
298
|
)
|
|
298
299
|
|
|
299
|
-
if adjudication.
|
|
300
|
+
if adjudication.decision == Decision.DENY:
|
|
300
301
|
return {"error": f"Tool blocked: {adjudication.reason}"}
|
|
301
302
|
return None
|
|
302
303
|
|
|
@@ -332,7 +333,7 @@ class SonderaHarnessPlugin(BasePlugin):
|
|
|
332
333
|
f"[SonderaHarness] After tool adjudication for trajectory {self._harness.trajectory_id}"
|
|
333
334
|
)
|
|
334
335
|
|
|
335
|
-
if adjudication.
|
|
336
|
+
if adjudication.decision == Decision.DENY:
|
|
336
337
|
return {"error": f"Tool result blocked: {adjudication.reason}"}
|
|
337
338
|
return None
|
|
338
339
|
|
|
@@ -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,
|
|
@@ -28,6 +28,7 @@ except ImportError:
|
|
|
28
28
|
|
|
29
29
|
from sondera.harness import Harness
|
|
30
30
|
from sondera.types import (
|
|
31
|
+
Decision,
|
|
31
32
|
PromptContent,
|
|
32
33
|
Role,
|
|
33
34
|
Stage,
|
|
@@ -173,7 +174,7 @@ class SonderaHarnessMiddleware(AgentMiddleware[State]):
|
|
|
173
174
|
f"[SonderaHarness] Before Agent Adjudication for trajectory {self._harness.trajectory_id}"
|
|
174
175
|
)
|
|
175
176
|
|
|
176
|
-
if adjudication.
|
|
177
|
+
if adjudication.decision == Decision.DENY:
|
|
177
178
|
self._log.warning(
|
|
178
179
|
f"[SonderaHarness] Policy violation detected (strategy={self._strategy.value}): "
|
|
179
180
|
f"{adjudication.reason}"
|
|
@@ -226,7 +227,7 @@ class SonderaHarnessMiddleware(AgentMiddleware[State]):
|
|
|
226
227
|
PromptContent(text=_message_to_text(request.messages[-1])),
|
|
227
228
|
)
|
|
228
229
|
|
|
229
|
-
if pre_adjudication.
|
|
230
|
+
if pre_adjudication.decision == Decision.DENY:
|
|
230
231
|
_LOGGER.warning(
|
|
231
232
|
f"[SonderaHarness] Pre-model policy violation (strategy={self._strategy.value}): "
|
|
232
233
|
f"{pre_adjudication.reason}"
|
|
@@ -259,7 +260,7 @@ class SonderaHarnessMiddleware(AgentMiddleware[State]):
|
|
|
259
260
|
self._log.info(
|
|
260
261
|
f"[SonderaHarness] Post-model Adjudication for trajectory {self._harness.trajectory_id}"
|
|
261
262
|
)
|
|
262
|
-
if post_adjudication.
|
|
263
|
+
if post_adjudication.decision == Decision.DENY:
|
|
263
264
|
self._log.warning(
|
|
264
265
|
f"[SonderaHarness] Post-model policy violation (strategy={self._strategy.value}): "
|
|
265
266
|
f"{post_adjudication.reason}"
|
|
@@ -324,7 +325,7 @@ class SonderaHarnessMiddleware(AgentMiddleware[State]):
|
|
|
324
325
|
f"[SonderaHarness] Before Tool Adjudication for trajectory {self._harness.trajectory_id}"
|
|
325
326
|
)
|
|
326
327
|
|
|
327
|
-
if pre_adjudication.
|
|
328
|
+
if pre_adjudication.decision == Decision.DENY:
|
|
328
329
|
self._log.warning(
|
|
329
330
|
f"[SonderaHarness] Pre-tool policy violation for {tool_name} "
|
|
330
331
|
f"(strategy={self._strategy.value}): {pre_adjudication.reason}"
|
|
@@ -367,7 +368,7 @@ class SonderaHarnessMiddleware(AgentMiddleware[State]):
|
|
|
367
368
|
f"[SonderaHarness] After Tool Adjudication for trajectory {self._harness.trajectory_id}"
|
|
368
369
|
)
|
|
369
370
|
|
|
370
|
-
if post_adjudication.
|
|
371
|
+
if post_adjudication.decision == Decision.DENY:
|
|
371
372
|
self._log.warning(
|
|
372
373
|
f"[SonderaHarness] Post-tool policy violation for {tool_name} "
|
|
373
374
|
f"(strategy={self._strategy.value}): {post_adjudication.reason}"
|
|
@@ -16,6 +16,7 @@ from strands.hooks.events import (
|
|
|
16
16
|
from sondera.harness import Harness
|
|
17
17
|
from sondera.strands.analyze import format_strands_agent
|
|
18
18
|
from sondera.types import (
|
|
19
|
+
Decision,
|
|
19
20
|
PromptContent,
|
|
20
21
|
Role,
|
|
21
22
|
Stage,
|
|
@@ -163,7 +164,7 @@ class SonderaHarnessHook(HookProvider):
|
|
|
163
164
|
f"[SonderaHarness] Before model adjudication for trajectory {self._harness.trajectory_id}"
|
|
164
165
|
)
|
|
165
166
|
|
|
166
|
-
if adjudication.
|
|
167
|
+
if adjudication.decision == Decision.DENY:
|
|
167
168
|
self._log.warning(
|
|
168
169
|
f"[SonderaHarness] Model call blocked: {adjudication.reason}"
|
|
169
170
|
)
|
|
@@ -192,7 +193,7 @@ class SonderaHarnessHook(HookProvider):
|
|
|
192
193
|
f"[SonderaHarness] After model adjudication for trajectory {self._harness.trajectory_id}"
|
|
193
194
|
)
|
|
194
195
|
|
|
195
|
-
if adjudication.
|
|
196
|
+
if adjudication.decision == Decision.DENY:
|
|
196
197
|
self._log.warning(
|
|
197
198
|
f"[SonderaHarness] Model response blocked: {adjudication.reason}"
|
|
198
199
|
)
|
|
@@ -231,7 +232,7 @@ class SonderaHarnessHook(HookProvider):
|
|
|
231
232
|
f"[SonderaHarness] Before tool adjudication for trajectory {self._harness.trajectory_id}"
|
|
232
233
|
)
|
|
233
234
|
|
|
234
|
-
if adjudication.
|
|
235
|
+
if adjudication.decision == Decision.DENY:
|
|
235
236
|
# Cancel the tool call using Strands' cancel_tool mechanism
|
|
236
237
|
event.cancel_tool = f"Tool blocked by policy: {adjudication.reason}"
|
|
237
238
|
self._log.warning(
|
|
@@ -262,7 +263,7 @@ class SonderaHarnessHook(HookProvider):
|
|
|
262
263
|
f"[SonderaHarness] After tool adjudication for trajectory {self._harness.trajectory_id}"
|
|
263
264
|
)
|
|
264
265
|
|
|
265
|
-
if adjudication.
|
|
266
|
+
if adjudication.decision == Decision.DENY:
|
|
266
267
|
# Modify the result to indicate policy violation
|
|
267
268
|
event.result = {
|
|
268
269
|
"content": [
|
|
@@ -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,25 +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."""
|
|
260
|
-
|
|
261
|
-
@property
|
|
262
|
-
def is_denied(self) -> bool:
|
|
263
|
-
"""Check if is denied."""
|
|
264
|
-
return self.decision == Decision.DENY
|
|
265
|
-
|
|
266
|
-
@property
|
|
267
|
-
def is_allowed(self) -> bool:
|
|
268
|
-
"""Check if allowed."""
|
|
269
|
-
return self.decision == Decision.ALLOW
|
|
270
|
-
|
|
271
|
-
@property
|
|
272
|
-
def is_escalated(self) -> bool:
|
|
273
|
-
"""Check if result requires escalation."""
|
|
274
|
-
return self.decision == Decision.ESCALATE
|
|
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."""
|
|
275
259
|
|
|
276
260
|
|
|
277
261
|
class AdjudicatedStep(Model):
|
|
@@ -286,27 +270,6 @@ class AdjudicatedStep(Model):
|
|
|
286
270
|
guardrails: GuardrailContext | None = None
|
|
287
271
|
"""Guardrail check results for this step."""
|
|
288
272
|
|
|
289
|
-
@property
|
|
290
|
-
def is_denied(self) -> bool:
|
|
291
|
-
"""Check if result is denied."""
|
|
292
|
-
return (
|
|
293
|
-
self.adjudication.decision == Decision.DENY
|
|
294
|
-
and self.mode == PolicyEngineMode.GOVERN
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
@property
|
|
298
|
-
def is_allowed(self) -> bool:
|
|
299
|
-
"""Check if result is allowed."""
|
|
300
|
-
return self.adjudication.decision == Decision.ALLOW
|
|
301
|
-
|
|
302
|
-
@property
|
|
303
|
-
def is_escalated(self) -> bool:
|
|
304
|
-
"""Check if result requires escalation."""
|
|
305
|
-
return (
|
|
306
|
-
self.adjudication.decision == Decision.ESCALATE
|
|
307
|
-
and self.mode == PolicyEngineMode.GOVERN
|
|
308
|
-
)
|
|
309
|
-
|
|
310
273
|
@property
|
|
311
274
|
def message(self) -> str:
|
|
312
275
|
"""Get the adjudication reason in a friendly format."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sondera-harness
|
|
3
|
-
Version: 0.
|
|
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
|
|
@@ -47,11 +46,6 @@ Requires-Dist: strands-agents>=1.21.0; extra == "all"
|
|
|
47
46
|
Dynamic: license-file
|
|
48
47
|
|
|
49
48
|
<div align="center">
|
|
50
|
-
<picture>
|
|
51
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-dark.svg">
|
|
52
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-light.svg">
|
|
53
|
-
<img alt="Sondera" src="https://raw.githubusercontent.com/sondera-ai/harness-sdk-python/main/assets/sondera-logo-light.svg" height="60">
|
|
54
|
-
</picture>
|
|
55
49
|
|
|
56
50
|
<h1>Sondera Harness</h1>
|
|
57
51
|
|
|
@@ -66,7 +60,7 @@ Dynamic: license-file
|
|
|
66
60
|
·
|
|
67
61
|
<a href="https://github.com/sondera-ai/sondera-harness-python/tree/main/examples">Examples</a>
|
|
68
62
|
·
|
|
69
|
-
<a href="https://
|
|
63
|
+
<a href="https://join.slack.com/t/sonderacommunity/shared_invite/zt-3onw10qhj-5UNQ7EMuAbPk0nTwh_sNcw">Slack</a>
|
|
70
64
|
</p>
|
|
71
65
|
|
|
72
66
|
<p>
|
|
@@ -94,12 +88,16 @@ This policy stops your agent from running `rm -rf`, every time.
|
|
|
94
88
|
|
|
95
89
|
## Quickstart
|
|
96
90
|
|
|
91
|
+
> **Try it now:** [](https://colab.research.google.com/github/sondera-ai/sondera-harness-python/blob/main/docs/src/notebooks/quickstart.ipynb) - no install required.
|
|
92
|
+
|
|
97
93
|
### 1. Install
|
|
98
94
|
|
|
99
95
|
```bash
|
|
100
96
|
uv add "sondera-harness[langgraph]" # or: pip install "sondera-harness[langgraph]"
|
|
101
97
|
```
|
|
102
98
|
|
|
99
|
+
Works with [LangChain/LangGraph](https://docs.sondera.ai/integrations/langgraph/), [Google ADK](https://docs.sondera.ai/integrations/adk/), [Strands](https://docs.sondera.ai/integrations/strands/), and [custom agents](https://docs.sondera.ai/integrations/custom/).
|
|
100
|
+
|
|
103
101
|
### 2. Add to Your Agent (LangGraph)
|
|
104
102
|
|
|
105
103
|
```python
|
|
@@ -132,8 +130,6 @@ agent = create_agent(
|
|
|
132
130
|
)
|
|
133
131
|
```
|
|
134
132
|
|
|
135
|
-
Also supports [Google ADK](https://docs.sondera.ai/integrations/adk/), [Strands](https://docs.sondera.ai/integrations/strands/), and [custom integrations](https://docs.sondera.ai/integrations/custom/).
|
|
136
|
-
|
|
137
133
|
> [!NOTE]
|
|
138
134
|
> This example uses Sondera Platform ([free account](https://sondera.ai)), which also enables the TUI below. For local-only development, see [CedarPolicyHarness](https://docs.sondera.ai/integrations/custom/).
|
|
139
135
|
|
|
@@ -163,7 +159,7 @@ uv run sondera # or: sondera (if installed via pip)
|
|
|
163
159
|
|
|
164
160
|
## Community
|
|
165
161
|
|
|
166
|
-
- [
|
|
162
|
+
- [Slack](https://join.slack.com/t/sonderacommunity/shared_invite/zt-3onw10qhj-5UNQ7EMuAbPk0nTwh_sNcw) for questions and feedback
|
|
167
163
|
- [GitHub Issues](https://github.com/sondera-ai/sondera-harness-python/issues) for bugs
|
|
168
164
|
- [Contributing](CONTRIBUTING.md) for development setup
|
|
169
165
|
|
|
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.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2.pyi
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/any_pb2_grpc.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/duration_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2.pyi
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/empty_pb2_grpc.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2.pyi
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/struct_pb2_grpc.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/timestamp_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/google/protobuf/wrappers_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → 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.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/__init__.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/core/v1/primitives_pb2.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/__init__.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/__init__.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/proto/sondera/harness/v1/harness_pb2.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → 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
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/recent_adjudications.py
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → 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.6.3 → sondera_harness-0.7.1}/src/sondera/tui/widgets/violations_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{sondera_harness-0.6.3 → sondera_harness-0.7.1}/src/sondera_harness.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|