sondera-harness 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl
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/__init__.py +35 -15
- sondera/adk/plugin.py +1 -1
- sondera/harness/cedar/harness.py +90 -19
- sondera/harness/sondera/_grpc.py +78 -5
- sondera/harness/sondera/harness.py +3 -18
- sondera/proto/sondera/core/v1/primitives_pb2.py +42 -42
- sondera/proto/sondera/core/v1/primitives_pb2.pyi +8 -2
- sondera/types.py +26 -0
- sondera_harness-0.6.3.dist-info/METADATA +172 -0
- {sondera_harness-0.6.1.dist-info → sondera_harness-0.6.3.dist-info}/RECORD +14 -14
- {sondera_harness-0.6.1.dist-info → sondera_harness-0.6.3.dist-info}/WHEEL +1 -1
- {sondera_harness-0.6.1.dist-info → sondera_harness-0.6.3.dist-info}/entry_points.txt +1 -0
- sondera_harness-0.6.1.dist-info/METADATA +0 -323
- {sondera_harness-0.6.1.dist-info → sondera_harness-0.6.3.dist-info}/licenses/LICENSE +0 -0
- {sondera_harness-0.6.1.dist-info → sondera_harness-0.6.3.dist-info}/top_level.txt +0 -0
sondera/__init__.py
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
|
-
"""Sondera
|
|
1
|
+
"""Sondera Harness - Steer agents with rules, not prompts.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Wrap your agent, write Cedar policies, ship with confidence. When a policy
|
|
4
|
+
denies an action, the agent gets the reason why and adjusts. Agents self-correct
|
|
5
|
+
instead of failing. This is steering, not just blocking.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Same input, same verdict. Rules are deterministic, not probabilistic. Stop
|
|
8
|
+
debugging prompts and start writing policies.
|
|
9
|
+
|
|
10
|
+
Why Sondera Harness:
|
|
11
|
+
- Steer, don't just block: Denied actions include explanations
|
|
12
|
+
- Drop-in integration: Native middleware for LangGraph, ADK, Strands
|
|
13
|
+
- Full observability: Trajectories capture every action and decision
|
|
14
|
+
- Deterministic rules: Same input, same verdict, every time
|
|
15
|
+
- Ship faster: Reliability, safety, security, and compliance built in
|
|
16
|
+
|
|
17
|
+
Harness Implementations:
|
|
18
|
+
- CedarPolicyHarness: Local evaluation, no network calls, no dependencies
|
|
19
|
+
- SonderaRemoteHarness: Team policies, dashboards, centralized audit logs
|
|
10
20
|
|
|
11
21
|
Framework Integrations:
|
|
12
|
-
- sondera.langgraph: LangGraph
|
|
22
|
+
- sondera.langgraph: LangGraph middleware
|
|
13
23
|
- sondera.adk: Google ADK plugin
|
|
14
|
-
- sondera.strands: Strands
|
|
24
|
+
- sondera.strands: Strands lifecycle hooks
|
|
15
25
|
|
|
16
26
|
Example:
|
|
17
|
-
>>> from sondera import
|
|
18
|
-
>>> harness
|
|
27
|
+
>>> from sondera import CedarPolicyHarness, Agent, Tool
|
|
28
|
+
>>> from sondera.harness.cedar.schema import agent_to_cedar_schema
|
|
29
|
+
>>>
|
|
19
30
|
>>> agent = Agent(
|
|
20
31
|
... id="my-agent",
|
|
21
|
-
... provider_id="
|
|
22
|
-
... name="
|
|
32
|
+
... provider_id="local",
|
|
33
|
+
... name="My_Agent",
|
|
23
34
|
... description="A helpful assistant",
|
|
24
|
-
... instruction="
|
|
25
|
-
... tools=[],
|
|
35
|
+
... instruction="Help users with tasks",
|
|
36
|
+
... tools=[Tool(name="Bash", description="Run commands", parameters=[])],
|
|
37
|
+
... )
|
|
38
|
+
>>> policy = "permit(principal, action, resource);"
|
|
39
|
+
>>> harness = CedarPolicyHarness(
|
|
40
|
+
... policy_set=policy,
|
|
41
|
+
... schema=agent_to_cedar_schema(agent),
|
|
26
42
|
... )
|
|
27
43
|
>>> await harness.initialize(agent=agent)
|
|
28
44
|
"""
|
|
@@ -47,10 +63,12 @@ from sondera.types import (
|
|
|
47
63
|
AdjudicatedStep,
|
|
48
64
|
AdjudicatedTrajectory,
|
|
49
65
|
Adjudication,
|
|
66
|
+
AdjudicationRecord,
|
|
50
67
|
Agent,
|
|
51
68
|
Content,
|
|
52
69
|
Decision,
|
|
53
70
|
Parameter,
|
|
71
|
+
PolicyAnnotation,
|
|
54
72
|
PolicyEngineMode,
|
|
55
73
|
PromptContent,
|
|
56
74
|
Role,
|
|
@@ -93,6 +111,8 @@ __all__ = [
|
|
|
93
111
|
"Adjudication",
|
|
94
112
|
"AdjudicatedStep",
|
|
95
113
|
"AdjudicatedTrajectory",
|
|
114
|
+
"AdjudicationRecord",
|
|
115
|
+
"PolicyAnnotation",
|
|
96
116
|
"Decision",
|
|
97
117
|
# Exceptions
|
|
98
118
|
"SonderaError",
|
sondera/adk/plugin.py
CHANGED
|
@@ -64,7 +64,7 @@ class SonderaHarnessPlugin(BasePlugin):
|
|
|
64
64
|
plugin = SonderaHarnessPlugin(harness=harness)
|
|
65
65
|
|
|
66
66
|
# Create agent and runner with the plugin
|
|
67
|
-
agent = Agent(name="my-agent", model="gemini-2.
|
|
67
|
+
agent = Agent(name="my-agent", model="gemini-2.5-flash", ...)
|
|
68
68
|
runner = Runner(
|
|
69
69
|
agent=agent,
|
|
70
70
|
app_name="my-app",
|
sondera/harness/cedar/harness.py
CHANGED
|
@@ -23,6 +23,7 @@ from sondera.types import (
|
|
|
23
23
|
Agent,
|
|
24
24
|
Content,
|
|
25
25
|
Decision,
|
|
26
|
+
PolicyAnnotation,
|
|
26
27
|
PromptContent,
|
|
27
28
|
Role,
|
|
28
29
|
Stage,
|
|
@@ -69,6 +70,7 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
69
70
|
*,
|
|
70
71
|
policy_set: PolicySet | str,
|
|
71
72
|
schema: CedarSchema,
|
|
73
|
+
agent: Agent | None = None,
|
|
72
74
|
logger: logging.Logger | None = None,
|
|
73
75
|
):
|
|
74
76
|
"""Initialize the Cedar policy engine.
|
|
@@ -77,12 +79,13 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
77
79
|
policy_set: Cedar policies to evaluate. Can be a PolicySet instance
|
|
78
80
|
or Cedar policy text. Required.
|
|
79
81
|
schema: Cedar schema generated from agent_to_cedar_schema(). Required.
|
|
82
|
+
agent: The agent to govern. Required for adjudication.
|
|
80
83
|
logger: Logger instance.
|
|
81
84
|
|
|
82
85
|
Raises:
|
|
83
86
|
ValueError: If policy_set or schema is not provided.
|
|
84
87
|
"""
|
|
85
|
-
self._agent: Agent | None =
|
|
88
|
+
self._agent: Agent | None = agent
|
|
86
89
|
self._trajectory_id: str | None = None
|
|
87
90
|
self._trajectory_step_count: int = 0
|
|
88
91
|
self._logger = logger or _LOGGER
|
|
@@ -101,6 +104,16 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
101
104
|
self._policy_set = PolicySet(policy_set)
|
|
102
105
|
else:
|
|
103
106
|
self._policy_set = policy_set
|
|
107
|
+
|
|
108
|
+
for policy in self._policy_set.policies():
|
|
109
|
+
annotations = policy.annotations()
|
|
110
|
+
if "escalate" in annotations and str(policy.effect()) != "Forbid":
|
|
111
|
+
policy_id = annotations.get("id", policy.id())
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Policy '{policy_id}' has @escalate but is not a forbid policy. "
|
|
114
|
+
"@escalate is only valid on forbid policies."
|
|
115
|
+
)
|
|
116
|
+
|
|
104
117
|
# Extract namespace name from schema
|
|
105
118
|
namespaces = list(schema.root.keys())
|
|
106
119
|
if namespaces:
|
|
@@ -110,6 +123,8 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
110
123
|
raise ValueError("Schema must have at least one namespace")
|
|
111
124
|
# Authorizer will be initialized with entities when agent is set
|
|
112
125
|
self._authorizer: Authorizer | None = None
|
|
126
|
+
# Cache for pre-parsed tool response schemas (tool_name -> parsed schema dict)
|
|
127
|
+
self._tool_response_schemas: dict[str, dict[str, object]] = {}
|
|
113
128
|
|
|
114
129
|
def _build_authorizer(self) -> Authorizer:
|
|
115
130
|
"""Build the Cedar authorizer with current entities."""
|
|
@@ -128,8 +143,9 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
128
143
|
if self._agent:
|
|
129
144
|
agent_uid = EntityUid(f"{self._namespace}::Agent", self._agent.id)
|
|
130
145
|
|
|
131
|
-
# Add tool entities from agent's tools
|
|
146
|
+
# Add tool entities from agent's tools and pre-parse response schemas
|
|
132
147
|
tool_entities: list[EntityUid] = []
|
|
148
|
+
self._tool_response_schemas = {}
|
|
133
149
|
for tool in self._agent.tools:
|
|
134
150
|
tool_id = tool.id or tool.name
|
|
135
151
|
tool_uid = EntityUid(f"{self._namespace}::Tool", tool_id)
|
|
@@ -142,6 +158,11 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
142
158
|
)
|
|
143
159
|
tool_entities.append(tool_uid)
|
|
144
160
|
entities.append(tool_entity)
|
|
161
|
+
# Pre-parse response JSON schema for use in _tool_response
|
|
162
|
+
if tool.response_json_schema:
|
|
163
|
+
self._tool_response_schemas[tool.name] = json.loads(
|
|
164
|
+
tool.response_json_schema
|
|
165
|
+
)
|
|
145
166
|
|
|
146
167
|
agent_entity = Entity(
|
|
147
168
|
agent_uid,
|
|
@@ -252,11 +273,47 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
252
273
|
assert request is not None, "Unexpected none request"
|
|
253
274
|
response = self._authorizer.is_authorized(request, self._policy_set)
|
|
254
275
|
if str(response.decision) == "Allow":
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
return Adjudication(
|
|
277
|
+
decision=Decision.ALLOW,
|
|
278
|
+
reason=f"Allowed by policies: {response.reason}",
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
annotations: list[PolicyAnnotation] = []
|
|
282
|
+
hard_deny_ids = []
|
|
283
|
+
for internal_id in response.reason:
|
|
284
|
+
policy = self._policy_set.policy(internal_id)
|
|
285
|
+
if policy is None:
|
|
286
|
+
raise RuntimeError(f"Policy '{internal_id}' not found in policy set")
|
|
287
|
+
policy_annotations = policy.annotations()
|
|
288
|
+
if "escalate" not in policy_annotations:
|
|
289
|
+
hard_deny_ids.append(internal_id)
|
|
290
|
+
else:
|
|
291
|
+
custom = {
|
|
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
|
+
)
|
|
305
|
+
|
|
306
|
+
if not hard_deny_ids and annotations:
|
|
307
|
+
return Adjudication(
|
|
308
|
+
decision=Decision.ESCALATE,
|
|
309
|
+
reason=f"Escalated by policies: {response.reason}",
|
|
310
|
+
annotations=annotations,
|
|
311
|
+
)
|
|
257
312
|
else:
|
|
258
|
-
|
|
259
|
-
|
|
313
|
+
return Adjudication(
|
|
314
|
+
decision=Decision.DENY,
|
|
315
|
+
reason=f"Denied by policies: {hard_deny_ids}",
|
|
316
|
+
)
|
|
260
317
|
|
|
261
318
|
def _message_request(
|
|
262
319
|
self,
|
|
@@ -311,11 +368,17 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
311
368
|
action_name = tool_id.replace(" ", "_").replace("-", "_")
|
|
312
369
|
action_uid = EntityUid(f"{self._namespace}::Action", action_name)
|
|
313
370
|
|
|
314
|
-
context
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
371
|
+
# Build context - only include typed parameters if schema defines them
|
|
372
|
+
context_data: dict[str, object] = {
|
|
373
|
+
"parameters_json": json.dumps(content.args),
|
|
374
|
+
}
|
|
375
|
+
# Check if tool has typed parameters schema
|
|
376
|
+
if self._agent:
|
|
377
|
+
tool = next((t for t in self._agent.tools if t.name == tool_id), None)
|
|
378
|
+
if tool and tool.parameters_json_schema:
|
|
379
|
+
context_data["parameters"] = content.args
|
|
380
|
+
|
|
381
|
+
context = Context(context_data, schema=self._schema, action=action_uid)
|
|
319
382
|
|
|
320
383
|
return Request(
|
|
321
384
|
principal=agent_uid,
|
|
@@ -345,14 +408,22 @@ class CedarPolicyHarness(AbstractHarness):
|
|
|
345
408
|
action_name = tool_id.replace(" ", "_").replace("-", "_")
|
|
346
409
|
action_uid = EntityUid(f"{self._namespace}::Action", action_name)
|
|
347
410
|
|
|
348
|
-
context
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
411
|
+
# Build context - only include typed response if schema defines it
|
|
412
|
+
context_data: dict[str, object] = {
|
|
413
|
+
"response_json": json.dumps(content.response, default=str),
|
|
414
|
+
}
|
|
415
|
+
# Check if tool has typed response schema (pre-parsed in _build_authorizer)
|
|
416
|
+
if tool_id in self._tool_response_schemas:
|
|
417
|
+
response_schema = self._tool_response_schemas[tool_id]
|
|
418
|
+
# Check if the response schema is a simple type (not object/Record)
|
|
419
|
+
# Simple types get wrapped in {"value": ...} by the schema generator
|
|
420
|
+
if response_schema.get("type") not in ["object", "OBJECT"]:
|
|
421
|
+
# Simple type was wrapped in {"value": ...} by schema generator
|
|
422
|
+
context_data["response"] = {"value": content.response}
|
|
423
|
+
else:
|
|
424
|
+
context_data["response"] = content.response
|
|
425
|
+
|
|
426
|
+
context = Context(context_data, schema=self._schema, action=action_uid)
|
|
356
427
|
|
|
357
428
|
return Request(
|
|
358
429
|
principal=agent_uid,
|
sondera/harness/sondera/_grpc.py
CHANGED
|
@@ -11,13 +11,16 @@ from sondera.types import (
|
|
|
11
11
|
Adjudication,
|
|
12
12
|
AdjudicationRecord,
|
|
13
13
|
Agent,
|
|
14
|
+
Check,
|
|
14
15
|
Content,
|
|
15
16
|
Decision,
|
|
17
|
+
GuardrailContext,
|
|
16
18
|
Parameter,
|
|
17
19
|
PolicyAnnotation,
|
|
18
20
|
PolicyEngineMode,
|
|
19
21
|
PromptContent,
|
|
20
22
|
Role,
|
|
23
|
+
SourceCode,
|
|
21
24
|
Stage,
|
|
22
25
|
Tool,
|
|
23
26
|
ToolRequestContent,
|
|
@@ -81,6 +84,38 @@ def _convert_sdk_content_to_pb(content: Content) -> primitives_pb2.Content:
|
|
|
81
84
|
raise ValueError(f"Unsupported content type: {type(content)}")
|
|
82
85
|
|
|
83
86
|
|
|
87
|
+
def _convert_sdk_tool_to_pb(tool: Tool) -> primitives_pb2.Tool:
|
|
88
|
+
"""Convert SDK Tool to protobuf Tool."""
|
|
89
|
+
pb_params = [
|
|
90
|
+
primitives_pb2.Parameter(
|
|
91
|
+
name=param.name,
|
|
92
|
+
description=param.description,
|
|
93
|
+
type=param.type,
|
|
94
|
+
)
|
|
95
|
+
for param in tool.parameters
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
pb_source = (
|
|
99
|
+
primitives_pb2.SourceCode(
|
|
100
|
+
language=tool.source.language,
|
|
101
|
+
code=tool.source.code,
|
|
102
|
+
)
|
|
103
|
+
if tool.source
|
|
104
|
+
else None
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return primitives_pb2.Tool(
|
|
108
|
+
id=tool.id,
|
|
109
|
+
name=tool.name,
|
|
110
|
+
description=tool.description,
|
|
111
|
+
parameters=pb_params,
|
|
112
|
+
parameters_json_schema=tool.parameters_json_schema,
|
|
113
|
+
response=tool.response,
|
|
114
|
+
response_json_schema=tool.response_json_schema,
|
|
115
|
+
source=pb_source,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
84
119
|
def _convert_pb_adjudication_to_sdk(
|
|
85
120
|
adjudication: primitives_pb2.Adjudication,
|
|
86
121
|
) -> Adjudication:
|
|
@@ -95,8 +130,8 @@ def _convert_pb_adjudication_to_sdk(
|
|
|
95
130
|
# Convert annotations
|
|
96
131
|
annotations = [
|
|
97
132
|
PolicyAnnotation(
|
|
98
|
-
id=ann.id,
|
|
99
|
-
description=ann.description,
|
|
133
|
+
id=ann.id if ann.HasField("id") else "",
|
|
134
|
+
description=ann.description if ann.HasField("description") else "",
|
|
100
135
|
custom=dict(ann.custom),
|
|
101
136
|
)
|
|
102
137
|
for ann in adjudication.annotations
|
|
@@ -105,6 +140,7 @@ def _convert_pb_adjudication_to_sdk(
|
|
|
105
140
|
return Adjudication(
|
|
106
141
|
decision=decision_map[adjudication.decision],
|
|
107
142
|
reason=adjudication.reason,
|
|
143
|
+
policy_ids=list(adjudication.policy_ids),
|
|
108
144
|
annotations=annotations,
|
|
109
145
|
)
|
|
110
146
|
|
|
@@ -117,13 +153,33 @@ def _convert_pb_agent_to_sdk(pb_agent: primitives_pb2.Agent) -> Agent:
|
|
|
117
153
|
Parameter(name=p.name, description=p.description, type=p.type)
|
|
118
154
|
for p in pb_tool.parameters
|
|
119
155
|
]
|
|
156
|
+
|
|
157
|
+
# Handle optional source field
|
|
158
|
+
source = None
|
|
159
|
+
if pb_tool.HasField("source"):
|
|
160
|
+
source = SourceCode(
|
|
161
|
+
language=pb_tool.source.language,
|
|
162
|
+
code=pb_tool.source.code,
|
|
163
|
+
)
|
|
164
|
+
|
|
120
165
|
tools.append(
|
|
121
166
|
Tool(
|
|
122
|
-
id=
|
|
167
|
+
id=pb_tool.id if pb_tool.HasField("id") else None,
|
|
123
168
|
name=pb_tool.name,
|
|
124
169
|
description=pb_tool.description,
|
|
125
170
|
parameters=params,
|
|
126
|
-
|
|
171
|
+
parameters_json_schema=(
|
|
172
|
+
pb_tool.parameters_json_schema
|
|
173
|
+
if pb_tool.HasField("parameters_json_schema")
|
|
174
|
+
else None
|
|
175
|
+
),
|
|
176
|
+
response=pb_tool.response if pb_tool.HasField("response") else None,
|
|
177
|
+
response_json_schema=(
|
|
178
|
+
pb_tool.response_json_schema
|
|
179
|
+
if pb_tool.HasField("response_json_schema")
|
|
180
|
+
else None
|
|
181
|
+
),
|
|
182
|
+
source=source,
|
|
127
183
|
)
|
|
128
184
|
)
|
|
129
185
|
|
|
@@ -266,11 +322,28 @@ def _convert_pb_adjudicated_step_to_sdk(
|
|
|
266
322
|
raise ValueError("AdjudicatedStep must have an adjudication field")
|
|
267
323
|
adjudication = _convert_pb_adjudication_to_sdk(pb_adjudication)
|
|
268
324
|
|
|
325
|
+
# Convert guardrails
|
|
326
|
+
guardrails = None
|
|
327
|
+
if pb_adjudicated_step.HasField("guardrails"):
|
|
328
|
+
checks = {}
|
|
329
|
+
for name, pb_check in pb_adjudicated_step.guardrails.checks.items():
|
|
330
|
+
checks[name] = Check(
|
|
331
|
+
name=pb_check.name,
|
|
332
|
+
flagged=pb_check.flagged,
|
|
333
|
+
message=pb_check.message if pb_check.HasField("message") else None,
|
|
334
|
+
)
|
|
335
|
+
guardrails = GuardrailContext(checks=checks)
|
|
336
|
+
|
|
269
337
|
# Default mode to GOVERN since protobuf doesn't have this field
|
|
270
338
|
# In practice, this should be set based on the policy engine configuration
|
|
271
339
|
mode = PolicyEngineMode.GOVERN
|
|
272
340
|
|
|
273
|
-
return AdjudicatedStep(
|
|
341
|
+
return AdjudicatedStep(
|
|
342
|
+
mode=mode,
|
|
343
|
+
adjudication=adjudication,
|
|
344
|
+
step=trajectory_step,
|
|
345
|
+
guardrails=guardrails,
|
|
346
|
+
)
|
|
274
347
|
|
|
275
348
|
|
|
276
349
|
def _convert_pb_trajectory_to_sdk(
|
|
@@ -24,6 +24,7 @@ from sondera.harness.sondera._grpc import (
|
|
|
24
24
|
_convert_sdk_content_to_pb,
|
|
25
25
|
_convert_sdk_role_to_pb,
|
|
26
26
|
_convert_sdk_stage_to_pb,
|
|
27
|
+
_convert_sdk_tool_to_pb,
|
|
27
28
|
_convert_sdk_trajectory_status_to_pb,
|
|
28
29
|
)
|
|
29
30
|
from sondera.proto.sondera.core.v1 import primitives_pb2
|
|
@@ -337,24 +338,8 @@ class SonderaRemoteHarness(AbstractHarness):
|
|
|
337
338
|
AuthenticationError: If authentication fails
|
|
338
339
|
grpc.RpcError: If other gRPC error occurs
|
|
339
340
|
"""
|
|
340
|
-
# Convert SDK agent to protobuf
|
|
341
|
-
pb_tools = []
|
|
342
|
-
for tool in agent.tools:
|
|
343
|
-
pb_params = []
|
|
344
|
-
for param in tool.parameters:
|
|
345
|
-
pb_params.append(
|
|
346
|
-
primitives_pb2.Parameter(
|
|
347
|
-
name=param.name, description=param.description, type=param.type
|
|
348
|
-
)
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
pb_tool = primitives_pb2.Tool(
|
|
352
|
-
name=tool.name,
|
|
353
|
-
description=tool.description,
|
|
354
|
-
parameters=pb_params,
|
|
355
|
-
response=tool.response,
|
|
356
|
-
)
|
|
357
|
-
pb_tools.append(pb_tool)
|
|
341
|
+
# Convert SDK agent tools to protobuf
|
|
342
|
+
pb_tools = [_convert_sdk_tool_to_pb(tool) for tool in agent.tools]
|
|
358
343
|
|
|
359
344
|
request = harness_pb2.RegisterAgentRequest(
|
|
360
345
|
provider_id=agent.provider_id,
|
|
@@ -26,7 +26,7 @@ from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
|
|
|
26
26
|
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n sondera/core/v1/primitives.proto\x12\x0fsondera.core.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\",\n\nSourceCode\x12\x10\n\x08language\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\"<\n\tParameter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\"\
|
|
29
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n sondera/core/v1/primitives.proto\x12\x0fsondera.core.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\",\n\nSourceCode\x12\x10\n\x08language\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\"<\n\tParameter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\"\xce\x02\n\x04Tool\x12\x0f\n\x02id\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nparameters\x18\x03 \x03(\x0b\x32\x1a.sondera.core.v1.Parameter\x12#\n\x16parameters_json_schema\x18\x07 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08response\x18\x04 \x01(\tH\x02\x88\x01\x01\x12!\n\x14response_json_schema\x18\x08 \x01(\tH\x03\x88\x01\x01\x12\x30\n\x06source\x18\x05 \x01(\x0b\x32\x1b.sondera.core.v1.SourceCodeH\x04\x88\x01\x01\x42\x05\n\x03_idB\x19\n\x17_parameters_json_schemaB\x0b\n\t_responseB\x17\n\x15_response_json_schemaB\t\n\x07_source\"\x86\x01\n\x05\x41gent\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bprovider_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\x13\n\x0binstruction\x18\x05 \x01(\t\x12$\n\x05tools\x18\x06 \x03(\x0b\x32\x15.sondera.core.v1.Tool\"\x16\n\x06Prompt\x12\x0c\n\x04text\x18\x01 \x01(\t\"D\n\x0bToolRequest\x12\x0f\n\x07tool_id\x18\x01 \x01(\t\x12$\n\x04\x61rgs\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value\"I\n\x0cToolResponse\x12\x0f\n\x07tool_id\x18\x01 \x01(\t\x12(\n\x08response\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value\"\xb2\x01\n\x07\x43ontent\x12)\n\x06prompt\x18\x01 \x01(\x0b\x32\x17.sondera.core.v1.PromptH\x00\x12\x34\n\x0ctool_request\x18\x02 \x01(\x0b\x32\x1c.sondera.core.v1.ToolRequestH\x00\x12\x36\n\rtool_response\x18\x03 \x01(\x0b\x32\x1d.sondera.core.v1.ToolResponseH\x00\x42\x0e\n\x0c\x63ontent_type\"\xb7\x01\n\x0eTrajectoryStep\x12%\n\x05stage\x18\x01 \x01(\x0e\x32\x16.sondera.core.v1.Stage\x12#\n\x04role\x18\x02 \x01(\x0e\x32\x15.sondera.core.v1.Role\x12.\n\ncreated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x07\x63ontent\x18\x04 \x01(\x0b\x32\x18.sondera.core.v1.Content\"\xf7\x03\n\nTrajectory\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08\x61gent_id\x18\x02 \x01(\t\x12\x31\n\x06status\x18\x03 \x01(\x0e\x32!.sondera.core.v1.TrajectoryStatus\x12;\n\x08metadata\x18\x04 \x03(\x0b\x32).sondera.core.v1.Trajectory.MetadataEntry\x12.\n\ncreated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x33\n\nstarted_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x31\n\x08\x65nded_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x01\x88\x01\x01\x12.\n\x05steps\x18\t \x03(\x0b\x32\x1f.sondera.core.v1.TrajectoryStep\x1aG\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\x42\r\n\x0b_started_atB\x0b\n\t_ended_at\"H\n\x05\x43heck\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x66lagged\x18\x02 \x01(\x08\x12\x14\n\x07message\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\n\n\x08_message\"\x98\x01\n\x10GuardrailContext\x12=\n\x06\x63hecks\x18\x01 \x03(\x0b\x32-.sondera.core.v1.GuardrailContext.ChecksEntry\x1a\x45\n\x0b\x43hecksEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.sondera.core.v1.Check:\x02\x38\x01\"\xc4\x01\n\x11PolicyAnnotations\x12\x0f\n\x02id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x01\x88\x01\x01\x12>\n\x06\x63ustom\x18\x03 \x03(\x0b\x32..sondera.core.v1.PolicyAnnotations.CustomEntry\x1a-\n\x0b\x43ustomEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x05\n\x03_idB\x0e\n\x0c_description\"\x98\x01\n\x0c\x41\x64judication\x12+\n\x08\x64\x65\x63ision\x18\x01 \x01(\x0e\x32\x19.sondera.core.v1.Decision\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x12\n\npolicy_ids\x18\x03 \x03(\t\x12\x37\n\x0b\x61nnotations\x18\x04 \x03(\x0b\x32\".sondera.core.v1.PolicyAnnotations\"\xac\x01\n\x0f\x41\x64judicatedStep\x12-\n\x04step\x18\x01 \x01(\x0b\x32\x1f.sondera.core.v1.TrajectoryStep\x12\x35\n\nguardrails\x18\x02 \x01(\x0b\x32!.sondera.core.v1.GuardrailContext\x12\x33\n\x0c\x61\x64judication\x18\x03 \x01(\x0b\x32\x1d.sondera.core.v1.Adjudication\"y\n\x15\x41\x64judicatedTrajectory\x12/\n\ntrajectory\x18\x01 \x01(\x0b\x32\x1b.sondera.core.v1.Trajectory\x12/\n\x05steps\x18\x02 \x03(\x0b\x32 .sondera.core.v1.AdjudicatedStep*\xd3\x01\n\x10TrajectoryStatus\x12!\n\x1dTRAJECTORY_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n\x19TRAJECTORY_STATUS_PENDING\x10\x01\x12\x1d\n\x19TRAJECTORY_STATUS_RUNNING\x10\x02\x12\x1f\n\x1bTRAJECTORY_STATUS_COMPLETED\x10\x03\x12\x1f\n\x1bTRAJECTORY_STATUS_SUSPENDED\x10\x04\x12\x1c\n\x18TRAJECTORY_STATUS_FAILED\x10\x05*\x99\x01\n\x05Stage\x12\x15\n\x11STAGE_UNSPECIFIED\x10\x00\x12\x11\n\rSTAGE_PRE_RUN\x10\x01\x12\x13\n\x0fSTAGE_PRE_MODEL\x10\x02\x12\x14\n\x10STAGE_POST_MODEL\x10\x03\x12\x12\n\x0eSTAGE_PRE_TOOL\x10\x04\x12\x13\n\x0fSTAGE_POST_TOOL\x10\x05\x12\x12\n\x0eSTAGE_POST_RUN\x10\x06*[\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\r\n\tROLE_USER\x10\x01\x12\x0e\n\nROLE_MODEL\x10\x02\x12\r\n\tROLE_TOOL\x10\x03\x12\x0f\n\x0bROLE_SYSTEM\x10\x04*b\n\x08\x44\x65\x63ision\x12\x18\n\x14\x44\x45\x43ISION_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x44\x45\x43ISION_ALLOW\x10\x01\x12\x11\n\rDECISION_DENY\x10\x02\x12\x15\n\x11\x44\x45\x43ISION_ESCALATE\x10\x03\x62\x06proto3')
|
|
30
30
|
|
|
31
31
|
_globals = globals()
|
|
32
32
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -39,50 +39,50 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|
|
39
39
|
_globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_options = b'8\001'
|
|
40
40
|
_globals['_POLICYANNOTATIONS_CUSTOMENTRY']._loaded_options = None
|
|
41
41
|
_globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_options = b'8\001'
|
|
42
|
-
_globals['_TRAJECTORYSTATUS']._serialized_start=
|
|
43
|
-
_globals['_TRAJECTORYSTATUS']._serialized_end=
|
|
44
|
-
_globals['_STAGE']._serialized_start=
|
|
45
|
-
_globals['_STAGE']._serialized_end=
|
|
46
|
-
_globals['_ROLE']._serialized_start=
|
|
47
|
-
_globals['_ROLE']._serialized_end=
|
|
48
|
-
_globals['_DECISION']._serialized_start=
|
|
49
|
-
_globals['_DECISION']._serialized_end=
|
|
42
|
+
_globals['_TRAJECTORYSTATUS']._serialized_start=2622
|
|
43
|
+
_globals['_TRAJECTORYSTATUS']._serialized_end=2833
|
|
44
|
+
_globals['_STAGE']._serialized_start=2836
|
|
45
|
+
_globals['_STAGE']._serialized_end=2989
|
|
46
|
+
_globals['_ROLE']._serialized_start=2991
|
|
47
|
+
_globals['_ROLE']._serialized_end=3082
|
|
48
|
+
_globals['_DECISION']._serialized_start=3084
|
|
49
|
+
_globals['_DECISION']._serialized_end=3182
|
|
50
50
|
_globals['_SOURCECODE']._serialized_start=116
|
|
51
51
|
_globals['_SOURCECODE']._serialized_end=160
|
|
52
52
|
_globals['_PARAMETER']._serialized_start=162
|
|
53
53
|
_globals['_PARAMETER']._serialized_end=222
|
|
54
54
|
_globals['_TOOL']._serialized_start=225
|
|
55
|
-
_globals['_TOOL']._serialized_end=
|
|
56
|
-
_globals['_AGENT']._serialized_start=
|
|
57
|
-
_globals['_AGENT']._serialized_end=
|
|
58
|
-
_globals['_PROMPT']._serialized_start=
|
|
59
|
-
_globals['_PROMPT']._serialized_end=
|
|
60
|
-
_globals['_TOOLREQUEST']._serialized_start=
|
|
61
|
-
_globals['_TOOLREQUEST']._serialized_end=
|
|
62
|
-
_globals['_TOOLRESPONSE']._serialized_start=
|
|
63
|
-
_globals['_TOOLRESPONSE']._serialized_end=
|
|
64
|
-
_globals['_CONTENT']._serialized_start=
|
|
65
|
-
_globals['_CONTENT']._serialized_end=
|
|
66
|
-
_globals['_TRAJECTORYSTEP']._serialized_start=
|
|
67
|
-
_globals['_TRAJECTORYSTEP']._serialized_end=
|
|
68
|
-
_globals['_TRAJECTORY']._serialized_start=
|
|
69
|
-
_globals['_TRAJECTORY']._serialized_end=
|
|
70
|
-
_globals['_TRAJECTORY_METADATAENTRY']._serialized_start=
|
|
71
|
-
_globals['_TRAJECTORY_METADATAENTRY']._serialized_end=
|
|
72
|
-
_globals['_CHECK']._serialized_start=
|
|
73
|
-
_globals['_CHECK']._serialized_end=
|
|
74
|
-
_globals['_GUARDRAILCONTEXT']._serialized_start=
|
|
75
|
-
_globals['_GUARDRAILCONTEXT']._serialized_end=
|
|
76
|
-
_globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_start=
|
|
77
|
-
_globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_end=
|
|
78
|
-
_globals['_POLICYANNOTATIONS']._serialized_start=
|
|
79
|
-
_globals['_POLICYANNOTATIONS']._serialized_end=
|
|
80
|
-
_globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_start=
|
|
81
|
-
_globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_end=
|
|
82
|
-
_globals['_ADJUDICATION']._serialized_start=
|
|
83
|
-
_globals['_ADJUDICATION']._serialized_end=
|
|
84
|
-
_globals['_ADJUDICATEDSTEP']._serialized_start=
|
|
85
|
-
_globals['_ADJUDICATEDSTEP']._serialized_end=
|
|
86
|
-
_globals['_ADJUDICATEDTRAJECTORY']._serialized_start=
|
|
87
|
-
_globals['_ADJUDICATEDTRAJECTORY']._serialized_end=
|
|
55
|
+
_globals['_TOOL']._serialized_end=559
|
|
56
|
+
_globals['_AGENT']._serialized_start=562
|
|
57
|
+
_globals['_AGENT']._serialized_end=696
|
|
58
|
+
_globals['_PROMPT']._serialized_start=698
|
|
59
|
+
_globals['_PROMPT']._serialized_end=720
|
|
60
|
+
_globals['_TOOLREQUEST']._serialized_start=722
|
|
61
|
+
_globals['_TOOLREQUEST']._serialized_end=790
|
|
62
|
+
_globals['_TOOLRESPONSE']._serialized_start=792
|
|
63
|
+
_globals['_TOOLRESPONSE']._serialized_end=865
|
|
64
|
+
_globals['_CONTENT']._serialized_start=868
|
|
65
|
+
_globals['_CONTENT']._serialized_end=1046
|
|
66
|
+
_globals['_TRAJECTORYSTEP']._serialized_start=1049
|
|
67
|
+
_globals['_TRAJECTORYSTEP']._serialized_end=1232
|
|
68
|
+
_globals['_TRAJECTORY']._serialized_start=1235
|
|
69
|
+
_globals['_TRAJECTORY']._serialized_end=1738
|
|
70
|
+
_globals['_TRAJECTORY_METADATAENTRY']._serialized_start=1639
|
|
71
|
+
_globals['_TRAJECTORY_METADATAENTRY']._serialized_end=1710
|
|
72
|
+
_globals['_CHECK']._serialized_start=1740
|
|
73
|
+
_globals['_CHECK']._serialized_end=1812
|
|
74
|
+
_globals['_GUARDRAILCONTEXT']._serialized_start=1815
|
|
75
|
+
_globals['_GUARDRAILCONTEXT']._serialized_end=1967
|
|
76
|
+
_globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_start=1898
|
|
77
|
+
_globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_end=1967
|
|
78
|
+
_globals['_POLICYANNOTATIONS']._serialized_start=1970
|
|
79
|
+
_globals['_POLICYANNOTATIONS']._serialized_end=2166
|
|
80
|
+
_globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_start=2098
|
|
81
|
+
_globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_end=2143
|
|
82
|
+
_globals['_ADJUDICATION']._serialized_start=2169
|
|
83
|
+
_globals['_ADJUDICATION']._serialized_end=2321
|
|
84
|
+
_globals['_ADJUDICATEDSTEP']._serialized_start=2324
|
|
85
|
+
_globals['_ADJUDICATEDSTEP']._serialized_end=2496
|
|
86
|
+
_globals['_ADJUDICATEDTRAJECTORY']._serialized_start=2498
|
|
87
|
+
_globals['_ADJUDICATEDTRAJECTORY']._serialized_end=2619
|
|
88
88
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -86,18 +86,24 @@ class Parameter(_message.Message):
|
|
|
86
86
|
def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., type: _Optional[str] = ...) -> None: ...
|
|
87
87
|
|
|
88
88
|
class Tool(_message.Message):
|
|
89
|
-
__slots__ = ("name", "description", "parameters", "response", "source")
|
|
89
|
+
__slots__ = ("id", "name", "description", "parameters", "parameters_json_schema", "response", "response_json_schema", "source")
|
|
90
|
+
ID_FIELD_NUMBER: _ClassVar[int]
|
|
90
91
|
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
91
92
|
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
|
|
92
93
|
PARAMETERS_FIELD_NUMBER: _ClassVar[int]
|
|
94
|
+
PARAMETERS_JSON_SCHEMA_FIELD_NUMBER: _ClassVar[int]
|
|
93
95
|
RESPONSE_FIELD_NUMBER: _ClassVar[int]
|
|
96
|
+
RESPONSE_JSON_SCHEMA_FIELD_NUMBER: _ClassVar[int]
|
|
94
97
|
SOURCE_FIELD_NUMBER: _ClassVar[int]
|
|
98
|
+
id: str
|
|
95
99
|
name: str
|
|
96
100
|
description: str
|
|
97
101
|
parameters: _containers.RepeatedCompositeFieldContainer[Parameter]
|
|
102
|
+
parameters_json_schema: str
|
|
98
103
|
response: str
|
|
104
|
+
response_json_schema: str
|
|
99
105
|
source: SourceCode
|
|
100
|
-
def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., parameters: _Optional[_Iterable[_Union[Parameter, _Mapping]]] = ..., response: _Optional[str] = ..., source: _Optional[_Union[SourceCode, _Mapping]] = ...) -> None: ...
|
|
106
|
+
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., parameters: _Optional[_Iterable[_Union[Parameter, _Mapping]]] = ..., parameters_json_schema: _Optional[str] = ..., response: _Optional[str] = ..., response_json_schema: _Optional[str] = ..., source: _Optional[_Union[SourceCode, _Mapping]] = ...) -> None: ...
|
|
101
107
|
|
|
102
108
|
class Agent(_message.Message):
|
|
103
109
|
__slots__ = ("id", "provider_id", "name", "description", "instruction", "tools")
|
sondera/types.py
CHANGED
|
@@ -213,6 +213,24 @@ class Decision(Enum):
|
|
|
213
213
|
ESCALATE = "escalate"
|
|
214
214
|
|
|
215
215
|
|
|
216
|
+
class Check(Model):
|
|
217
|
+
"""Single guardrail check result."""
|
|
218
|
+
|
|
219
|
+
name: str
|
|
220
|
+
"""Name of the guardrail check."""
|
|
221
|
+
flagged: bool
|
|
222
|
+
"""Whether the check was flagged."""
|
|
223
|
+
message: str | None = None
|
|
224
|
+
"""Optional message describing the check result."""
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class GuardrailContext(Model):
|
|
228
|
+
"""Aggregated guardrail check results."""
|
|
229
|
+
|
|
230
|
+
checks: dict[str, Check] = Field(default_factory=dict)
|
|
231
|
+
"""Map of check name to check result."""
|
|
232
|
+
|
|
233
|
+
|
|
216
234
|
class PolicyAnnotation(Model):
|
|
217
235
|
"""Annotation from a policy evaluation."""
|
|
218
236
|
|
|
@@ -220,6 +238,10 @@ class PolicyAnnotation(Model):
|
|
|
220
238
|
"""Unique identifier of the policy that produced this annotation."""
|
|
221
239
|
description: str
|
|
222
240
|
"""Human-readable description of why this annotation was added."""
|
|
241
|
+
escalate: bool = False
|
|
242
|
+
"""Whether this policy requires escalation to a human or other oracle to decide the final verdict."""
|
|
243
|
+
escalate_arg: str = ""
|
|
244
|
+
"""The argument passed to @escalate, if any."""
|
|
223
245
|
custom: dict[str, str] = Field(default_factory=dict)
|
|
224
246
|
"""Custom key-value metadata from the policy."""
|
|
225
247
|
|
|
@@ -231,6 +253,8 @@ class Adjudication(Model):
|
|
|
231
253
|
"""Whether the input is allowed."""
|
|
232
254
|
reason: str
|
|
233
255
|
"""Reason for the adjudication decision."""
|
|
256
|
+
policy_ids: list[str] = Field(default_factory=list)
|
|
257
|
+
"""IDs of policies that contributed to this decision."""
|
|
234
258
|
annotations: list[PolicyAnnotation] = Field(default_factory=list)
|
|
235
259
|
"""Annotations from policy evaluations."""
|
|
236
260
|
|
|
@@ -259,6 +283,8 @@ class AdjudicatedStep(Model):
|
|
|
259
283
|
"""Adjudication of the input."""
|
|
260
284
|
step: TrajectoryStep
|
|
261
285
|
"""Step of the adjudication."""
|
|
286
|
+
guardrails: GuardrailContext | None = None
|
|
287
|
+
"""Guardrail check results for this step."""
|
|
262
288
|
|
|
263
289
|
@property
|
|
264
290
|
def is_denied(self) -> bool:
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sondera-harness
|
|
3
|
+
Version: 0.6.3
|
|
4
|
+
Summary: Sondera Harness SDK for Python - Agent governance and policy enforcement
|
|
5
|
+
Author-email: Sondera AI <sdk@sondera.ai>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sondera-ai/harness-sdk-python
|
|
8
|
+
Project-URL: Documentation, https://docs.sondera.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/sondera-ai/harness-sdk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/sondera-ai/harness-sdk-python/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/sondera-ai/harness-sdk-python/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: ai,agents,governance,policy,guardrails,llm,langchain,langgraph
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: <3.15,>=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: cedar-python>=0.1.1
|
|
27
|
+
Requires-Dist: click>=8.0.0
|
|
28
|
+
Requires-Dist: click-default-group>=1.2.4
|
|
29
|
+
Requires-Dist: grpcio>=1.76.0
|
|
30
|
+
Requires-Dist: grpcio-tools>=1.76.0
|
|
31
|
+
Requires-Dist: httpx>=0.27.0
|
|
32
|
+
Requires-Dist: pydantic>=2.12.0
|
|
33
|
+
Requires-Dist: pydantic-settings>=2.12.0
|
|
34
|
+
Requires-Dist: textual>=6.11.0
|
|
35
|
+
Provides-Extra: adk
|
|
36
|
+
Requires-Dist: google-adk>=1.22.0; extra == "adk"
|
|
37
|
+
Provides-Extra: langgraph
|
|
38
|
+
Requires-Dist: langchain>=1.2.0; extra == "langgraph"
|
|
39
|
+
Requires-Dist: langgraph>=1.0.5; extra == "langgraph"
|
|
40
|
+
Provides-Extra: strands
|
|
41
|
+
Requires-Dist: strands-agents>=1.21.0; extra == "strands"
|
|
42
|
+
Provides-Extra: all
|
|
43
|
+
Requires-Dist: google-adk>=1.22.0; extra == "all"
|
|
44
|
+
Requires-Dist: langchain>=1.2.0; extra == "all"
|
|
45
|
+
Requires-Dist: langgraph>=1.0.5; extra == "all"
|
|
46
|
+
Requires-Dist: strands-agents>=1.21.0; extra == "all"
|
|
47
|
+
Dynamic: license-file
|
|
48
|
+
|
|
49
|
+
<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
|
+
|
|
56
|
+
<h1>Sondera Harness</h1>
|
|
57
|
+
|
|
58
|
+
<p><strong>Deterministic guardrails for AI agents.</strong></p>
|
|
59
|
+
|
|
60
|
+
<p>Open-source. Works with LangGraph, ADK, Strands, or any custom agent.</p>
|
|
61
|
+
|
|
62
|
+
<p>
|
|
63
|
+
<a href="https://docs.sondera.ai/">Docs</a>
|
|
64
|
+
·
|
|
65
|
+
<a href="https://docs.sondera.ai/quickstart/">Quickstart</a>
|
|
66
|
+
·
|
|
67
|
+
<a href="https://github.com/sondera-ai/sondera-harness-python/tree/main/examples">Examples</a>
|
|
68
|
+
·
|
|
69
|
+
<a href="https://discord.gg/8zMbcnDnZs">Discord</a>
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
<p>
|
|
73
|
+
<a href="https://pypi.org/project/sondera-harness/"><img src="https://img.shields.io/pypi/v/sondera-harness.svg" alt="PyPI version"></a>
|
|
74
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+"></a>
|
|
75
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/sondera-ai/sondera-harness-python.svg" alt="License: MIT"></a>
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## What is Sondera Harness?
|
|
83
|
+
|
|
84
|
+
Sondera Harness evaluates [Cedar](https://www.cedarpolicy.com/) policies before your agent's actions execute. When a policy denies an action, the agent gets a reason why and can try a different approach. Same input, same verdict. Deterministic, not probabilistic.
|
|
85
|
+
|
|
86
|
+
**Example policy:**
|
|
87
|
+
|
|
88
|
+
```cedar
|
|
89
|
+
forbid(principal, action, resource)
|
|
90
|
+
when { context has parameters_json && context.parameters_json like "*rm -rf*" };
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This policy stops your agent from running `rm -rf`, every time.
|
|
94
|
+
|
|
95
|
+
## Quickstart
|
|
96
|
+
|
|
97
|
+
### 1. Install
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv add "sondera-harness[langgraph]" # or: pip install "sondera-harness[langgraph]"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Add to Your Agent (LangGraph)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from langchain.agents import create_agent
|
|
107
|
+
from sondera.harness import SonderaRemoteHarness
|
|
108
|
+
from sondera.langgraph import SonderaHarnessMiddleware, Strategy, create_agent_from_langchain_tools
|
|
109
|
+
|
|
110
|
+
# Analyze your tools and create agent metadata
|
|
111
|
+
sondera_agent = create_agent_from_langchain_tools(
|
|
112
|
+
tools=my_tools,
|
|
113
|
+
agent_id="langchain-agent",
|
|
114
|
+
agent_name="My LangChain Agent",
|
|
115
|
+
agent_description="An agent that helps with tasks",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Create harness with agent
|
|
119
|
+
harness = SonderaRemoteHarness(agent=sondera_agent)
|
|
120
|
+
|
|
121
|
+
# Create middleware
|
|
122
|
+
middleware = SonderaHarnessMiddleware(
|
|
123
|
+
harness=harness,
|
|
124
|
+
strategy=Strategy.BLOCK, # or Strategy.STEER
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Create agent with middleware
|
|
128
|
+
agent = create_agent(
|
|
129
|
+
model=my_model,
|
|
130
|
+
tools=my_tools,
|
|
131
|
+
middleware=[middleware],
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
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
|
+
> [!NOTE]
|
|
138
|
+
> 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
|
+
|
|
140
|
+
### 3. See It in Action
|
|
141
|
+
|
|
142
|
+
<div align="center">
|
|
143
|
+
<img src="docs/src/assets/sondera-tui.gif" alt="Sondera TUI" width="700" />
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uv run sondera # or: sondera (if installed via pip)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Why Sondera Harness?
|
|
151
|
+
|
|
152
|
+
- **Steer, don't block:** Denied actions include a reason. Return it to the model, and it tries something else.
|
|
153
|
+
- **Deterministic:** Stop debugging prompts. Rules are predictable.
|
|
154
|
+
- **Drop-in integration:** Native middleware for LangGraph, Google ADK, and Strands.
|
|
155
|
+
- **Full observability:** Every action, every decision, every reason. Audit-ready.
|
|
156
|
+
|
|
157
|
+
## Documentation
|
|
158
|
+
|
|
159
|
+
- [Quickstart](https://docs.sondera.ai/quickstart/)
|
|
160
|
+
- [Writing Policies](https://docs.sondera.ai/writing-policies/)
|
|
161
|
+
- [Integrations](https://docs.sondera.ai/integrations/)
|
|
162
|
+
- [Reference](https://docs.sondera.ai/reference/)
|
|
163
|
+
|
|
164
|
+
## Community
|
|
165
|
+
|
|
166
|
+
- [Discord](https://discord.gg/8zMbcnDnZs) for questions and feedback
|
|
167
|
+
- [GitHub Issues](https://github.com/sondera-ai/sondera-harness-python/issues) for bugs
|
|
168
|
+
- [Contributing](CONTRIBUTING.md) for development setup
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
[MIT](LICENSE)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
sondera/__init__.py,sha256=
|
|
1
|
+
sondera/__init__.py,sha256=0Z1R6ZkA5VSE_3ZCCgfeecm5nU6mFBdl4-18b8Nwt3Y,3558
|
|
2
2
|
sondera/__main__.py,sha256=MNgWvrV4g4Ot653Ngi2D4cAOyRIG19qHAWLzQecsMdg,66
|
|
3
3
|
sondera/cli.py,sha256=owchF-eA6kttYGTSsLl0B7XJMmn9O2n2LFjpfYQvznQ,494
|
|
4
4
|
sondera/exceptions.py,sha256=vtuToFc5tSlzAyVYvayyOatKBcoenisDA1RN2yk9aSI,3584
|
|
5
5
|
sondera/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
sondera/settings.py,sha256=bLB98vT75aXKh5ihYnCd0dTk1AdfUUaGuPkyzhcldE0,459
|
|
7
|
-
sondera/types.py,sha256=
|
|
7
|
+
sondera/types.py,sha256=t4TbVcieoPgGr9wje0jz0eFmXPmfOvdqsKds8aKgMDI,10428
|
|
8
8
|
sondera/adk/__init__.py,sha256=weoilnJyr8JNBv2HK6s3hhW-6rOBGBcNwwkKk1oHVFE,77
|
|
9
9
|
sondera/adk/analyze.py,sha256=IurwCWPZlNbMkIwi3TGWUu4k-w_VmKCkAnVFbfipbxY,7974
|
|
10
|
-
sondera/adk/plugin.py,sha256=
|
|
10
|
+
sondera/adk/plugin.py,sha256=ioxJCRaAC5Rt73h_Y6G3LgHSkz28K1hVi8nZGyCdAgc,13047
|
|
11
11
|
sondera/harness/__init__.py,sha256=gK0rFEyixD9X67pFyWKQpcq_oZ6pep6up8K0Y-zvXUc,221
|
|
12
12
|
sondera/harness/abc.py,sha256=hL77Rlzy1B-DjFexhskGm_9j5Sue-TaWdlSnjr-Al70,3548
|
|
13
13
|
sondera/harness/cedar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
sondera/harness/cedar/harness.py,sha256=
|
|
14
|
+
sondera/harness/cedar/harness.py,sha256=ZTUa1TuzNBLESkLMZZu_jAVOnqMOYLGgD1RbylqbhMk,15597
|
|
15
15
|
sondera/harness/cedar/schema.py,sha256=jDAGdLciK3fQ-7yKrqeFkU4YHQg-KwWvTi1leEuYVxo,7766
|
|
16
16
|
sondera/harness/sondera/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
sondera/harness/sondera/_grpc.py,sha256=
|
|
18
|
-
sondera/harness/sondera/harness.py,sha256=
|
|
17
|
+
sondera/harness/sondera/_grpc.py,sha256=vLVJwJBtDwX-p8qZPfShpChKxdP3lSFoTV6QiL4dh7s,14708
|
|
18
|
+
sondera/harness/sondera/harness.py,sha256=Xg0zc6DZbYvAVceJMlC8fL6D-qX8J-xI1OqPHb-Taxk,31231
|
|
19
19
|
sondera/langgraph/__init__.py,sha256=F2eNoPp944tvGbf9a4etNh3-o49WOPrcs9EFVT0OYYw,473
|
|
20
20
|
sondera/langgraph/analyze.py,sha256=1n-1yKr7-kfdFNSBe9JozZ08oJeYvPqKkFttcQ4MXHI,20514
|
|
21
21
|
sondera/langgraph/exceptions.py,sha256=BRdh1gpELb3_WZ9Bh_UUwZsIOcIPrpQCD-7LnnAGeV4,501
|
|
@@ -42,8 +42,8 @@ sondera/proto/google/protobuf/wrappers_pb2_grpc.py,sha256=SO9GezWgSOXwclY-Jjo8lv
|
|
|
42
42
|
sondera/proto/sondera/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
43
|
sondera/proto/sondera/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
44
|
sondera/proto/sondera/core/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
sondera/proto/sondera/core/v1/primitives_pb2.py,sha256=
|
|
46
|
-
sondera/proto/sondera/core/v1/primitives_pb2.pyi,sha256=
|
|
45
|
+
sondera/proto/sondera/core/v1/primitives_pb2.py,sha256=q5BvZY4QIySmfUc5tGl2AiwAtYaZCbQgXcOuf3BaUfs,9492
|
|
46
|
+
sondera/proto/sondera/core/v1/primitives_pb2.pyi,sha256=yRiHab6ut8MzJDZHas2kXvvJUjbXwAjkQ75rpkmZHs4,12216
|
|
47
47
|
sondera/proto/sondera/core/v1/primitives_pb2_grpc.py,sha256=YNObfhmUBQVh_n-kRidSAi_V4sPLf2Mo9Jc7hEEHT2s,906
|
|
48
48
|
sondera/proto/sondera/harness/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
sondera/proto/sondera/harness/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,9 +70,9 @@ sondera/tui/widgets/tool_card.py,sha256=3yNQcc_umcan4V1S5GiEKd7l4YA_atibwn3HF0n6
|
|
|
70
70
|
sondera/tui/widgets/violation_panel.py,sha256=fowe4KWb13NXLX0_RAxEPdRqYeyGzlImpRs4_L9y1zI,2933
|
|
71
71
|
sondera/tui/widgets/violations_list.py,sha256=86qICAsQOC6kjQLs64WxK7u59vEJ8kvfiToLVlzFyHM,2866
|
|
72
72
|
sondera/tui/widgets/violations_summary.py,sha256=e2LwqlB1aS8sZ2gEC5clk7siA16NSgePU1mpv8T1iTc,4473
|
|
73
|
-
sondera_harness-0.6.
|
|
74
|
-
sondera_harness-0.6.
|
|
75
|
-
sondera_harness-0.6.
|
|
76
|
-
sondera_harness-0.6.
|
|
77
|
-
sondera_harness-0.6.
|
|
78
|
-
sondera_harness-0.6.
|
|
73
|
+
sondera_harness-0.6.3.dist-info/licenses/LICENSE,sha256=DmSfauhgrslTxZOcDAmcYqsqsKBkMqVh3PYdjPghNbU,1070
|
|
74
|
+
sondera_harness-0.6.3.dist-info/METADATA,sha256=cWb8iOVxQLKyIRcNq8902znCu6oqPr8IRhU6Yvm64lg,6419
|
|
75
|
+
sondera_harness-0.6.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
76
|
+
sondera_harness-0.6.3.dist-info/entry_points.txt,sha256=5cLgW0-GzEwNnQjXIhGT21iFprQb1lftBaFJjC4IrgE,78
|
|
77
|
+
sondera_harness-0.6.3.dist-info/top_level.txt,sha256=BR0X8Gq9CCpwbQg5evpQfy5zwp9fTuGnlJhXSNqQ_hA,8
|
|
78
|
+
sondera_harness-0.6.3.dist-info/RECORD,,
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: sondera-harness
|
|
3
|
-
Version: 0.6.1
|
|
4
|
-
Summary: Sondera Harness SDK for Python - Agent governance and policy enforcement
|
|
5
|
-
Author-email: Sondera AI <sdk@sondera.ai>
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/sondera-ai/harness-sdk-python
|
|
8
|
-
Project-URL: Documentation, https://docs.sondera.ai
|
|
9
|
-
Project-URL: Repository, https://github.com/sondera-ai/harness-sdk-python
|
|
10
|
-
Project-URL: Issues, https://github.com/sondera-ai/harness-sdk-python/issues
|
|
11
|
-
Project-URL: Changelog, https://github.com/sondera-ai/harness-sdk-python/blob/main/CHANGELOG.md
|
|
12
|
-
Keywords: ai,agents,governance,policy,guardrails,llm,langchain,langgraph
|
|
13
|
-
Classifier: Development Status :: 4 - Beta
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
-
Classifier: Topic :: Security
|
|
22
|
-
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: <3.15,>=3.12
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Requires-Dist: cedar-python>=0.1.1
|
|
27
|
-
Requires-Dist: click>=8.0.0
|
|
28
|
-
Requires-Dist: click-default-group>=1.2.4
|
|
29
|
-
Requires-Dist: grpcio>=1.76.0
|
|
30
|
-
Requires-Dist: grpcio-tools>=1.76.0
|
|
31
|
-
Requires-Dist: httpx>=0.27.0
|
|
32
|
-
Requires-Dist: pydantic>=2.12.0
|
|
33
|
-
Requires-Dist: pydantic-settings>=2.12.0
|
|
34
|
-
Requires-Dist: textual>=6.11.0
|
|
35
|
-
Provides-Extra: adk
|
|
36
|
-
Requires-Dist: google-adk>=1.22.0; extra == "adk"
|
|
37
|
-
Provides-Extra: langgraph
|
|
38
|
-
Requires-Dist: langchain>=1.2.0; extra == "langgraph"
|
|
39
|
-
Requires-Dist: langgraph>=1.0.5; extra == "langgraph"
|
|
40
|
-
Provides-Extra: strands
|
|
41
|
-
Requires-Dist: strands-agents>=1.21.0; extra == "strands"
|
|
42
|
-
Provides-Extra: all
|
|
43
|
-
Requires-Dist: google-adk>=1.22.0; extra == "all"
|
|
44
|
-
Requires-Dist: langchain>=1.2.0; extra == "all"
|
|
45
|
-
Requires-Dist: langgraph>=1.0.5; extra == "all"
|
|
46
|
-
Requires-Dist: strands-agents>=1.21.0; extra == "all"
|
|
47
|
-
Dynamic: license-file
|
|
48
|
-
|
|
49
|
-
# Sondera Harness SDK for Python
|
|
50
|
-
|
|
51
|
-
[](https://www.python.org/downloads/)
|
|
52
|
-
[](LICENSE)
|
|
53
|
-
|
|
54
|
-
>
|
|
55
|
-
> One step at a time. One action at a time. One trajectory at a time.
|
|
56
|
-
>
|
|
57
|
-
|
|
58
|
-
AI agents systems operate beyond traditional security boundaries, making autonomous decisions, calling tools, and accessing resources based on context that changes with every execution. Sondera SDK provides runtime governance for these agentic systems, answering not just "can this agent do X?" but "should it do X here, now, with this data?" Built for developers deploying agents through LangGraph, Google ADK, and Strands, Sondera enables real-time trajectory tracking, policy-as-code enforcement via Cedar, and behavioral adjudication so you can ship agents with confidence.
|
|
59
|
-
|
|
60
|
-
## Features
|
|
61
|
-
|
|
62
|
-
- **Managed harness-as-a-service** with the Sondera Harness for enterprise policy governance and guardrails
|
|
63
|
-
- **Local policy-as-code** using Cedar policy language in the Cedar Policy Harness
|
|
64
|
-
- **Real-time trajectory** observability, adjudication, and steering
|
|
65
|
-
- **Scaffold integrations** for LangGraph, Google ADK, and Strands
|
|
66
|
-
- **CLI and TUI** for monitoring agent behavior
|
|
67
|
-
|
|
68
|
-
## Installation
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
uv add sondera-harness
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Optional Dependencies
|
|
75
|
-
|
|
76
|
-
Install extras for specific framework integrations:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
# Google ADK support
|
|
80
|
-
uv add sondera-harness --extra adk
|
|
81
|
-
|
|
82
|
-
# LangGraph support
|
|
83
|
-
uv add sondera-harness --extra langgraph
|
|
84
|
-
|
|
85
|
-
# Strands support
|
|
86
|
-
uv add sondera-harness --extra strands
|
|
87
|
-
|
|
88
|
-
# All integrations
|
|
89
|
-
uv add sondera-harness --all-extras
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Quick Start
|
|
93
|
-
|
|
94
|
-
### Configuration
|
|
95
|
-
|
|
96
|
-
Set your API credentials via environment variables:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
export SONDERA_HARNESS_ENDPOINT="your-harness.sondera.ai:443"
|
|
100
|
-
export SONDERA_API_TOKEN="<YOUR_SONDERA_API_KEY>"
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Or create a `.env` file or `~/.sondera/env`:
|
|
104
|
-
|
|
105
|
-
```env
|
|
106
|
-
SONDERA_HARNESS_ENDPOINT=your-harness.sondera.ai:443
|
|
107
|
-
SONDERA_API_TOKEN=<YOUR_SONDERA_API_KEY>
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Scaffold Integrations
|
|
111
|
-
|
|
112
|
-
### LangGraph / LangChain
|
|
113
|
-
|
|
114
|
-
```python
|
|
115
|
-
from langchain.agents import create_agent
|
|
116
|
-
from sondera.harness import SonderaRemoteHarness
|
|
117
|
-
from sondera.langgraph import SonderaHarnessMiddleware, Strategy, create_agent_from_langchain_tools
|
|
118
|
-
|
|
119
|
-
# Analyze your tools and create agent metadata
|
|
120
|
-
sondera_agent = create_agent_from_langchain_tools(
|
|
121
|
-
tools=my_tools,
|
|
122
|
-
agent_id="langchain-agent",
|
|
123
|
-
agent_name="My LangChain Agent",
|
|
124
|
-
agent_description="An agent that helps with tasks",
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
# Create harness with agent
|
|
128
|
-
harness = SonderaRemoteHarness(agent=sondera_agent)
|
|
129
|
-
|
|
130
|
-
# Create middleware
|
|
131
|
-
middleware = SonderaHarnessMiddleware(
|
|
132
|
-
harness=harness,
|
|
133
|
-
strategy=Strategy.BLOCK, # or Strategy.STEER
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
# Create agent with middleware
|
|
137
|
-
agent = create_agent(
|
|
138
|
-
model=my_model,
|
|
139
|
-
tools=my_tools,
|
|
140
|
-
middleware=[middleware],
|
|
141
|
-
)
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Google ADK
|
|
145
|
-
|
|
146
|
-
```python
|
|
147
|
-
from google.adk.agents import Agent
|
|
148
|
-
from google.adk.runners import Runner
|
|
149
|
-
from sondera.harness import SonderaRemoteHarness
|
|
150
|
-
from sondera.adk import SonderaHarnessPlugin
|
|
151
|
-
|
|
152
|
-
# Create harness
|
|
153
|
-
harness = SonderaRemoteHarness(
|
|
154
|
-
sondera_api_key="<YOUR_SONDERA_API_KEY>",
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# Create plugin
|
|
158
|
-
plugin = SonderaHarnessPlugin(harness=harness)
|
|
159
|
-
|
|
160
|
-
# Create agent
|
|
161
|
-
agent = Agent(
|
|
162
|
-
name="my-adk-agent",
|
|
163
|
-
model="gemini-2.0-flash",
|
|
164
|
-
instruction="Be helpful and safe",
|
|
165
|
-
tools=[...],
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
# Create runner with plugin
|
|
169
|
-
runner = Runner(
|
|
170
|
-
agent=agent,
|
|
171
|
-
app_name="my-app",
|
|
172
|
-
plugins=[plugin],
|
|
173
|
-
)
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Strands Agents
|
|
177
|
-
|
|
178
|
-
```python
|
|
179
|
-
from strands import Agent
|
|
180
|
-
from sondera.harness import SonderaRemoteHarness
|
|
181
|
-
from sondera.strands import SonderaHarnessHook
|
|
182
|
-
|
|
183
|
-
# Create harness
|
|
184
|
-
harness = SonderaRemoteHarness(
|
|
185
|
-
sondera_api_key="<YOUR_SONDERA_API_KEY>",
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
# Create hook
|
|
189
|
-
hook = SonderaHarnessHook(harness=harness)
|
|
190
|
-
|
|
191
|
-
# Create agent with hook
|
|
192
|
-
agent = Agent(
|
|
193
|
-
system_prompt="You are a helpful assistant",
|
|
194
|
-
model="anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
195
|
-
hooks=[hook],
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
# Run agent (hooks fire automatically)
|
|
199
|
-
response = agent("What is 5 + 3?")
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### Custom Scaffold
|
|
203
|
-
|
|
204
|
-
```python
|
|
205
|
-
from sondera import SonderaRemoteHarness, Agent, PromptContent, Role, Stage
|
|
206
|
-
|
|
207
|
-
# Create a harness instance
|
|
208
|
-
harness = SonderaRemoteHarness(
|
|
209
|
-
sondera_harness_endpoint="localhost:50051",
|
|
210
|
-
sondera_api_key="<YOUR_SONDERA_API_KEY>",
|
|
211
|
-
sondera_harness_client_secure=True, # Enable TLS for production
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
# Define your agent
|
|
215
|
-
agent = Agent(
|
|
216
|
-
id="my-agent",
|
|
217
|
-
provider_id="custom",
|
|
218
|
-
name="My Assistant",
|
|
219
|
-
description="A helpful AI assistant",
|
|
220
|
-
instruction="Be helpful, accurate, and safe",
|
|
221
|
-
tools=[],
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
# Initialize a trajectory
|
|
225
|
-
await harness.initialize(agent=agent)
|
|
226
|
-
|
|
227
|
-
# Adjudicate user input
|
|
228
|
-
adjudication = await harness.adjudicate(
|
|
229
|
-
Stage.PRE_MODEL,
|
|
230
|
-
Role.USER,
|
|
231
|
-
PromptContent(text="Hello, can you help me?"),
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
if adjudication.is_allowed:
|
|
235
|
-
# Proceed with agent logic
|
|
236
|
-
pass
|
|
237
|
-
elif adjudication.is_denied:
|
|
238
|
-
print(f"Request blocked: {adjudication.reason}")
|
|
239
|
-
|
|
240
|
-
# Finalize the trajectory
|
|
241
|
-
await harness.finalize()
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Remote and Local Harnesses
|
|
245
|
-
|
|
246
|
-
### Cedar Policy Harness (Local Only)
|
|
247
|
-
|
|
248
|
-
For a local harness deployment, you can use the `CedarPolicyHarness` to evaluate Cedar policies:
|
|
249
|
-
|
|
250
|
-
```python
|
|
251
|
-
from sondera.harness import CedarPolicyHarness
|
|
252
|
-
from sondera import Agent
|
|
253
|
-
|
|
254
|
-
# Define Cedar policies
|
|
255
|
-
policies = '''
|
|
256
|
-
@id("forbid-dangerous-bash")
|
|
257
|
-
forbid(
|
|
258
|
-
principal,
|
|
259
|
-
action == Coding_Agent::Action::"Bash",
|
|
260
|
-
resource
|
|
261
|
-
)
|
|
262
|
-
when {
|
|
263
|
-
context has parameters &&
|
|
264
|
-
(context.parameters.command like "*rm -rf /*" ||
|
|
265
|
-
context.parameters.command like "*mkfs*" ||
|
|
266
|
-
context.parameters.command like "*dd if=/dev/zero*" ||
|
|
267
|
-
context.parameters.command like "*> /dev/sda*")
|
|
268
|
-
};
|
|
269
|
-
'''
|
|
270
|
-
|
|
271
|
-
# Create local policy engine
|
|
272
|
-
harness = CedarPolicyHarness(
|
|
273
|
-
policy_set=policies,
|
|
274
|
-
agent=my_agent,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
await harness.initialize()
|
|
278
|
-
# Use same adjudication API as RemoteHarness
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## CLI & TUI
|
|
282
|
-
|
|
283
|
-
Launch the Sondera TUI for monitoring (note, requires a Sondera account and API key):
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
sondera
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
The TUI provides:
|
|
290
|
-
- Real-time agent and trajectory overview
|
|
291
|
-
- Adjudication history and policy violations
|
|
292
|
-
- Agent details and tool inspection
|
|
293
|
-
|
|
294
|
-
## Examples
|
|
295
|
-
|
|
296
|
-
See the [examples/](examples/) directory for complete demos:
|
|
297
|
-
|
|
298
|
-
- **LangGraph**: Investment chatbot with policy enforcement
|
|
299
|
-
- **ADK**: Payment and healthcare agents
|
|
300
|
-
- **Strands**: Various agent implementations
|
|
301
|
-
|
|
302
|
-
## Environment Variables
|
|
303
|
-
|
|
304
|
-
| Variable | Description | Default |
|
|
305
|
-
|----------|-------------|---------|
|
|
306
|
-
| `SONDERA_HARNESS_ENDPOINT` | Harness service endpoint | `localhost:50051` |
|
|
307
|
-
| `SONDERA_API_TOKEN` | JWT authentication token | Required for remote |
|
|
308
|
-
|
|
309
|
-
## Requirements
|
|
310
|
-
|
|
311
|
-
- Python 3.12 or higher (up to 3.14)
|
|
312
|
-
|
|
313
|
-
## Security
|
|
314
|
-
|
|
315
|
-
See [SECURITY.md](SECURITY.md) for security best practices and vulnerability reporting.
|
|
316
|
-
|
|
317
|
-
## Contributing
|
|
318
|
-
|
|
319
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
320
|
-
|
|
321
|
-
## License
|
|
322
|
-
|
|
323
|
-
MIT - see [LICENSE](LICENSE) for details.
|
|
File without changes
|
|
File without changes
|