tactus 0.31.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 (160) hide show
  1. tactus/__init__.py +49 -0
  2. tactus/adapters/__init__.py +9 -0
  3. tactus/adapters/broker_log.py +76 -0
  4. tactus/adapters/cli_hitl.py +189 -0
  5. tactus/adapters/cli_log.py +223 -0
  6. tactus/adapters/cost_collector_log.py +56 -0
  7. tactus/adapters/file_storage.py +367 -0
  8. tactus/adapters/http_callback_log.py +109 -0
  9. tactus/adapters/ide_log.py +71 -0
  10. tactus/adapters/lua_tools.py +336 -0
  11. tactus/adapters/mcp.py +289 -0
  12. tactus/adapters/mcp_manager.py +196 -0
  13. tactus/adapters/memory.py +53 -0
  14. tactus/adapters/plugins.py +419 -0
  15. tactus/backends/http_backend.py +58 -0
  16. tactus/backends/model_backend.py +35 -0
  17. tactus/backends/pytorch_backend.py +110 -0
  18. tactus/broker/__init__.py +12 -0
  19. tactus/broker/client.py +247 -0
  20. tactus/broker/protocol.py +183 -0
  21. tactus/broker/server.py +1123 -0
  22. tactus/broker/stdio.py +12 -0
  23. tactus/cli/__init__.py +7 -0
  24. tactus/cli/app.py +2245 -0
  25. tactus/cli/commands/__init__.py +0 -0
  26. tactus/core/__init__.py +32 -0
  27. tactus/core/config_manager.py +790 -0
  28. tactus/core/dependencies/__init__.py +14 -0
  29. tactus/core/dependencies/registry.py +180 -0
  30. tactus/core/dsl_stubs.py +2117 -0
  31. tactus/core/exceptions.py +66 -0
  32. tactus/core/execution_context.py +480 -0
  33. tactus/core/lua_sandbox.py +508 -0
  34. tactus/core/message_history_manager.py +236 -0
  35. tactus/core/mocking.py +286 -0
  36. tactus/core/output_validator.py +291 -0
  37. tactus/core/registry.py +499 -0
  38. tactus/core/runtime.py +2907 -0
  39. tactus/core/template_resolver.py +142 -0
  40. tactus/core/yaml_parser.py +301 -0
  41. tactus/docker/Dockerfile +61 -0
  42. tactus/docker/entrypoint.sh +69 -0
  43. tactus/dspy/__init__.py +39 -0
  44. tactus/dspy/agent.py +1144 -0
  45. tactus/dspy/broker_lm.py +181 -0
  46. tactus/dspy/config.py +212 -0
  47. tactus/dspy/history.py +196 -0
  48. tactus/dspy/module.py +405 -0
  49. tactus/dspy/prediction.py +318 -0
  50. tactus/dspy/signature.py +185 -0
  51. tactus/formatting/__init__.py +7 -0
  52. tactus/formatting/formatter.py +437 -0
  53. tactus/ide/__init__.py +9 -0
  54. tactus/ide/coding_assistant.py +343 -0
  55. tactus/ide/server.py +2223 -0
  56. tactus/primitives/__init__.py +49 -0
  57. tactus/primitives/control.py +168 -0
  58. tactus/primitives/file.py +229 -0
  59. tactus/primitives/handles.py +378 -0
  60. tactus/primitives/host.py +94 -0
  61. tactus/primitives/human.py +342 -0
  62. tactus/primitives/json.py +189 -0
  63. tactus/primitives/log.py +187 -0
  64. tactus/primitives/message_history.py +157 -0
  65. tactus/primitives/model.py +163 -0
  66. tactus/primitives/procedure.py +564 -0
  67. tactus/primitives/procedure_callable.py +318 -0
  68. tactus/primitives/retry.py +155 -0
  69. tactus/primitives/session.py +152 -0
  70. tactus/primitives/state.py +182 -0
  71. tactus/primitives/step.py +209 -0
  72. tactus/primitives/system.py +93 -0
  73. tactus/primitives/tool.py +375 -0
  74. tactus/primitives/tool_handle.py +279 -0
  75. tactus/primitives/toolset.py +229 -0
  76. tactus/protocols/__init__.py +38 -0
  77. tactus/protocols/chat_recorder.py +81 -0
  78. tactus/protocols/config.py +97 -0
  79. tactus/protocols/cost.py +31 -0
  80. tactus/protocols/hitl.py +71 -0
  81. tactus/protocols/log_handler.py +27 -0
  82. tactus/protocols/models.py +355 -0
  83. tactus/protocols/result.py +33 -0
  84. tactus/protocols/storage.py +90 -0
  85. tactus/providers/__init__.py +13 -0
  86. tactus/providers/base.py +92 -0
  87. tactus/providers/bedrock.py +117 -0
  88. tactus/providers/google.py +105 -0
  89. tactus/providers/openai.py +98 -0
  90. tactus/sandbox/__init__.py +63 -0
  91. tactus/sandbox/config.py +171 -0
  92. tactus/sandbox/container_runner.py +1099 -0
  93. tactus/sandbox/docker_manager.py +433 -0
  94. tactus/sandbox/entrypoint.py +227 -0
  95. tactus/sandbox/protocol.py +213 -0
  96. tactus/stdlib/__init__.py +10 -0
  97. tactus/stdlib/io/__init__.py +13 -0
  98. tactus/stdlib/io/csv.py +88 -0
  99. tactus/stdlib/io/excel.py +136 -0
  100. tactus/stdlib/io/file.py +90 -0
  101. tactus/stdlib/io/fs.py +154 -0
  102. tactus/stdlib/io/hdf5.py +121 -0
  103. tactus/stdlib/io/json.py +109 -0
  104. tactus/stdlib/io/parquet.py +83 -0
  105. tactus/stdlib/io/tsv.py +88 -0
  106. tactus/stdlib/loader.py +274 -0
  107. tactus/stdlib/tac/tactus/tools/done.tac +33 -0
  108. tactus/stdlib/tac/tactus/tools/log.tac +50 -0
  109. tactus/testing/README.md +273 -0
  110. tactus/testing/__init__.py +61 -0
  111. tactus/testing/behave_integration.py +380 -0
  112. tactus/testing/context.py +486 -0
  113. tactus/testing/eval_models.py +114 -0
  114. tactus/testing/evaluation_runner.py +222 -0
  115. tactus/testing/evaluators.py +634 -0
  116. tactus/testing/events.py +94 -0
  117. tactus/testing/gherkin_parser.py +134 -0
  118. tactus/testing/mock_agent.py +315 -0
  119. tactus/testing/mock_dependencies.py +234 -0
  120. tactus/testing/mock_hitl.py +171 -0
  121. tactus/testing/mock_registry.py +168 -0
  122. tactus/testing/mock_tools.py +133 -0
  123. tactus/testing/models.py +115 -0
  124. tactus/testing/pydantic_eval_runner.py +508 -0
  125. tactus/testing/steps/__init__.py +13 -0
  126. tactus/testing/steps/builtin.py +902 -0
  127. tactus/testing/steps/custom.py +69 -0
  128. tactus/testing/steps/registry.py +68 -0
  129. tactus/testing/test_runner.py +489 -0
  130. tactus/tracing/__init__.py +5 -0
  131. tactus/tracing/trace_manager.py +417 -0
  132. tactus/utils/__init__.py +1 -0
  133. tactus/utils/cost_calculator.py +72 -0
  134. tactus/utils/model_pricing.py +132 -0
  135. tactus/utils/safe_file_library.py +502 -0
  136. tactus/utils/safe_libraries.py +234 -0
  137. tactus/validation/LuaLexerBase.py +66 -0
  138. tactus/validation/LuaParserBase.py +23 -0
  139. tactus/validation/README.md +224 -0
  140. tactus/validation/__init__.py +7 -0
  141. tactus/validation/error_listener.py +21 -0
  142. tactus/validation/generated/LuaLexer.interp +231 -0
  143. tactus/validation/generated/LuaLexer.py +5548 -0
  144. tactus/validation/generated/LuaLexer.tokens +124 -0
  145. tactus/validation/generated/LuaLexerBase.py +66 -0
  146. tactus/validation/generated/LuaParser.interp +173 -0
  147. tactus/validation/generated/LuaParser.py +6439 -0
  148. tactus/validation/generated/LuaParser.tokens +124 -0
  149. tactus/validation/generated/LuaParserBase.py +23 -0
  150. tactus/validation/generated/LuaParserVisitor.py +118 -0
  151. tactus/validation/generated/__init__.py +7 -0
  152. tactus/validation/grammar/LuaLexer.g4 +123 -0
  153. tactus/validation/grammar/LuaParser.g4 +178 -0
  154. tactus/validation/semantic_visitor.py +817 -0
  155. tactus/validation/validator.py +157 -0
  156. tactus-0.31.0.dist-info/METADATA +1809 -0
  157. tactus-0.31.0.dist-info/RECORD +160 -0
  158. tactus-0.31.0.dist-info/WHEEL +4 -0
  159. tactus-0.31.0.dist-info/entry_points.txt +2 -0
  160. tactus-0.31.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,355 @@
1
+ """
2
+ Core Pydantic models used across Tactus protocols.
3
+ """
4
+
5
+ from typing import Any, Dict, Optional
6
+ from pydantic import BaseModel, Field
7
+ from datetime import datetime, timezone
8
+
9
+
10
+ def utc_now() -> datetime:
11
+ """Return current UTC time as a timezone-aware datetime."""
12
+ return datetime.now(timezone.utc)
13
+
14
+
15
+ class SourceLocation(BaseModel):
16
+ """Source code location for a checkpoint."""
17
+
18
+ file: str = Field(..., description="Absolute path to .tac file")
19
+ line: int = Field(..., description="Line number (1-indexed)")
20
+ function: Optional[str] = Field(None, description="Function/procedure name")
21
+ code_context: Optional[str] = Field(None, description="3 lines of surrounding code")
22
+
23
+ model_config = {"arbitrary_types_allowed": True}
24
+
25
+
26
+ class CheckpointEntry(BaseModel):
27
+ """A single checkpoint entry in the execution log (position-based)."""
28
+
29
+ position: int = Field(..., description="Checkpoint position (0, 1, 2, ...)")
30
+ type: str = Field(
31
+ ...,
32
+ description="Checkpoint type: agent_turn, model_predict, procedure_call, hitl_approval, explicit_checkpoint",
33
+ )
34
+ result: Any = Field(..., description="Result value from the checkpointed operation")
35
+ timestamp: datetime = Field(..., description="When checkpoint was created")
36
+ duration_ms: Optional[float] = Field(None, description="Operation duration in milliseconds")
37
+ input_hash: Optional[str] = Field(None, description="Hash of inputs for determinism checking")
38
+ run_id: Optional[str] = Field(
39
+ None, description="Unique identifier for the run that created this checkpoint"
40
+ )
41
+
42
+ # NEW: Debugging/tracing fields
43
+ source_location: Optional[SourceLocation] = Field(
44
+ None, description="Source code location where checkpoint was created"
45
+ )
46
+ captured_vars: Optional[Dict[str, Any]] = Field(
47
+ None, description="State snapshot at checkpoint"
48
+ )
49
+
50
+ model_config = {"arbitrary_types_allowed": True}
51
+
52
+
53
+ class ProcedureMetadata(BaseModel):
54
+ """Complete metadata for a procedure run (position-based execution log)."""
55
+
56
+ procedure_id: str = Field(..., description="Unique procedure identifier")
57
+ execution_log: list[CheckpointEntry] = Field(
58
+ default_factory=list,
59
+ description="Position-based execution log (ordered list of checkpoints)",
60
+ )
61
+ replay_index: int = Field(
62
+ default=0, description="Current replay position (next checkpoint to execute)"
63
+ )
64
+ state: Dict[str, Any] = Field(default_factory=dict, description="Mutable state dictionary")
65
+ lua_state: Dict[str, Any] = Field(
66
+ default_factory=dict, description="Lua-specific state (preserved across execution)"
67
+ )
68
+ status: str = Field(
69
+ default="RUNNING",
70
+ description="Current procedure status (RUNNING, WAITING_FOR_HUMAN, COMPLETED, FAILED)",
71
+ )
72
+ waiting_on_message_id: Optional[str] = Field(
73
+ default=None, description="Message ID if procedure is waiting for human response"
74
+ )
75
+
76
+ model_config = {"arbitrary_types_allowed": True}
77
+
78
+
79
+ class HITLResponse(BaseModel):
80
+ """Response from a human interaction."""
81
+
82
+ value: Any = Field(..., description="The response value from the human")
83
+ responded_at: datetime = Field(..., description="When the human responded")
84
+ timed_out: bool = Field(default=False, description="Whether the response timed out")
85
+
86
+ model_config = {"arbitrary_types_allowed": True}
87
+
88
+
89
+ class HITLRequest(BaseModel):
90
+ """Request for human interaction."""
91
+
92
+ request_type: str = Field(
93
+ ..., description="Type of interaction: 'approval', 'input', 'review', 'escalation'"
94
+ )
95
+ message: str = Field(..., description="Message to display to the human")
96
+ timeout_seconds: Optional[int] = Field(
97
+ default=None, description="Timeout in seconds (None = wait forever)"
98
+ )
99
+ default_value: Any = Field(default=None, description="Default value to return on timeout")
100
+ options: Optional[list[Dict[str, Any]]] = Field(
101
+ default=None, description="Options for review requests (list of {label, type} dicts)"
102
+ )
103
+ metadata: Dict[str, Any] = Field(
104
+ default_factory=dict, description="Additional context and metadata"
105
+ )
106
+
107
+ model_config = {"arbitrary_types_allowed": True}
108
+
109
+
110
+ class LogEvent(BaseModel):
111
+ """A log event from procedure execution."""
112
+
113
+ event_type: str = Field(default="log", description="Event type identifier")
114
+ level: str = Field(..., description="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
115
+ message: str = Field(..., description="Log message")
116
+ context: Optional[Dict[str, Any]] = Field(None, description="Additional context")
117
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
118
+ logger_name: Optional[str] = Field(None, description="Logger name")
119
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
120
+
121
+ model_config = {"arbitrary_types_allowed": True}
122
+
123
+
124
+ class AgentTurnEvent(BaseModel):
125
+ """Event emitted when an agent turn starts or completes."""
126
+
127
+ event_type: str = Field(default="agent_turn", description="Event type")
128
+ agent_name: str = Field(..., description="Agent name")
129
+ stage: str = Field(..., description="Stage: 'started' or 'completed'")
130
+ duration_ms: Optional[float] = Field(None, description="Duration in ms (for completed stage)")
131
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
132
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
133
+
134
+ model_config = {"arbitrary_types_allowed": True}
135
+
136
+
137
+ class AgentStreamChunkEvent(BaseModel):
138
+ """Event emitted for each chunk of streamed agent response."""
139
+
140
+ event_type: str = Field(default="agent_stream_chunk", description="Event type")
141
+ agent_name: str = Field(..., description="Agent name")
142
+ chunk_text: str = Field(..., description="Text chunk from this update")
143
+ accumulated_text: str = Field(..., description="Full text accumulated so far")
144
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
145
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
146
+
147
+ model_config = {"arbitrary_types_allowed": True}
148
+
149
+
150
+ class CostEvent(BaseModel):
151
+ """Cost event for a single LLM call with comprehensive tracing data."""
152
+
153
+ event_type: str = Field(default="cost", description="Event type")
154
+
155
+ # Agent/Model Info
156
+ agent_name: str = Field(..., description="Agent that made the call")
157
+ model: str = Field(..., description="Model used")
158
+ provider: str = Field(..., description="Provider (openai, bedrock, etc.)")
159
+
160
+ # Token Usage (Primary Metrics)
161
+ prompt_tokens: int = Field(..., description="Prompt tokens used")
162
+ completion_tokens: int = Field(..., description="Completion tokens used")
163
+ total_tokens: int = Field(..., description="Total tokens")
164
+
165
+ # Cost Calculation (Primary Metrics)
166
+ prompt_cost: float = Field(..., description="Cost for prompt tokens")
167
+ completion_cost: float = Field(..., description="Cost for completion tokens")
168
+ total_cost: float = Field(..., description="Total cost")
169
+
170
+ # Performance Metrics (Details)
171
+ duration_ms: Optional[float] = Field(None, description="Call duration in milliseconds")
172
+ latency_ms: Optional[float] = Field(None, description="Time to first token (if available)")
173
+
174
+ # Retry/Validation Metrics (Details)
175
+ retry_count: int = Field(default=0, description="Number of retries due to validation")
176
+ validation_errors: list[str] = Field(
177
+ default_factory=list, description="Validation errors encountered"
178
+ )
179
+
180
+ # Cache Metrics (Details)
181
+ cache_hit: bool = Field(default=False, description="Whether cache was used")
182
+ cache_tokens: Optional[int] = Field(None, description="Cached tokens used (if available)")
183
+ cache_cost: Optional[float] = Field(None, description="Cost saved via cache")
184
+
185
+ # Message Metrics (Details)
186
+ message_count: int = Field(default=0, description="Number of messages in conversation")
187
+ new_message_count: int = Field(default=0, description="New messages from this call")
188
+
189
+ # Request Metadata (Details)
190
+ request_id: Optional[str] = Field(None, description="Provider request ID (if available)")
191
+ model_version: Optional[str] = Field(None, description="Specific model version")
192
+ temperature: Optional[float] = Field(None, description="Temperature setting used")
193
+ max_tokens: Optional[int] = Field(None, description="Max tokens setting")
194
+
195
+ # Timestamps
196
+ timestamp: datetime = Field(default_factory=utc_now)
197
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
198
+
199
+ # Raw tracing data (for future analysis)
200
+ raw_tracing_data: Optional[Dict[str, Any]] = Field(
201
+ None, description="Any additional tracing data"
202
+ )
203
+
204
+ # Response data (new field)
205
+ response_data: Optional[Dict[str, Any]] = Field(
206
+ None, description="Agent's response data (extracted from result.data)"
207
+ )
208
+
209
+ model_config = {"arbitrary_types_allowed": True}
210
+
211
+
212
+ class ExecutionSummaryEvent(BaseModel):
213
+ """Summary event at the end of procedure execution."""
214
+
215
+ event_type: str = Field(default="execution_summary", description="Event type identifier")
216
+ result: Any = Field(..., description="Validated procedure result")
217
+ final_state: Dict[str, Any] = Field(default_factory=dict, description="Final state dictionary")
218
+ iterations: int = Field(default=0, description="Number of iterations executed")
219
+ tools_used: list[str] = Field(default_factory=list, description="List of tool names used")
220
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
221
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
222
+
223
+ # Cost tracking
224
+ total_cost: float = Field(default=0.0, description="Total LLM cost")
225
+ total_tokens: int = Field(default=0, description="Total tokens used")
226
+ cost_breakdown: list[Any] = Field(default_factory=list, description="Per-call cost details")
227
+
228
+ # Checkpoint tracking
229
+ checkpoint_count: int = Field(default=0, description="Total number of checkpoints")
230
+ checkpoint_types: Dict[str, int] = Field(
231
+ default_factory=dict, description="Checkpoint count by type"
232
+ )
233
+ checkpoint_duration_ms: Optional[float] = Field(None, description="Total checkpoint duration")
234
+
235
+ # Exit code and error information
236
+ exit_code: Optional[int] = Field(
237
+ default=0, description="Exit code (0 for success, non-zero for error)"
238
+ )
239
+ error_message: Optional[str] = Field(
240
+ default=None, description="Error message if execution failed"
241
+ )
242
+ error_type: Optional[str] = Field(
243
+ default=None, description="Error type/class name if execution failed"
244
+ )
245
+ traceback: Optional[str] = Field(default=None, description="Full traceback if execution failed")
246
+
247
+ model_config = {"arbitrary_types_allowed": True}
248
+
249
+
250
+ class ToolCallEvent(BaseModel):
251
+ """Event emitted when a tool is called by an agent."""
252
+
253
+ event_type: str = Field(default="tool_call", description="Event type")
254
+ agent_name: str = Field(..., description="Agent that called the tool")
255
+ tool_name: str = Field(..., description="Name of the tool called")
256
+ tool_args: Dict[str, Any] = Field(
257
+ default_factory=dict, description="Arguments passed to the tool"
258
+ )
259
+ tool_result: Any = Field(None, description="Result returned by the tool")
260
+ duration_ms: Optional[float] = Field(
261
+ None, description="Tool execution duration in milliseconds"
262
+ )
263
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
264
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
265
+
266
+ model_config = {"arbitrary_types_allowed": True}
267
+
268
+
269
+ class SystemAlertEvent(BaseModel):
270
+ """Event emitted for non-blocking system alerts."""
271
+
272
+ event_type: str = Field(default="system_alert", description="Event type")
273
+ level: str = Field(..., description="Alert level: info, warning, error, critical")
274
+ message: str = Field(..., description="Alert message")
275
+ source: Optional[str] = Field(default=None, description="Alert source identifier")
276
+ context: Optional[Dict[str, Any]] = Field(default=None, description="Structured context data")
277
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
278
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
279
+
280
+ model_config = {"arbitrary_types_allowed": True}
281
+
282
+
283
+ class CheckpointCreatedEvent(BaseModel):
284
+ """Event emitted when a checkpoint is created during execution."""
285
+
286
+ event_type: str = Field(default="checkpoint_created", description="Event type")
287
+ checkpoint_position: int = Field(..., description="Checkpoint position (0, 1, 2, ...)")
288
+ checkpoint_type: str = Field(
289
+ ..., description="Checkpoint type (agent_turn, model_predict, etc.)"
290
+ )
291
+ duration_ms: Optional[float] = Field(None, description="Operation duration in milliseconds")
292
+ source_location: Optional[SourceLocation] = Field(None, description="Source code location")
293
+ timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
294
+ procedure_id: Optional[str] = Field(None, description="Procedure identifier")
295
+
296
+ model_config = {"arbitrary_types_allowed": True}
297
+
298
+
299
+ class ChatMessage(BaseModel):
300
+ """A message in a chat session."""
301
+
302
+ role: str = Field(..., description="Message role: USER, ASSISTANT, SYSTEM, TOOL")
303
+ content: str = Field(..., description="Message content")
304
+ message_type: str = Field(default="MESSAGE", description="Type of message")
305
+ tool_name: Optional[str] = Field(
306
+ default=None, description="Tool name if this is a tool message"
307
+ )
308
+ tool_parameters: Optional[Dict[str, Any]] = Field(
309
+ default=None, description="Tool call parameters"
310
+ )
311
+ tool_response: Optional[Dict[str, Any]] = Field(default=None, description="Tool response data")
312
+ parent_message_id: Optional[str] = Field(
313
+ default=None, description="Parent message ID for threading"
314
+ )
315
+ human_interaction: Optional[str] = Field(
316
+ default=None, description="Human interaction type (PENDING_APPROVAL, RESPONSE, etc.)"
317
+ )
318
+ metadata: Optional[Dict[str, Any]] = Field(
319
+ default=None, description="Additional message metadata"
320
+ )
321
+
322
+ model_config = {"arbitrary_types_allowed": True}
323
+
324
+
325
+ class Breakpoint(BaseModel):
326
+ """A breakpoint set by the user for debugging."""
327
+
328
+ breakpoint_id: str = Field(..., description="Unique breakpoint identifier")
329
+ file: str = Field(..., description="File path where breakpoint is set")
330
+ line: int = Field(..., description="Line number (1-indexed)")
331
+ condition: Optional[str] = Field(None, description="Python expression to evaluate")
332
+ enabled: bool = Field(default=True, description="Whether breakpoint is active")
333
+ hit_count: int = Field(default=0, description="Number of times breakpoint has been hit")
334
+
335
+ model_config = {"arbitrary_types_allowed": True}
336
+
337
+
338
+ class ExecutionRun(BaseModel):
339
+ """A complete procedure execution with tracing data."""
340
+
341
+ run_id: str = Field(..., description="Unique run identifier")
342
+ procedure_name: str = Field(..., description="Name of the procedure")
343
+ file_path: str = Field(..., description="Path to the .tac file")
344
+ start_time: datetime = Field(..., description="When execution started")
345
+ end_time: Optional[datetime] = Field(None, description="When execution completed")
346
+ status: str = Field(..., description="RUNNING, PAUSED, COMPLETED, FAILED")
347
+ execution_log: list[CheckpointEntry] = Field(
348
+ default_factory=list, description="All checkpoints from this run"
349
+ )
350
+ final_state: Dict[str, Any] = Field(default_factory=dict, description="Final state dictionary")
351
+ breakpoints: list[Breakpoint] = Field(
352
+ default_factory=list, description="Breakpoints for this run"
353
+ )
354
+
355
+ model_config = {"arbitrary_types_allowed": True}
@@ -0,0 +1,33 @@
1
+ """
2
+ Result object returned by cost-incurring primitives (e.g., Agents).
3
+
4
+ Standardizes on `result.output` for the returned data (string or structured).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from tactus.protocols.cost import UsageStats, CostStats
14
+
15
+
16
+ class TactusResult(BaseModel):
17
+ """
18
+ Standard Result wrapper for Lua and Python consumption.
19
+
20
+ - `output`: The returned data (string or structured dict/list/etc.)
21
+ - `usage`: Token usage stats for the call that produced this result
22
+ - `cost_stats`: Cost stats for the call that produced this result
23
+ """
24
+
25
+ output: Any = Field(..., description="Result output (string or structured data)")
26
+ usage: UsageStats = Field(default_factory=UsageStats)
27
+ cost_stats: CostStats = Field(default_factory=CostStats)
28
+
29
+ model_config = {"arbitrary_types_allowed": True}
30
+
31
+ def cost(self) -> CostStats:
32
+ """Return cost statistics for this result."""
33
+ return self.cost_stats
@@ -0,0 +1,90 @@
1
+ """
2
+ Storage backend protocol for Tactus.
3
+
4
+ Defines the interface for persisting procedure state, execution log, and metadata.
5
+ Implementations can use any storage backend (memory, files, databases, etc.).
6
+ """
7
+
8
+ from typing import Protocol, Optional, Any
9
+ from tactus.protocols.models import ProcedureMetadata
10
+
11
+
12
+ class StorageBackend(Protocol):
13
+ """
14
+ Protocol for storage backends.
15
+
16
+ Implementations provide persistence for procedure state and execution log.
17
+ This allows Tactus to work with any storage system (memory, files, databases, etc.).
18
+
19
+ Position-based checkpointing: All checkpoints are stored in ProcedureMetadata.execution_log
20
+ as an ordered list. No named checkpoint methods needed.
21
+ """
22
+
23
+ def load_procedure_metadata(self, procedure_id: str) -> ProcedureMetadata:
24
+ """
25
+ Load complete procedure metadata from storage.
26
+
27
+ Args:
28
+ procedure_id: Unique procedure identifier
29
+
30
+ Returns:
31
+ ProcedureMetadata with execution_log, state, replay_index, and status
32
+
33
+ Raises:
34
+ StorageError: If loading fails
35
+ """
36
+ ...
37
+
38
+ def save_procedure_metadata(self, procedure_id: str, metadata: ProcedureMetadata) -> None:
39
+ """
40
+ Save complete procedure metadata to storage.
41
+
42
+ Args:
43
+ procedure_id: Unique procedure identifier
44
+ metadata: ProcedureMetadata to persist (includes execution_log)
45
+
46
+ Raises:
47
+ StorageError: If saving fails
48
+ """
49
+ ...
50
+
51
+ def update_procedure_status(
52
+ self, procedure_id: str, status: str, waiting_on_message_id: Optional[str] = None
53
+ ) -> None:
54
+ """
55
+ Update procedure status (and optionally waiting message ID).
56
+
57
+ Args:
58
+ procedure_id: Unique procedure identifier
59
+ status: New status (RUNNING, WAITING_FOR_HUMAN, COMPLETED, FAILED)
60
+ waiting_on_message_id: Optional message ID if waiting for human
61
+
62
+ Raises:
63
+ StorageError: If update fails
64
+ """
65
+ ...
66
+
67
+ def get_state(self, procedure_id: str) -> dict[str, Any]:
68
+ """
69
+ Get mutable state dictionary.
70
+
71
+ Args:
72
+ procedure_id: Unique procedure identifier
73
+
74
+ Returns:
75
+ State dictionary
76
+ """
77
+ ...
78
+
79
+ def set_state(self, procedure_id: str, state: dict[str, Any]) -> None:
80
+ """
81
+ Set mutable state dictionary.
82
+
83
+ Args:
84
+ procedure_id: Unique procedure identifier
85
+ state: State dictionary to save
86
+
87
+ Raises:
88
+ StorageError: If saving fails
89
+ """
90
+ ...
@@ -0,0 +1,13 @@
1
+ """
2
+ Provider abstraction layer for LLM providers.
3
+
4
+ This module provides abstractions for different LLM providers (OpenAI, Bedrock, Google, etc.)
5
+ to enable multi-provider support in Tactus.
6
+ """
7
+
8
+ from tactus.providers.base import ProviderConfig
9
+ from tactus.providers.openai import OpenAIProvider
10
+ from tactus.providers.bedrock import BedrockProvider
11
+ from tactus.providers.google import GoogleProvider
12
+
13
+ __all__ = ["ProviderConfig", "OpenAIProvider", "BedrockProvider", "GoogleProvider"]
@@ -0,0 +1,92 @@
1
+ """
2
+ Base provider configuration protocol.
3
+
4
+ Defines the interface that all LLM providers must implement.
5
+ """
6
+
7
+ from typing import Protocol, Optional, Dict, Any
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class ProviderConfig:
13
+ """
14
+ Configuration for an LLM provider.
15
+
16
+ Attributes:
17
+ provider_name: Name of the provider (e.g., 'openai', 'bedrock')
18
+ model_id: Model identifier within the provider
19
+ credentials: Optional credentials dict (API keys, etc.)
20
+ region: Optional region for cloud providers
21
+ additional_config: Any additional provider-specific configuration
22
+ """
23
+
24
+ provider_name: str
25
+ model_id: str
26
+ credentials: Optional[Dict[str, str]] = None
27
+ region: Optional[str] = None
28
+ additional_config: Optional[Dict[str, Any]] = None
29
+
30
+ def get_model_string(self) -> str:
31
+ """
32
+ Get the model string for pydantic-ai.
33
+
34
+ Returns:
35
+ Model string in format 'provider:model_id'
36
+ """
37
+ return f"{self.provider_name}:{self.model_id}"
38
+
39
+
40
+ class Provider(Protocol):
41
+ """
42
+ Protocol for LLM provider implementations.
43
+
44
+ Each provider must implement methods to:
45
+ - Validate configuration
46
+ - Get model string for pydantic-ai
47
+ - Check if credentials are available
48
+ """
49
+
50
+ @staticmethod
51
+ def validate_model(model_id: str) -> bool:
52
+ """
53
+ Validate that a model ID is valid for this provider.
54
+
55
+ Args:
56
+ model_id: The model identifier to validate
57
+
58
+ Returns:
59
+ True if valid, False otherwise
60
+ """
61
+ ...
62
+
63
+ @staticmethod
64
+ def get_required_credentials() -> list[str]:
65
+ """
66
+ Get list of required credential keys for this provider.
67
+
68
+ Returns:
69
+ List of environment variable names needed
70
+ """
71
+ ...
72
+
73
+ @staticmethod
74
+ def create_config(
75
+ model_id: str,
76
+ credentials: Optional[Dict[str, str]] = None,
77
+ region: Optional[str] = None,
78
+ **kwargs,
79
+ ) -> ProviderConfig:
80
+ """
81
+ Create a provider configuration.
82
+
83
+ Args:
84
+ model_id: Model identifier
85
+ credentials: Optional credentials dict
86
+ region: Optional region
87
+ **kwargs: Additional provider-specific config
88
+
89
+ Returns:
90
+ ProviderConfig instance
91
+ """
92
+ ...