AbstractRuntime 0.4.0__py3-none-any.whl → 0.4.1__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 (65) hide show
  1. abstractruntime/__init__.py +76 -1
  2. abstractruntime/core/config.py +68 -1
  3. abstractruntime/core/models.py +5 -0
  4. abstractruntime/core/policy.py +74 -3
  5. abstractruntime/core/runtime.py +1002 -126
  6. abstractruntime/core/vars.py +8 -2
  7. abstractruntime/evidence/recorder.py +1 -1
  8. abstractruntime/history_bundle.py +772 -0
  9. abstractruntime/integrations/abstractcore/__init__.py +3 -0
  10. abstractruntime/integrations/abstractcore/default_tools.py +127 -3
  11. abstractruntime/integrations/abstractcore/effect_handlers.py +2440 -99
  12. abstractruntime/integrations/abstractcore/embeddings_client.py +69 -0
  13. abstractruntime/integrations/abstractcore/factory.py +68 -20
  14. abstractruntime/integrations/abstractcore/llm_client.py +447 -15
  15. abstractruntime/integrations/abstractcore/mcp_worker.py +1 -0
  16. abstractruntime/integrations/abstractcore/session_attachments.py +946 -0
  17. abstractruntime/integrations/abstractcore/tool_executor.py +31 -10
  18. abstractruntime/integrations/abstractcore/workspace_scoped_tools.py +561 -0
  19. abstractruntime/integrations/abstractmemory/__init__.py +3 -0
  20. abstractruntime/integrations/abstractmemory/effect_handlers.py +946 -0
  21. abstractruntime/memory/active_context.py +6 -1
  22. abstractruntime/memory/kg_packets.py +164 -0
  23. abstractruntime/memory/memact_composer.py +175 -0
  24. abstractruntime/memory/recall_levels.py +163 -0
  25. abstractruntime/memory/token_budget.py +86 -0
  26. abstractruntime/storage/__init__.py +4 -1
  27. abstractruntime/storage/artifacts.py +158 -30
  28. abstractruntime/storage/base.py +17 -1
  29. abstractruntime/storage/commands.py +339 -0
  30. abstractruntime/storage/in_memory.py +41 -1
  31. abstractruntime/storage/json_files.py +195 -12
  32. abstractruntime/storage/observable.py +38 -1
  33. abstractruntime/storage/offloading.py +433 -0
  34. abstractruntime/storage/sqlite.py +836 -0
  35. abstractruntime/visualflow_compiler/__init__.py +29 -0
  36. abstractruntime/visualflow_compiler/adapters/__init__.py +11 -0
  37. abstractruntime/visualflow_compiler/adapters/agent_adapter.py +126 -0
  38. abstractruntime/visualflow_compiler/adapters/context_adapter.py +109 -0
  39. abstractruntime/visualflow_compiler/adapters/control_adapter.py +615 -0
  40. abstractruntime/visualflow_compiler/adapters/effect_adapter.py +1051 -0
  41. abstractruntime/visualflow_compiler/adapters/event_adapter.py +307 -0
  42. abstractruntime/visualflow_compiler/adapters/function_adapter.py +97 -0
  43. abstractruntime/visualflow_compiler/adapters/memact_adapter.py +114 -0
  44. abstractruntime/visualflow_compiler/adapters/subflow_adapter.py +74 -0
  45. abstractruntime/visualflow_compiler/adapters/variable_adapter.py +316 -0
  46. abstractruntime/visualflow_compiler/compiler.py +3832 -0
  47. abstractruntime/visualflow_compiler/flow.py +247 -0
  48. abstractruntime/visualflow_compiler/visual/__init__.py +13 -0
  49. abstractruntime/visualflow_compiler/visual/agent_ids.py +29 -0
  50. abstractruntime/visualflow_compiler/visual/builtins.py +1376 -0
  51. abstractruntime/visualflow_compiler/visual/code_executor.py +214 -0
  52. abstractruntime/visualflow_compiler/visual/executor.py +2804 -0
  53. abstractruntime/visualflow_compiler/visual/models.py +211 -0
  54. abstractruntime/workflow_bundle/__init__.py +52 -0
  55. abstractruntime/workflow_bundle/models.py +236 -0
  56. abstractruntime/workflow_bundle/packer.py +317 -0
  57. abstractruntime/workflow_bundle/reader.py +87 -0
  58. abstractruntime/workflow_bundle/registry.py +587 -0
  59. abstractruntime-0.4.1.dist-info/METADATA +177 -0
  60. abstractruntime-0.4.1.dist-info/RECORD +86 -0
  61. abstractruntime-0.4.0.dist-info/METADATA +0 -167
  62. abstractruntime-0.4.0.dist-info/RECORD +0 -49
  63. {abstractruntime-0.4.0.dist-info → abstractruntime-0.4.1.dist-info}/WHEEL +0 -0
  64. {abstractruntime-0.4.0.dist-info → abstractruntime-0.4.1.dist-info}/entry_points.txt +0 -0
  65. {abstractruntime-0.4.0.dist-info → abstractruntime-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,316 @@
1
+ """Variable node adapters (Blueprint-style Get/Set Variable).
2
+
3
+ Design goals:
4
+ - Variables are stored durably in `run.vars` (so pause/resume works).
5
+ - `Set Variable` must not clobber the visual pipeline `_last_output` (pass-through),
6
+ otherwise inserting it into a chain would destroy downstream inputs.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from typing import Any, Callable, Dict, Optional
13
+
14
+
15
+ def _set_by_path(target: Dict[str, Any], dotted_key: str, value: Any) -> None:
16
+ """Set a dotted path on a dict, creating intermediate dicts as needed."""
17
+ parts = [p for p in dotted_key.split(".") if p]
18
+ if not parts:
19
+ raise ValueError("Variable name must be non-empty")
20
+ cur: Dict[str, Any] = target
21
+ for part in parts[:-1]:
22
+ nxt = cur.get(part)
23
+ if not isinstance(nxt, dict):
24
+ nxt = {}
25
+ cur[part] = nxt
26
+ cur = nxt
27
+ cur[parts[-1]] = value
28
+
29
+
30
+ def _get_by_path(source: Dict[str, Any], dotted_key: str) -> Any:
31
+ """Best-effort dotted-path lookup supporting dicts (and nested dicts).
32
+
33
+ This is intentionally conservative: workflow variables (`run.vars`) are dict-like state.
34
+ """
35
+ parts = [p for p in str(dotted_key or "").split(".") if p]
36
+ if not parts:
37
+ return None
38
+ current: Any = source
39
+ for part in parts:
40
+ if not isinstance(current, dict):
41
+ return None
42
+ current = current.get(part)
43
+ return current
44
+
45
+
46
+ def _set_on_object(obj: Dict[str, Any], dotted_key: str, value: Any) -> Dict[str, Any]:
47
+ """Set a nested key on an object dict (mutates the given dict) and return it."""
48
+ parts = [p for p in str(dotted_key or "").split(".") if p]
49
+ if not parts:
50
+ return obj
51
+ cur: Dict[str, Any] = obj
52
+ for part in parts[:-1]:
53
+ nxt = cur.get(part)
54
+ if not isinstance(nxt, dict):
55
+ nxt = {}
56
+ cur[part] = nxt
57
+ cur = nxt
58
+ cur[parts[-1]] = value
59
+ return obj
60
+
61
+
62
+ def _persist_node_output(run_vars: Dict[str, Any], node_id: str, value: Dict[str, Any]) -> None:
63
+ temp = run_vars.get("_temp")
64
+ if not isinstance(temp, dict):
65
+ temp = {}
66
+ run_vars["_temp"] = temp
67
+ persisted = temp.get("node_outputs")
68
+ if not isinstance(persisted, dict):
69
+ persisted = {}
70
+ temp["node_outputs"] = persisted
71
+ persisted[node_id] = value
72
+
73
+
74
+ def create_set_var_node_handler(
75
+ *,
76
+ node_id: str,
77
+ next_node: Optional[str],
78
+ data_aware_handler: Optional[Callable[[Any], Any]],
79
+ flow: Any,
80
+ ) -> Callable:
81
+ """Create a handler for `set_var` visual nodes."""
82
+ from abstractruntime.core.models import StepPlan
83
+ from ..compiler import _sync_effect_results_to_node_outputs
84
+
85
+ def handler(run: Any, ctx: Any) -> "StepPlan":
86
+ del ctx
87
+ if flow is not None and hasattr(flow, "_node_outputs") and hasattr(flow, "_data_edge_map"):
88
+ _sync_effect_results_to_node_outputs(run, flow)
89
+
90
+ last_output = run.vars.get("_last_output", {})
91
+ resolved = data_aware_handler(last_output) if callable(data_aware_handler) else {}
92
+ payload = resolved if isinstance(resolved, dict) else {}
93
+
94
+ raw_name = payload.get("name")
95
+ name = (raw_name if isinstance(raw_name, str) else str(raw_name or "")).strip()
96
+ if not name:
97
+ run.vars["_flow_error"] = "Set Variable requires a non-empty variable name."
98
+ run.vars["_flow_error_node"] = node_id
99
+ return StepPlan(
100
+ node_id=node_id,
101
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
102
+ )
103
+ if name.startswith("_"):
104
+ run.vars["_flow_error"] = f"Invalid variable name '{name}': names starting with '_' are reserved."
105
+ run.vars["_flow_error_node"] = node_id
106
+ return StepPlan(
107
+ node_id=node_id,
108
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
109
+ )
110
+
111
+ value = payload.get("value")
112
+
113
+ try:
114
+ if not isinstance(run.vars, dict):
115
+ raise ValueError("run.vars is not a dict")
116
+ _set_by_path(run.vars, name, value)
117
+ except Exception as e:
118
+ run.vars["_flow_error"] = f"Failed to set variable '{name}': {e}"
119
+ run.vars["_flow_error_node"] = node_id
120
+ return StepPlan(
121
+ node_id=node_id,
122
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
123
+ )
124
+
125
+ # Persist this node's outputs for pause/resume (data edges may depend on them).
126
+ _persist_node_output(run.vars, node_id, {"value": value})
127
+
128
+ # IMPORTANT: pass-through semantics (do NOT clobber the pipeline output).
129
+ # `_last_output` stays as-is.
130
+
131
+ if next_node:
132
+ return StepPlan(node_id=node_id, next_node=next_node)
133
+ return StepPlan(node_id=node_id, complete_output={"success": True, "result": run.vars.get("_last_output")})
134
+
135
+ return handler
136
+
137
+
138
+ def create_set_var_property_node_handler(
139
+ *,
140
+ node_id: str,
141
+ next_node: Optional[str],
142
+ data_aware_handler: Optional[Callable[[Any], Any]],
143
+ flow: Any,
144
+ ) -> Callable:
145
+ """Create a handler for `set_var_property` visual nodes.
146
+
147
+ Contract:
148
+ - Inputs:
149
+ - `name`: base variable path (e.g. "state" or "state.player")
150
+ - `key`: nested key path inside that variable's object (e.g. "hp" or "stats.hp")
151
+ - `value`: value to set at `key`
152
+ - Behavior:
153
+ - reads current object at `name` (defaults to `{}` if missing/not an object)
154
+ - applies the update to a copy
155
+ - writes the updated object back into `run.vars[name]` (durable)
156
+ - persists node outputs for pause/resume
157
+ - does NOT clobber `_last_output` (pass-through)
158
+ """
159
+ from abstractruntime.core.models import StepPlan
160
+ from ..compiler import _sync_effect_results_to_node_outputs
161
+
162
+ def handler(run: Any, ctx: Any) -> "StepPlan":
163
+ del ctx
164
+ if flow is not None and hasattr(flow, "_node_outputs") and hasattr(flow, "_data_edge_map"):
165
+ _sync_effect_results_to_node_outputs(run, flow)
166
+
167
+ last_output = run.vars.get("_last_output", {})
168
+ resolved = data_aware_handler(last_output) if callable(data_aware_handler) else {}
169
+ payload = resolved if isinstance(resolved, dict) else {}
170
+
171
+ raw_name = payload.get("name")
172
+ name = (raw_name if isinstance(raw_name, str) else str(raw_name or "")).strip()
173
+ if not name:
174
+ run.vars["_flow_error"] = "Set Variable Property requires a non-empty variable name."
175
+ run.vars["_flow_error_node"] = node_id
176
+ return StepPlan(
177
+ node_id=node_id,
178
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
179
+ )
180
+ if name.startswith("_"):
181
+ run.vars["_flow_error"] = f"Invalid variable name '{name}': names starting with '_' are reserved."
182
+ run.vars["_flow_error_node"] = node_id
183
+ return StepPlan(
184
+ node_id=node_id,
185
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
186
+ )
187
+
188
+ raw_key = payload.get("key")
189
+ key = (raw_key if isinstance(raw_key, str) else str(raw_key or "")).strip()
190
+ if not key:
191
+ run.vars["_flow_error"] = "Set Variable Property requires a non-empty key."
192
+ run.vars["_flow_error_node"] = node_id
193
+ return StepPlan(
194
+ node_id=node_id,
195
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
196
+ )
197
+
198
+ value = payload.get("value")
199
+
200
+ try:
201
+ if not isinstance(run.vars, dict):
202
+ raise ValueError("run.vars is not a dict")
203
+
204
+ current = _get_by_path(run.vars, name)
205
+ base_obj: Dict[str, Any] = dict(current) if isinstance(current, dict) else {}
206
+ _set_on_object(base_obj, key, value)
207
+ _set_by_path(run.vars, name, base_obj)
208
+ except Exception as e:
209
+ run.vars["_flow_error"] = f"Failed to set variable property '{name}.{key}': {e}"
210
+ run.vars["_flow_error_node"] = node_id
211
+ return StepPlan(
212
+ node_id=node_id,
213
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
214
+ )
215
+
216
+ # Persist this node's outputs for pause/resume (data edges may depend on them).
217
+ _persist_node_output(run.vars, node_id, {"value": base_obj})
218
+
219
+ # IMPORTANT: pass-through semantics (do NOT clobber the pipeline output).
220
+ # `_last_output` stays as-is.
221
+ if next_node:
222
+ return StepPlan(node_id=node_id, next_node=next_node)
223
+ return StepPlan(node_id=node_id, complete_output={"success": True, "value": base_obj, "result": run.vars.get("_last_output")})
224
+
225
+ return handler
226
+
227
+
228
+ def create_set_vars_node_handler(
229
+ *,
230
+ node_id: str,
231
+ next_node: Optional[str],
232
+ data_aware_handler: Optional[Callable[[Any], Any]],
233
+ flow: Any,
234
+ ) -> Callable:
235
+ """Create a handler for `set_vars` visual nodes.
236
+
237
+ Contract:
238
+ - Input pin: `updates` (object or JSON string), where keys are dotted paths and values are JSON-safe values.
239
+ - Output pin: `updates` (echoed), for observability/debugging.
240
+ - Pass-through: must NOT clobber `_last_output` (same as `set_var`).
241
+ """
242
+ from abstractruntime.core.models import StepPlan
243
+ from ..compiler import _sync_effect_results_to_node_outputs
244
+
245
+ def _coerce_updates(raw: Any) -> Dict[str, Any]:
246
+ if isinstance(raw, dict):
247
+ return dict(raw)
248
+ if isinstance(raw, str) and raw.strip():
249
+ try:
250
+ parsed = json.loads(raw)
251
+ except Exception:
252
+ return {}
253
+ return dict(parsed) if isinstance(parsed, dict) else {}
254
+ return {}
255
+
256
+ def handler(run: Any, ctx: Any) -> "StepPlan":
257
+ del ctx
258
+ if flow is not None and hasattr(flow, "_node_outputs") and hasattr(flow, "_data_edge_map"):
259
+ _sync_effect_results_to_node_outputs(run, flow)
260
+
261
+ last_output = run.vars.get("_last_output", {})
262
+ resolved = data_aware_handler(last_output) if callable(data_aware_handler) else {}
263
+ payload = resolved if isinstance(resolved, dict) else {}
264
+
265
+ updates = _coerce_updates(payload.get("updates"))
266
+ if not updates:
267
+ # Deterministic no-op (still counts as a step, but doesn't pollute `_flow_error`).
268
+ _persist_node_output(run.vars, node_id, {"updates": {}})
269
+ if next_node:
270
+ return StepPlan(node_id=node_id, next_node=next_node)
271
+ return StepPlan(node_id=node_id, complete_output={"success": True, "updates": {}, "result": run.vars.get("_last_output")})
272
+
273
+ # Validate all keys first so we don't partially apply.
274
+ normalized: Dict[str, Any] = {}
275
+ for k, v in updates.items():
276
+ name = (k if isinstance(k, str) else str(k or "")).strip()
277
+ if not name:
278
+ run.vars["_flow_error"] = "Set Variables requires non-empty variable names in updates."
279
+ run.vars["_flow_error_node"] = node_id
280
+ return StepPlan(
281
+ node_id=node_id,
282
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
283
+ )
284
+ if name.startswith("_"):
285
+ run.vars["_flow_error"] = f"Invalid variable name '{name}': names starting with '_' are reserved."
286
+ run.vars["_flow_error_node"] = node_id
287
+ return StepPlan(
288
+ node_id=node_id,
289
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
290
+ )
291
+ normalized[name] = v
292
+
293
+ try:
294
+ if not isinstance(run.vars, dict):
295
+ raise ValueError("run.vars is not a dict")
296
+ for name, value in normalized.items():
297
+ _set_by_path(run.vars, name, value)
298
+ except Exception as e:
299
+ run.vars["_flow_error"] = f"Failed to set variables: {e}"
300
+ run.vars["_flow_error_node"] = node_id
301
+ return StepPlan(
302
+ node_id=node_id,
303
+ complete_output={"success": False, "error": run.vars["_flow_error"], "node": node_id},
304
+ )
305
+
306
+ # Persist this node's outputs for pause/resume (data edges may depend on them).
307
+ _persist_node_output(run.vars, node_id, {"updates": normalized})
308
+
309
+ # IMPORTANT: pass-through semantics (do NOT clobber the pipeline output).
310
+ # `_last_output` stays as-is.
311
+ if next_node:
312
+ return StepPlan(node_id=node_id, next_node=next_node)
313
+ return StepPlan(node_id=node_id, complete_output={"success": True, "updates": normalized, "result": run.vars.get("_last_output")})
314
+
315
+ return handler
316
+