sondera-harness 0.6.0__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.
Files changed (77) hide show
  1. sondera/__init__.py +111 -0
  2. sondera/__main__.py +4 -0
  3. sondera/adk/__init__.py +3 -0
  4. sondera/adk/analyze.py +222 -0
  5. sondera/adk/plugin.py +387 -0
  6. sondera/cli.py +22 -0
  7. sondera/exceptions.py +167 -0
  8. sondera/harness/__init__.py +6 -0
  9. sondera/harness/abc.py +102 -0
  10. sondera/harness/cedar/__init__.py +0 -0
  11. sondera/harness/cedar/harness.py +363 -0
  12. sondera/harness/cedar/schema.py +225 -0
  13. sondera/harness/sondera/__init__.py +0 -0
  14. sondera/harness/sondera/_grpc.py +354 -0
  15. sondera/harness/sondera/harness.py +890 -0
  16. sondera/langgraph/__init__.py +15 -0
  17. sondera/langgraph/analyze.py +543 -0
  18. sondera/langgraph/exceptions.py +19 -0
  19. sondera/langgraph/graph.py +210 -0
  20. sondera/langgraph/middleware.py +454 -0
  21. sondera/proto/google/protobuf/any_pb2.py +37 -0
  22. sondera/proto/google/protobuf/any_pb2.pyi +14 -0
  23. sondera/proto/google/protobuf/any_pb2_grpc.py +24 -0
  24. sondera/proto/google/protobuf/duration_pb2.py +37 -0
  25. sondera/proto/google/protobuf/duration_pb2.pyi +14 -0
  26. sondera/proto/google/protobuf/duration_pb2_grpc.py +24 -0
  27. sondera/proto/google/protobuf/empty_pb2.py +37 -0
  28. sondera/proto/google/protobuf/empty_pb2.pyi +9 -0
  29. sondera/proto/google/protobuf/empty_pb2_grpc.py +24 -0
  30. sondera/proto/google/protobuf/struct_pb2.py +47 -0
  31. sondera/proto/google/protobuf/struct_pb2.pyi +49 -0
  32. sondera/proto/google/protobuf/struct_pb2_grpc.py +24 -0
  33. sondera/proto/google/protobuf/timestamp_pb2.py +37 -0
  34. sondera/proto/google/protobuf/timestamp_pb2.pyi +14 -0
  35. sondera/proto/google/protobuf/timestamp_pb2_grpc.py +24 -0
  36. sondera/proto/google/protobuf/wrappers_pb2.py +53 -0
  37. sondera/proto/google/protobuf/wrappers_pb2.pyi +59 -0
  38. sondera/proto/google/protobuf/wrappers_pb2_grpc.py +24 -0
  39. sondera/proto/sondera/__init__.py +0 -0
  40. sondera/proto/sondera/core/__init__.py +0 -0
  41. sondera/proto/sondera/core/v1/__init__.py +0 -0
  42. sondera/proto/sondera/core/v1/primitives_pb2.py +88 -0
  43. sondera/proto/sondera/core/v1/primitives_pb2.pyi +259 -0
  44. sondera/proto/sondera/core/v1/primitives_pb2_grpc.py +24 -0
  45. sondera/proto/sondera/harness/__init__.py +0 -0
  46. sondera/proto/sondera/harness/v1/__init__.py +0 -0
  47. sondera/proto/sondera/harness/v1/harness_pb2.py +81 -0
  48. sondera/proto/sondera/harness/v1/harness_pb2.pyi +192 -0
  49. sondera/proto/sondera/harness/v1/harness_pb2_grpc.py +498 -0
  50. sondera/py.typed +0 -0
  51. sondera/settings.py +20 -0
  52. sondera/strands/__init__.py +5 -0
  53. sondera/strands/analyze.py +244 -0
  54. sondera/strands/harness.py +333 -0
  55. sondera/tui/__init__.py +0 -0
  56. sondera/tui/app.py +309 -0
  57. sondera/tui/screens/__init__.py +5 -0
  58. sondera/tui/screens/adjudication.py +184 -0
  59. sondera/tui/screens/agent.py +158 -0
  60. sondera/tui/screens/trajectory.py +158 -0
  61. sondera/tui/widgets/__init__.py +23 -0
  62. sondera/tui/widgets/agent_card.py +94 -0
  63. sondera/tui/widgets/agent_list.py +73 -0
  64. sondera/tui/widgets/recent_adjudications.py +52 -0
  65. sondera/tui/widgets/recent_trajectories.py +54 -0
  66. sondera/tui/widgets/summary.py +57 -0
  67. sondera/tui/widgets/tool_card.py +33 -0
  68. sondera/tui/widgets/violation_panel.py +72 -0
  69. sondera/tui/widgets/violations_list.py +78 -0
  70. sondera/tui/widgets/violations_summary.py +104 -0
  71. sondera/types.py +346 -0
  72. sondera_harness-0.6.0.dist-info/METADATA +323 -0
  73. sondera_harness-0.6.0.dist-info/RECORD +77 -0
  74. sondera_harness-0.6.0.dist-info/WHEEL +5 -0
  75. sondera_harness-0.6.0.dist-info/entry_points.txt +2 -0
  76. sondera_harness-0.6.0.dist-info/licenses/LICENSE +21 -0
  77. sondera_harness-0.6.0.dist-info/top_level.txt +1 -0
sondera/adk/plugin.py ADDED
@@ -0,0 +1,387 @@
1
+ """
2
+ Sondera Harness Plugin for Google ADK integration.
3
+
4
+ This plugin implements the ADK BasePlugin callback patterns for policy enforcement,
5
+ guardrails, and security controls across agent workflows using the Sondera Harness.
6
+ """
7
+
8
+ import logging
9
+ from typing import Any, cast
10
+
11
+ from google.adk.agents.base_agent import BaseAgent
12
+ from google.adk.agents.callback_context import CallbackContext
13
+ from google.adk.agents.invocation_context import InvocationContext
14
+ from google.adk.agents.llm_agent import LlmAgent
15
+ from google.adk.events.event import Event
16
+ from google.adk.models.llm_request import LlmRequest
17
+ from google.adk.models.llm_response import LlmResponse
18
+ from google.adk.plugins.base_plugin import BasePlugin
19
+ from google.adk.tools.base_tool import BaseTool
20
+ from google.adk.tools.tool_context import ToolContext
21
+ from google.genai import types as genai_types
22
+
23
+ from sondera.adk.analyze import format
24
+ from sondera.harness import Harness
25
+ from sondera.types import (
26
+ PromptContent,
27
+ Role,
28
+ Stage,
29
+ ToolRequestContent,
30
+ ToolResponseContent,
31
+ )
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class SonderaHarnessPlugin(BasePlugin):
37
+ """Sondera Harness Plugin for ADK integration.
38
+
39
+ This plugin integrates with the Sondera Harness for policy enforcement,
40
+ guardrails, and governance across ADK agent workflows. It implements
41
+ the ADK BasePlugin interface and uses dependency injection for the
42
+ Harness instance.
43
+
44
+ The plugin intercepts agent execution at key points:
45
+ - User message: Initialize trajectory and evaluate user input
46
+ - Before/after model: Evaluate model requests and responses
47
+ - Before/after tool: Evaluate tool calls and results
48
+ - After run: Finalize the trajectory
49
+
50
+ Example:
51
+ ```python
52
+ from sondera.adk import SonderaHarnessPlugin
53
+ from sondera.harness import RemoteHarness
54
+ from google.adk import Agent
55
+ from google.adk.runners import Runner
56
+
57
+ # Create a harness instance
58
+ harness = RemoteHarness(
59
+ sondera_harness_endpoint="localhost:50051",
60
+ sondera_api_key="<YOUR_SONDERA_API_KEY>",
61
+ )
62
+
63
+ # Create the plugin with the harness
64
+ plugin = SonderaHarnessPlugin(harness=harness)
65
+
66
+ # Create agent and runner with the plugin
67
+ agent = Agent(name="my-agent", model="gemini-2.0-flash", ...)
68
+ runner = Runner(
69
+ agent=agent,
70
+ app_name="my-app",
71
+ plugins=[plugin],
72
+ )
73
+ ```
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ harness: Harness,
79
+ *,
80
+ logger_instance: logging.Logger | None = None,
81
+ ):
82
+ """Initialize the Sondera Harness Plugin.
83
+
84
+ Args:
85
+ harness: The Sondera Harness instance to use for policy enforcement.
86
+ Can be RemoteHarness for production or LocalHarness for testing.
87
+ logger_instance: Optional custom logger instance.
88
+ """
89
+ super().__init__(name="sondera_harness")
90
+ self._harness = harness
91
+ self._log = logger_instance or logger
92
+
93
+ # -------------------------------------------------------------------------
94
+ # User Message Callback
95
+ # -------------------------------------------------------------------------
96
+
97
+ async def on_user_message_callback(
98
+ self,
99
+ *,
100
+ invocation_context: InvocationContext,
101
+ user_message: genai_types.Content,
102
+ ) -> genai_types.Content | None:
103
+ """Callback executed when a user message is received.
104
+
105
+ Initializes the harness trajectory and evaluates the user input
106
+ against policies before the agent processes it.
107
+
108
+ Args:
109
+ invocation_context: The context for the entire invocation.
110
+ user_message: The message content input by user.
111
+
112
+ Returns:
113
+ Modified content if policy violation, None to proceed normally.
114
+ """
115
+ # Initialize trajectory with agent metadata
116
+ agent = format(
117
+ cast(LlmAgent, invocation_context.agent),
118
+ invocation_context.app_name,
119
+ invocation_context.app_name,
120
+ )
121
+ await self._harness.initialize(agent=agent)
122
+
123
+ # Extract text content from user message
124
+ content = None
125
+ if user_message.parts is not None:
126
+ content = user_message.parts[-1].text
127
+ if not content:
128
+ return None
129
+
130
+ # Adjudicate user input
131
+ adjudication = await self._harness.adjudicate(
132
+ Stage.PRE_MODEL, Role.USER, PromptContent(text=content)
133
+ )
134
+ self._log.info(
135
+ f"[SonderaHarness] User message adjudication for trajectory {self._harness.trajectory_id}"
136
+ )
137
+
138
+ if adjudication.is_denied:
139
+ return genai_types.Content(
140
+ parts=[genai_types.Part(text=adjudication.reason)]
141
+ )
142
+ return None
143
+
144
+ # -------------------------------------------------------------------------
145
+ # Agent Callbacks
146
+ # -------------------------------------------------------------------------
147
+
148
+ async def before_agent_callback(
149
+ self,
150
+ *,
151
+ agent: BaseAgent,
152
+ callback_context: CallbackContext,
153
+ ) -> genai_types.Content | None:
154
+ """Callback executed before an agent's primary logic is invoked.
155
+
156
+ Args:
157
+ agent: The agent that is about to run.
158
+ callback_context: The context for the agent invocation.
159
+
160
+ Returns:
161
+ None to allow agent to proceed normally.
162
+ """
163
+ self._log.debug(f"[SonderaHarness] Before agent: {agent.name}")
164
+ return None
165
+
166
+ async def after_agent_callback(
167
+ self,
168
+ *,
169
+ agent: BaseAgent,
170
+ callback_context: CallbackContext,
171
+ ) -> genai_types.Content | None:
172
+ """Callback executed after an agent's primary logic has completed.
173
+
174
+ Args:
175
+ agent: The agent that has just run.
176
+ callback_context: The context for the agent invocation.
177
+
178
+ Returns:
179
+ None to use original agent response.
180
+ """
181
+ self._log.debug(f"[SonderaHarness] After agent: {agent.name}")
182
+ return None
183
+
184
+ # -------------------------------------------------------------------------
185
+ # Model Callbacks
186
+ # -------------------------------------------------------------------------
187
+
188
+ async def before_model_callback(
189
+ self,
190
+ *,
191
+ callback_context: CallbackContext,
192
+ llm_request: LlmRequest,
193
+ ) -> LlmResponse | None:
194
+ """Callback executed before a request is sent to the model.
195
+
196
+ Evaluates the model request against policies.
197
+
198
+ Args:
199
+ callback_context: The context for the current agent call.
200
+ llm_request: The prepared request object to be sent to the model.
201
+
202
+ Returns:
203
+ LlmResponse if policy violation, None to proceed normally.
204
+ """
205
+ self._log.debug(
206
+ f"[SonderaHarness] Before model call for trajectory {self._harness.trajectory_id}"
207
+ )
208
+ adjudication = await self._harness.adjudicate(
209
+ Stage.PRE_MODEL, Role.MODEL, PromptContent(text="")
210
+ )
211
+ self._log.info(
212
+ f"[SonderaHarness] Before model adjudication for trajectory {self._harness.trajectory_id}"
213
+ )
214
+
215
+ if adjudication.is_denied:
216
+ return LlmResponse(
217
+ content=genai_types.Content(
218
+ parts=[genai_types.Part(text=adjudication.reason)]
219
+ )
220
+ )
221
+ return None
222
+
223
+ async def after_model_callback(
224
+ self,
225
+ *,
226
+ callback_context: CallbackContext,
227
+ llm_response: LlmResponse,
228
+ ) -> LlmResponse | None:
229
+ """Callback executed after a response is received from the model.
230
+
231
+ Evaluates the model response against policies.
232
+
233
+ Args:
234
+ callback_context: The context for the current agent call.
235
+ llm_response: The response object received from the model.
236
+
237
+ Returns:
238
+ Modified LlmResponse if policy violation, None to use original.
239
+ """
240
+ self._log.debug("[SonderaHarness] After model call")
241
+
242
+ # Extract text content from response
243
+ content = None
244
+ if llm_response.content is not None and llm_response.content.parts is not None:
245
+ content = llm_response.content.parts[-1].text
246
+
247
+ if not content:
248
+ return None
249
+
250
+ adjudication = await self._harness.adjudicate(
251
+ Stage.POST_MODEL, Role.MODEL, PromptContent(text=content)
252
+ )
253
+ self._log.info(
254
+ f"[SonderaHarness] After model adjudication for trajectory {self._harness.trajectory_id}"
255
+ )
256
+
257
+ if adjudication.is_denied:
258
+ return LlmResponse(
259
+ content=genai_types.Content(
260
+ parts=[genai_types.Part(text=adjudication.reason)]
261
+ )
262
+ )
263
+ return None
264
+
265
+ # -------------------------------------------------------------------------
266
+ # Tool Callbacks
267
+ # -------------------------------------------------------------------------
268
+
269
+ async def before_tool_callback(
270
+ self,
271
+ *,
272
+ tool: BaseTool,
273
+ tool_args: dict[str, Any],
274
+ tool_context: ToolContext,
275
+ ) -> dict[str, Any] | None:
276
+ """Callback executed before a tool is called.
277
+
278
+ Evaluates the tool request against policies.
279
+
280
+ Args:
281
+ tool: The tool instance that is about to be executed.
282
+ tool_args: The dictionary of arguments for the tool.
283
+ tool_context: The context specific to the tool execution.
284
+
285
+ Returns:
286
+ Dict result if policy violation (stops tool), None to proceed.
287
+ """
288
+ self._log.debug(f"[SonderaHarness] Before tool: {tool.name}")
289
+
290
+ adjudication = await self._harness.adjudicate(
291
+ Stage.PRE_TOOL,
292
+ Role.TOOL,
293
+ ToolRequestContent(tool_id=tool.name, args=tool_args),
294
+ )
295
+ self._log.info(
296
+ f"[SonderaHarness] Before tool adjudication for trajectory {self._harness.trajectory_id}"
297
+ )
298
+
299
+ if adjudication.is_denied:
300
+ return {"error": f"Tool blocked: {adjudication.reason}"}
301
+ return None
302
+
303
+ async def after_tool_callback(
304
+ self,
305
+ *,
306
+ tool: BaseTool,
307
+ tool_args: dict[str, Any],
308
+ tool_context: ToolContext,
309
+ result: dict[str, Any],
310
+ ) -> dict[str, Any] | None:
311
+ """Callback executed after a tool has been called.
312
+
313
+ Evaluates the tool result against policies.
314
+
315
+ Args:
316
+ tool: The tool instance that has just been executed.
317
+ tool_args: The original arguments passed to the tool.
318
+ tool_context: The context specific to the tool execution.
319
+ result: The dictionary returned by the tool invocation.
320
+
321
+ Returns:
322
+ Modified result dict if policy violation, None to use original.
323
+ """
324
+ self._log.debug(f"[SonderaHarness] After tool: {tool.name}")
325
+
326
+ adjudication = await self._harness.adjudicate(
327
+ Stage.POST_TOOL,
328
+ Role.TOOL,
329
+ ToolResponseContent(tool_id=tool.name, response=result),
330
+ )
331
+ self._log.info(
332
+ f"[SonderaHarness] After tool adjudication for trajectory {self._harness.trajectory_id}"
333
+ )
334
+
335
+ if adjudication.is_denied:
336
+ return {"error": f"Tool result blocked: {adjudication.reason}"}
337
+ return None
338
+
339
+ # -------------------------------------------------------------------------
340
+ # Event Callback
341
+ # -------------------------------------------------------------------------
342
+
343
+ async def on_event_callback(
344
+ self,
345
+ *,
346
+ invocation_context: InvocationContext,
347
+ event: Event,
348
+ ) -> Event | None:
349
+ """Callback executed after an event is yielded from runner.
350
+
351
+ Args:
352
+ invocation_context: The context for the entire invocation.
353
+ event: The event raised by the runner.
354
+
355
+ Returns:
356
+ None to use original event.
357
+ """
358
+ self._log.debug(f"[SonderaHarness] Event: {event.author}")
359
+ return None
360
+
361
+ # -------------------------------------------------------------------------
362
+ # Runner Lifecycle Callbacks
363
+ # -------------------------------------------------------------------------
364
+
365
+ async def after_run_callback(
366
+ self,
367
+ *,
368
+ invocation_context: InvocationContext,
369
+ ) -> None:
370
+ """Callback executed after an ADK runner run has completed.
371
+
372
+ Finalizes the harness trajectory.
373
+
374
+ Args:
375
+ invocation_context: The context for the entire invocation.
376
+ """
377
+ self._log.info(
378
+ f"[SonderaHarness] Finalizing trajectory {self._harness.trajectory_id}"
379
+ )
380
+ await self._harness.finalize()
381
+
382
+ async def close(self) -> None:
383
+ """Method executed when the runner is closed.
384
+
385
+ Used for cleanup tasks such as closing network connections.
386
+ """
387
+ self._log.debug("[SonderaHarness] Plugin closed")
sondera/cli.py ADDED
@@ -0,0 +1,22 @@
1
+ """The Sondera Harness CLI and TUI entrypoints."""
2
+
3
+ import click
4
+ from click_default_group import DefaultGroup
5
+
6
+ from sondera.tui.app import SonderaApp
7
+
8
+
9
+ @click.group(cls=DefaultGroup, default="default", default_if_no_args=True)
10
+ def cli() -> None:
11
+ """A CLI and TUI for interacting with the Sondera Harness SDK and Harness Service."""
12
+
13
+
14
+ @cli.command()
15
+ def default() -> None:
16
+ """Launch the Sondera Harness TUI."""
17
+ app = SonderaApp()
18
+ app.run()
19
+
20
+
21
+ if __name__ == "__main__":
22
+ cli()
sondera/exceptions.py ADDED
@@ -0,0 +1,167 @@
1
+ """Sondera SDK exception hierarchy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from sondera.types import Adjudication, Stage
9
+
10
+
11
+ class SonderaError(Exception):
12
+ """Base exception for all Sondera SDK errors."""
13
+
14
+ pass
15
+
16
+
17
+ class ConfigurationError(SonderaError):
18
+ """Raised when there is a configuration error.
19
+
20
+ Examples:
21
+ - Missing required API key
22
+ - Invalid endpoint format
23
+ - Missing required settings
24
+ """
25
+
26
+ pass
27
+
28
+
29
+ class AuthenticationError(SonderaError):
30
+ """Raised when authentication fails.
31
+
32
+ Examples:
33
+ - Invalid or expired JWT token
34
+ - Missing authentication credentials
35
+ - Token lacks required claims
36
+ """
37
+
38
+ pass
39
+
40
+
41
+ class ConnectionError(SonderaError):
42
+ """Raised when connection to the harness service fails.
43
+
44
+ Examples:
45
+ - Network unreachable
46
+ - Service unavailable
47
+ - TLS handshake failure
48
+ """
49
+
50
+ pass
51
+
52
+
53
+ class TrajectoryError(SonderaError):
54
+ """Raised for trajectory-related errors.
55
+
56
+ Examples:
57
+ - Trajectory not initialized
58
+ - Invalid trajectory state
59
+ - Trajectory already finalized
60
+ """
61
+
62
+ pass
63
+
64
+
65
+ class TrajectoryNotInitializedError(TrajectoryError):
66
+ """Raised when attempting operations without an active trajectory."""
67
+
68
+ def __init__(self, message: str = "No active trajectory. Call initialize() first."):
69
+ super().__init__(message)
70
+
71
+
72
+ class PolicyError(SonderaError):
73
+ """Base exception for policy-related errors."""
74
+
75
+ pass
76
+
77
+
78
+ class PolicyViolationError(PolicyError):
79
+ """Raised when a policy violation blocks execution.
80
+
81
+ Attributes:
82
+ stage: The execution stage where the violation occurred
83
+ reason: The policy violation reason
84
+ adjudication: The full adjudication result
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ stage: Stage,
90
+ reason: str,
91
+ adjudication: Adjudication | None = None,
92
+ ):
93
+ self.stage = stage
94
+ self.reason = reason
95
+ self.adjudication = adjudication
96
+ super().__init__(f"Policy violation at {stage.value}: {reason}")
97
+
98
+
99
+ class PolicyEvaluationError(PolicyError):
100
+ """Raised when policy evaluation fails.
101
+
102
+ Examples:
103
+ - Invalid policy syntax
104
+ - Schema mismatch
105
+ - Policy engine internal error
106
+ """
107
+
108
+ pass
109
+
110
+
111
+ class AgentError(SonderaError):
112
+ """Raised for agent-related errors.
113
+
114
+ Examples:
115
+ - Invalid agent configuration
116
+ - Agent not found
117
+ - Agent registration failed
118
+ """
119
+
120
+ pass
121
+
122
+
123
+ class ToolError(SonderaError):
124
+ """Raised for tool-related errors.
125
+
126
+ Examples:
127
+ - Tool not found
128
+ - Invalid tool arguments
129
+ - Tool execution blocked
130
+ """
131
+
132
+ def __init__(
133
+ self,
134
+ tool_name: str,
135
+ message: str,
136
+ *,
137
+ tool_args: dict | None = None,
138
+ ):
139
+ self.tool_name = tool_name
140
+ self.tool_args = tool_args
141
+ super().__init__(f"Tool '{tool_name}': {message}")
142
+
143
+
144
+ class ToolBlockedError(ToolError):
145
+ """Raised when a tool execution is blocked by policy."""
146
+
147
+ def __init__(
148
+ self,
149
+ tool_name: str,
150
+ reason: str,
151
+ *,
152
+ tool_args: dict | None = None,
153
+ ):
154
+ self.reason = reason
155
+ super().__init__(tool_name, f"Blocked - {reason}", tool_args=tool_args)
156
+
157
+
158
+ class SerializationError(SonderaError):
159
+ """Raised when serialization or deserialization fails.
160
+
161
+ Examples:
162
+ - Invalid protobuf message
163
+ - JSON encoding error
164
+ - Type conversion failure
165
+ """
166
+
167
+ pass
@@ -0,0 +1,6 @@
1
+ from sondera.harness.cedar.harness import CedarPolicyHarness
2
+ from sondera.harness.sondera.harness import SonderaRemoteHarness
3
+
4
+ from .abc import Harness
5
+
6
+ __all__ = ["SonderaRemoteHarness", "CedarPolicyHarness", "Harness"]
sondera/harness/abc.py ADDED
@@ -0,0 +1,102 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from sondera.types import Adjudication, Agent, Content, Role, Stage
4
+
5
+
6
+ class Harness(ABC):
7
+ """Abstract base class defining the interface for Sondera Harness implementations.
8
+
9
+ This ABC defines the core contract for harness implementations that integrate
10
+ with the Sondera Platform for agent governance, trajectory management, and
11
+ real-time step adjudication.
12
+
13
+ Subclasses must implement:
14
+ - resume: Resume an existing trajectory for continued execution
15
+ - initialize: Set up a new trajectory for agent execution
16
+ - finalize: Complete and save the current trajectory
17
+ - adjudicate: Evaluate a trajectory step against policies
18
+
19
+ Attributes:
20
+ trajectory_id: The current active trajectory ID (None if no active trajectory)
21
+ agent: The agent being governed (may be None until initialize is called)
22
+ """
23
+
24
+ _trajectory_id: str | None
25
+ _agent: Agent | None
26
+
27
+ @property
28
+ def trajectory_id(self) -> str | None:
29
+ """Get the current trajectory ID."""
30
+ return self._trajectory_id
31
+
32
+ @property
33
+ def agent(self) -> Agent | None:
34
+ """Get the current agent."""
35
+ return self._agent
36
+
37
+ @abstractmethod
38
+ async def resume(self, trajectory_id, *, agent: Agent | None = None) -> None:
39
+ """Resume an existing trajectory for the given agent."""
40
+ ...
41
+
42
+ @abstractmethod
43
+ async def initialize(self, *, agent: Agent | None = None) -> None:
44
+ """Initialize a new trajectory for the current execution.
45
+
46
+ This method should:
47
+ 1. Register the agent with the Sondera Platform if not already registered
48
+ 2. Create a new trajectory for tracking the agent's execution
49
+ 3. Store the trajectory ID for subsequent adjudication calls
50
+
51
+ Args:
52
+ agent: Optional agent to use for this trajectory. If provided, overrides
53
+ any agent set during construction.
54
+
55
+ Raises:
56
+ ValueError: If no agent is provided and none was set during construction
57
+ RuntimeError: If connection to the harness service fails
58
+ """
59
+ ...
60
+
61
+ @abstractmethod
62
+ async def finalize(self) -> None:
63
+ """Finalize the current trajectory and save artifacts.
64
+
65
+ This method should:
66
+ 1. Mark the trajectory as completed
67
+ 2. Persist any remaining trajectory data
68
+ 3. Clear the active trajectory state
69
+
70
+ Raises:
71
+ ValueError: If no active trajectory exists (initialize not called)
72
+ RuntimeError: If finalization fails
73
+ """
74
+ ...
75
+
76
+ @abstractmethod
77
+ async def adjudicate(
78
+ self,
79
+ stage: Stage,
80
+ role: Role,
81
+ content: Content,
82
+ ) -> Adjudication:
83
+ """Adjudicate a trajectory step using the policy engine.
84
+
85
+ Evaluates the given step against configured policies and returns
86
+ an adjudication decision (ALLOW, DENY, or ESCALATE).
87
+
88
+ Args:
89
+ stage: The execution stage (PRE_RUN, PRE_MODEL, POST_MODEL,
90
+ PRE_TOOL, POST_TOOL, POST_RUN)
91
+ role: The role of the actor (USER, MODEL, TOOL, SYSTEM)
92
+ content: The content to evaluate (PromptContent, ToolRequestContent,
93
+ or ToolResponseContent)
94
+
95
+ Returns:
96
+ Adjudication containing the decision and reason
97
+
98
+ Raises:
99
+ RuntimeError: If no active trajectory exists (initialize not called)
100
+ ValueError: If the content type is not supported
101
+ """
102
+ ...
File without changes