tactus 0.34.0__py3-none-any.whl → 0.35.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 (81) hide show
  1. tactus/__init__.py +1 -1
  2. tactus/adapters/broker_log.py +17 -14
  3. tactus/adapters/channels/__init__.py +17 -15
  4. tactus/adapters/channels/base.py +16 -7
  5. tactus/adapters/channels/broker.py +43 -13
  6. tactus/adapters/channels/cli.py +19 -15
  7. tactus/adapters/channels/host.py +15 -6
  8. tactus/adapters/channels/ipc.py +82 -31
  9. tactus/adapters/channels/sse.py +41 -23
  10. tactus/adapters/cli_hitl.py +19 -19
  11. tactus/adapters/cli_log.py +4 -4
  12. tactus/adapters/control_loop.py +138 -99
  13. tactus/adapters/cost_collector_log.py +9 -9
  14. tactus/adapters/file_storage.py +56 -52
  15. tactus/adapters/http_callback_log.py +23 -13
  16. tactus/adapters/ide_log.py +17 -9
  17. tactus/adapters/lua_tools.py +4 -5
  18. tactus/adapters/mcp.py +16 -19
  19. tactus/adapters/mcp_manager.py +46 -30
  20. tactus/adapters/memory.py +9 -9
  21. tactus/adapters/plugins.py +42 -42
  22. tactus/broker/client.py +75 -78
  23. tactus/broker/protocol.py +57 -57
  24. tactus/broker/server.py +252 -197
  25. tactus/cli/app.py +3 -1
  26. tactus/cli/control.py +2 -2
  27. tactus/core/config_manager.py +181 -135
  28. tactus/core/dependencies/registry.py +66 -48
  29. tactus/core/dsl_stubs.py +222 -163
  30. tactus/core/exceptions.py +10 -1
  31. tactus/core/execution_context.py +152 -112
  32. tactus/core/lua_sandbox.py +72 -64
  33. tactus/core/message_history_manager.py +138 -43
  34. tactus/core/mocking.py +41 -27
  35. tactus/core/output_validator.py +49 -44
  36. tactus/core/registry.py +94 -80
  37. tactus/core/runtime.py +211 -176
  38. tactus/core/template_resolver.py +16 -16
  39. tactus/core/yaml_parser.py +55 -45
  40. tactus/docs/extractor.py +7 -6
  41. tactus/ide/server.py +119 -78
  42. tactus/primitives/control.py +10 -6
  43. tactus/primitives/file.py +48 -46
  44. tactus/primitives/handles.py +47 -35
  45. tactus/primitives/host.py +29 -27
  46. tactus/primitives/human.py +154 -137
  47. tactus/primitives/json.py +22 -23
  48. tactus/primitives/log.py +26 -26
  49. tactus/primitives/message_history.py +285 -31
  50. tactus/primitives/model.py +15 -9
  51. tactus/primitives/procedure.py +86 -64
  52. tactus/primitives/procedure_callable.py +58 -51
  53. tactus/primitives/retry.py +31 -29
  54. tactus/primitives/session.py +42 -29
  55. tactus/primitives/state.py +54 -43
  56. tactus/primitives/step.py +9 -13
  57. tactus/primitives/system.py +34 -21
  58. tactus/primitives/tool.py +44 -31
  59. tactus/primitives/tool_handle.py +76 -54
  60. tactus/primitives/toolset.py +25 -22
  61. tactus/sandbox/config.py +4 -4
  62. tactus/sandbox/container_runner.py +161 -107
  63. tactus/sandbox/docker_manager.py +20 -20
  64. tactus/sandbox/entrypoint.py +16 -14
  65. tactus/sandbox/protocol.py +15 -15
  66. tactus/stdlib/classify/llm.py +1 -3
  67. tactus/stdlib/core/validation.py +0 -3
  68. tactus/testing/pydantic_eval_runner.py +1 -1
  69. tactus/utils/asyncio_helpers.py +27 -0
  70. tactus/utils/cost_calculator.py +7 -7
  71. tactus/utils/model_pricing.py +11 -12
  72. tactus/utils/safe_file_library.py +156 -132
  73. tactus/utils/safe_libraries.py +27 -27
  74. tactus/validation/error_listener.py +18 -5
  75. tactus/validation/semantic_visitor.py +392 -333
  76. tactus/validation/validator.py +89 -49
  77. {tactus-0.34.0.dist-info → tactus-0.35.0.dist-info}/METADATA +12 -3
  78. {tactus-0.34.0.dist-info → tactus-0.35.0.dist-info}/RECORD +81 -80
  79. {tactus-0.34.0.dist-info → tactus-0.35.0.dist-info}/WHEEL +0 -0
  80. {tactus-0.34.0.dist-info → tactus-0.35.0.dist-info}/entry_points.txt +0 -0
  81. {tactus-0.34.0.dist-info → tactus-0.35.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,12 +6,12 @@ warn when used outside checkpoint boundaries to prevent replay bugs in
6
6
  Tactus's checkpoint-and-replay execution system.
7
7
  """
8
8
 
9
- import warnings
10
- import time
11
- import random
12
9
  import math
10
+ import random
11
+ import time
12
+ import warnings
13
13
  from datetime import datetime
14
- from typing import Callable, Any, Optional
14
+ from typing import Any, Callable, Optional
15
15
  from functools import wraps
16
16
 
17
17
 
@@ -27,7 +27,7 @@ class NonDeterministicError(Exception):
27
27
  pass
28
28
 
29
29
 
30
- def warn_if_unsafe(operation_name: str, get_context: Callable[[], Optional[Any]]):
30
+ def warn_if_unsafe(operation_label: str, get_context: Callable[[], Optional[Any]]):
31
31
  """
32
32
  Decorator that warns when non-deterministic operation used outside checkpoint.
33
33
 
@@ -43,31 +43,31 @@ def warn_if_unsafe(operation_name: str, get_context: Callable[[], Optional[Any]]
43
43
  @wraps(func)
44
44
  def wrapper(*args, **kwargs):
45
45
  # Get context via callback
46
- context = get_context()
46
+ execution_context = get_context()
47
47
 
48
48
  # If no context, can't enforce (allow silently for REPL/testing)
49
- if context is None:
49
+ if execution_context is None:
50
50
  return func(*args, **kwargs)
51
51
 
52
52
  # Check if inside checkpoint
53
- inside_checkpoint = getattr(context, "_inside_checkpoint", False)
53
+ inside_checkpoint = getattr(execution_context, "_inside_checkpoint", False)
54
54
 
55
55
  if not inside_checkpoint:
56
56
  message = (
57
57
  f"\n{'=' * 70}\n"
58
- f"DETERMINISM WARNING: {operation_name} called outside checkpoint\n"
58
+ f"DETERMINISM WARNING: {operation_label} called outside checkpoint\n"
59
59
  f"{'=' * 70}\n\n"
60
60
  f"Non-deterministic operations must be wrapped in checkpoints "
61
61
  f"for durability.\n\n"
62
62
  f"To fix, wrap your code in a checkpoint:\n\n"
63
63
  f" -- Lua example:\n"
64
64
  f" local random_value = Step.checkpoint(function()\n"
65
- f" return {operation_name}\n"
65
+ f" return {operation_label}\n"
66
66
  f" end)\n\n"
67
67
  f"Or use checkpoint() directly:\n\n"
68
68
  f" local result = checkpoint(function()\n"
69
69
  f" -- Your non-deterministic code here\n"
70
- f" return {operation_name}\n"
70
+ f" return {operation_label}\n"
71
71
  f" end)\n\n"
72
72
  f"Why: Tactus uses checkpointing for durable execution. "
73
73
  f"Operations outside\n"
@@ -77,7 +77,7 @@ def warn_if_unsafe(operation_name: str, get_context: Callable[[], Optional[Any]]
77
77
  )
78
78
 
79
79
  # Check strict mode
80
- strict_mode = getattr(context, "strict_determinism", False)
80
+ strict_mode = getattr(execution_context, "strict_determinism", False)
81
81
 
82
82
  if strict_mode:
83
83
  raise NonDeterministicError(message)
@@ -105,7 +105,7 @@ def create_safe_math_library(get_context: Callable, strict_mode: bool = False):
105
105
  """
106
106
 
107
107
  @warn_if_unsafe("math.random()", get_context)
108
- def safe_random(m=None, n=None):
108
+ def safe_random(minimum_inclusive=None, maximum_inclusive=None):
109
109
  """
110
110
  Safe math.random() with checkpoint warning.
111
111
 
@@ -114,20 +114,20 @@ def create_safe_math_library(get_context: Callable, strict_mode: bool = False):
114
114
  - math.random(n): returns integer in [1, n]
115
115
  - math.random(m, n): returns integer in [m, n]
116
116
  """
117
- if m is None and n is None:
117
+ if minimum_inclusive is None and maximum_inclusive is None:
118
118
  # No arguments: return float [0, 1)
119
119
  return random.random()
120
- elif n is None:
120
+ elif maximum_inclusive is None:
121
121
  # One argument: return integer [1, m]
122
- return random.randint(1, int(m))
122
+ return random.randint(1, int(minimum_inclusive))
123
123
  else:
124
124
  # Two arguments: return integer [m, n]
125
- return random.randint(int(m), int(n))
125
+ return random.randint(int(minimum_inclusive), int(maximum_inclusive))
126
126
 
127
127
  @warn_if_unsafe("math.randomseed()", get_context)
128
- def safe_randomseed(seed):
128
+ def safe_randomseed(seed_value):
129
129
  """Safe math.randomseed() with checkpoint warning."""
130
- random.seed(int(seed))
130
+ random.seed(int(seed_value))
131
131
  return None
132
132
 
133
133
  # Standard math functions (deterministic - pass through)
@@ -178,9 +178,9 @@ def create_safe_os_library(get_context: Callable, strict_mode: bool = False):
178
178
  """
179
179
 
180
180
  @warn_if_unsafe("os.time()", get_context)
181
- def safe_time(date_table=None):
181
+ def safe_time(lua_date_table=None):
182
182
  """Safe os.time() with checkpoint warning."""
183
- if date_table is None:
183
+ if lua_date_table is None:
184
184
  return int(time.time())
185
185
  else:
186
186
  # Lua date table format: {year, month, day, hour, min, sec}
@@ -189,20 +189,20 @@ def create_safe_os_library(get_context: Callable, strict_mode: bool = False):
189
189
  return int(time.time())
190
190
 
191
191
  @warn_if_unsafe("os.date()", get_context)
192
- def safe_date(format_str=None):
192
+ def safe_date(format_string=None):
193
193
  """Safe os.date() with checkpoint warning."""
194
194
  now = datetime.utcnow()
195
195
 
196
- if format_str is None:
196
+ if format_string is None:
197
197
  # Default format like Lua's os.date()
198
198
  return now.strftime("%a %b %d %H:%M:%S %Y")
199
- elif format_str == "%Y-%m-%dT%H:%M:%SZ":
199
+ elif format_string == "%Y-%m-%dT%H:%M:%SZ":
200
200
  # ISO 8601 format
201
201
  return now.strftime("%Y-%m-%dT%H:%M:%SZ")
202
202
  else:
203
203
  # Support Python strftime formats
204
204
  try:
205
- return now.strftime(format_str)
205
+ return now.strftime(format_string)
206
206
  except Exception:
207
207
  return now.strftime("%a %b %d %H:%M:%S %Y")
208
208
 
@@ -212,11 +212,11 @@ def create_safe_os_library(get_context: Callable, strict_mode: bool = False):
212
212
  return time.process_time()
213
213
 
214
214
  @warn_if_unsafe("os.getenv()", get_context)
215
- def safe_getenv(varname):
215
+ def safe_getenv(variable_name):
216
216
  """Safe os.getenv() with checkpoint warning - environment variables can change."""
217
217
  import os
218
218
 
219
- return os.getenv(varname)
219
+ return os.getenv(variable_name)
220
220
 
221
221
  @warn_if_unsafe("os.tmpname()", get_context)
222
222
  def safe_tmpname():
@@ -9,13 +9,26 @@ from tactus.core.registry import ValidationMessage
9
9
  class TactusErrorListener(ErrorListener):
10
10
  """Collects syntax errors from ANTLR parser."""
11
11
 
12
- def __init__(self):
13
- self.errors = []
12
+ def __init__(self) -> None:
13
+ self.syntax_errors: list[ValidationMessage] = []
14
+ # Backward-compatible alias used by formatter/tests.
15
+ self.errors = self.syntax_errors
14
16
 
15
- def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
17
+ def syntaxError(
18
+ self,
19
+ parser,
20
+ offending_token,
21
+ line_number: int,
22
+ column_number: int,
23
+ antlr_error_message: str,
24
+ antlr_exception,
25
+ ) -> None:
16
26
  """Called when parser encounters a syntax error."""
17
- self.errors.append(
27
+ del parser, offending_token, antlr_exception
28
+ self.syntax_errors.append(
18
29
  ValidationMessage(
19
- level="error", message=f"Syntax error: {msg}", location=(line, column)
30
+ level="error",
31
+ message=f"Syntax error: {antlr_error_message}",
32
+ location=(line_number, column_number),
20
33
  )
21
34
  )