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 CHANGED
@@ -1,28 +1,44 @@
1
- """Sondera SDK for Python - Agent governance and policy enforcement.
1
+ """Sondera Harness - Steer agents with rules, not prompts.
2
2
 
3
- This SDK provides tools for integrating AI agents with the Sondera Platform
4
- for policy enforcement, guardrails, and governance.
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
- Main Components:
7
- - Harness: Abstract base class for harness implementations
8
- - RemoteHarness: Production harness connecting to Sondera Platform
9
- - CedarPolicyEngine: Local policy-as-code engine using Cedar
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/LangChain middleware
22
+ - sondera.langgraph: LangGraph middleware
13
23
  - sondera.adk: Google ADK plugin
14
- - sondera.strands: Strands Agent SDK hook
24
+ - sondera.strands: Strands lifecycle hooks
15
25
 
16
26
  Example:
17
- >>> from sondera import SonderaRemoteHarness, Agent
18
- >>> harness = SonderaRemoteHarness(sondera_api_key="<YOUR_SONDERA_API_KEY>")
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="langchain",
22
- ... name="My Agent",
32
+ ... provider_id="local",
33
+ ... name="My_Agent",
23
34
  ... description="A helpful assistant",
24
- ... instruction="Be helpful and concise",
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.0-flash", ...)
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",
@@ -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 = 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
- reason = f"Allowed by policies: {response.reason}"
256
- return Adjudication(decision=Decision.ALLOW, reason=reason)
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
- reason = f"Denied by policies: {response.reason}"
259
- return Adjudication(decision=Decision.DENY, reason=reason)
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 = Context(
315
- {"parameters_json": json.dumps(content.args), "parameters": content.args},
316
- schema=self._schema,
317
- action=action_uid,
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 = Context(
349
- {
350
- "response_json": json.dumps(content.response, default=str),
351
- "response": content.response,
352
- },
353
- schema=self._schema,
354
- action=action_uid,
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,
@@ -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=None, # Protobuf Tool message doesn't have an id field
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
- response=pb_tool.response if pb_tool.response else None,
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(mode=mode, adjudication=adjudication, step=trajectory_step)
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 tools
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\"\xba\x01\n\x04Tool\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\x15\n\x08response\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x06source\x18\x05 \x01(\x0b\x32\x1b.sondera.core.v1.SourceCodeH\x01\x88\x01\x01\x42\x0b\n\t_responseB\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')
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=2474
43
- _globals['_TRAJECTORYSTATUS']._serialized_end=2685
44
- _globals['_STAGE']._serialized_start=2688
45
- _globals['_STAGE']._serialized_end=2841
46
- _globals['_ROLE']._serialized_start=2843
47
- _globals['_ROLE']._serialized_end=2934
48
- _globals['_DECISION']._serialized_start=2936
49
- _globals['_DECISION']._serialized_end=3034
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=411
56
- _globals['_AGENT']._serialized_start=414
57
- _globals['_AGENT']._serialized_end=548
58
- _globals['_PROMPT']._serialized_start=550
59
- _globals['_PROMPT']._serialized_end=572
60
- _globals['_TOOLREQUEST']._serialized_start=574
61
- _globals['_TOOLREQUEST']._serialized_end=642
62
- _globals['_TOOLRESPONSE']._serialized_start=644
63
- _globals['_TOOLRESPONSE']._serialized_end=717
64
- _globals['_CONTENT']._serialized_start=720
65
- _globals['_CONTENT']._serialized_end=898
66
- _globals['_TRAJECTORYSTEP']._serialized_start=901
67
- _globals['_TRAJECTORYSTEP']._serialized_end=1084
68
- _globals['_TRAJECTORY']._serialized_start=1087
69
- _globals['_TRAJECTORY']._serialized_end=1590
70
- _globals['_TRAJECTORY_METADATAENTRY']._serialized_start=1491
71
- _globals['_TRAJECTORY_METADATAENTRY']._serialized_end=1562
72
- _globals['_CHECK']._serialized_start=1592
73
- _globals['_CHECK']._serialized_end=1664
74
- _globals['_GUARDRAILCONTEXT']._serialized_start=1667
75
- _globals['_GUARDRAILCONTEXT']._serialized_end=1819
76
- _globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_start=1750
77
- _globals['_GUARDRAILCONTEXT_CHECKSENTRY']._serialized_end=1819
78
- _globals['_POLICYANNOTATIONS']._serialized_start=1822
79
- _globals['_POLICYANNOTATIONS']._serialized_end=2018
80
- _globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_start=1950
81
- _globals['_POLICYANNOTATIONS_CUSTOMENTRY']._serialized_end=1995
82
- _globals['_ADJUDICATION']._serialized_start=2021
83
- _globals['_ADJUDICATION']._serialized_end=2173
84
- _globals['_ADJUDICATEDSTEP']._serialized_start=2176
85
- _globals['_ADJUDICATEDSTEP']._serialized_end=2348
86
- _globals['_ADJUDICATEDTRAJECTORY']._serialized_start=2350
87
- _globals['_ADJUDICATEDTRAJECTORY']._serialized_end=2471
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=g6Ewr03q_2JZGIJ9APVs-6MLvZsAEtQJ2dcYEp4a6oc,2695
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=T6_vZQwXO4CtOW1B9X9JhuSuN7FvE6qq4T_Li1KLZbc,9556
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=HfVdbzp0bHc4YIs0MjNO-pwrMMex1AsKbQWoQNp7dmI,13047
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=mkskBjz4REVhV79r_zgrRBQjwGqbKom7NlpJr8aM0P8,12128
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=wAhk5H3BZvyi72RtUo1nuJFqp-U4UXe44l2eODDJrF8,12504
18
- sondera/harness/sondera/harness.py,sha256=ueR2Khfmz3R4-fZrQsv_AtjOC7OAN861UEX0D-Okmwg,31710
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=12rqeMQnq7OOVIaQBE3ig7hiMUtOK-ENCuiz-T91nMo,9231
46
- sondera/proto/sondera/core/v1/primitives_pb2.pyi,sha256=IFzGfoUe2TcQqZJRT-jmyofosmOOmrtWoTRPIqqLa5E,11824
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.1.dist-info/licenses/LICENSE,sha256=DmSfauhgrslTxZOcDAmcYqsqsKBkMqVh3PYdjPghNbU,1070
74
- sondera_harness-0.6.1.dist-info/METADATA,sha256=ZNFNrXzrWJEHRFmLfAqqmRYwxgxP-4oheQRKjpcT_EQ,8899
75
- sondera_harness-0.6.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
76
- sondera_harness-0.6.1.dist-info/entry_points.txt,sha256=e9aHpIPUUlP5MPKORk7k6ekUfZLN3RyO1MEJa-nCzK4,44
77
- sondera_harness-0.6.1.dist-info/top_level.txt,sha256=BR0X8Gq9CCpwbQg5evpQfy5zwp9fTuGnlJhXSNqQ_hA,8
78
- sondera_harness-0.6.1.dist-info/RECORD,,
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  sondera = sondera.cli:cli
3
+ sondera-harness = sondera.cli:cli
@@ -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
- [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
52
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](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.