AbstractRuntime 0.2.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.
- abstractruntime/__init__.py +83 -3
- abstractruntime/core/config.py +82 -2
- abstractruntime/core/event_keys.py +62 -0
- abstractruntime/core/models.py +17 -1
- abstractruntime/core/policy.py +74 -3
- abstractruntime/core/runtime.py +3334 -28
- abstractruntime/core/vars.py +103 -2
- abstractruntime/evidence/__init__.py +10 -0
- abstractruntime/evidence/recorder.py +325 -0
- abstractruntime/history_bundle.py +772 -0
- abstractruntime/integrations/abstractcore/__init__.py +6 -0
- abstractruntime/integrations/abstractcore/constants.py +19 -0
- abstractruntime/integrations/abstractcore/default_tools.py +258 -0
- abstractruntime/integrations/abstractcore/effect_handlers.py +2622 -32
- abstractruntime/integrations/abstractcore/embeddings_client.py +69 -0
- abstractruntime/integrations/abstractcore/factory.py +149 -16
- abstractruntime/integrations/abstractcore/llm_client.py +891 -55
- abstractruntime/integrations/abstractcore/mcp_worker.py +587 -0
- abstractruntime/integrations/abstractcore/observability.py +80 -0
- abstractruntime/integrations/abstractcore/session_attachments.py +946 -0
- abstractruntime/integrations/abstractcore/summarizer.py +154 -0
- abstractruntime/integrations/abstractcore/tool_executor.py +509 -31
- abstractruntime/integrations/abstractcore/workspace_scoped_tools.py +561 -0
- abstractruntime/integrations/abstractmemory/__init__.py +3 -0
- abstractruntime/integrations/abstractmemory/effect_handlers.py +946 -0
- abstractruntime/memory/__init__.py +21 -0
- abstractruntime/memory/active_context.py +751 -0
- abstractruntime/memory/active_memory.py +452 -0
- abstractruntime/memory/compaction.py +105 -0
- abstractruntime/memory/kg_packets.py +164 -0
- abstractruntime/memory/memact_composer.py +175 -0
- abstractruntime/memory/recall_levels.py +163 -0
- abstractruntime/memory/token_budget.py +86 -0
- abstractruntime/rendering/__init__.py +17 -0
- abstractruntime/rendering/agent_trace_report.py +256 -0
- abstractruntime/rendering/json_stringify.py +136 -0
- abstractruntime/scheduler/scheduler.py +93 -2
- abstractruntime/storage/__init__.py +7 -2
- abstractruntime/storage/artifacts.py +175 -32
- abstractruntime/storage/base.py +17 -1
- abstractruntime/storage/commands.py +339 -0
- abstractruntime/storage/in_memory.py +41 -1
- abstractruntime/storage/json_files.py +210 -14
- abstractruntime/storage/observable.py +136 -0
- abstractruntime/storage/offloading.py +433 -0
- abstractruntime/storage/sqlite.py +836 -0
- abstractruntime/visualflow_compiler/__init__.py +29 -0
- abstractruntime/visualflow_compiler/adapters/__init__.py +11 -0
- abstractruntime/visualflow_compiler/adapters/agent_adapter.py +126 -0
- abstractruntime/visualflow_compiler/adapters/context_adapter.py +109 -0
- abstractruntime/visualflow_compiler/adapters/control_adapter.py +615 -0
- abstractruntime/visualflow_compiler/adapters/effect_adapter.py +1051 -0
- abstractruntime/visualflow_compiler/adapters/event_adapter.py +307 -0
- abstractruntime/visualflow_compiler/adapters/function_adapter.py +97 -0
- abstractruntime/visualflow_compiler/adapters/memact_adapter.py +114 -0
- abstractruntime/visualflow_compiler/adapters/subflow_adapter.py +74 -0
- abstractruntime/visualflow_compiler/adapters/variable_adapter.py +316 -0
- abstractruntime/visualflow_compiler/compiler.py +3832 -0
- abstractruntime/visualflow_compiler/flow.py +247 -0
- abstractruntime/visualflow_compiler/visual/__init__.py +13 -0
- abstractruntime/visualflow_compiler/visual/agent_ids.py +29 -0
- abstractruntime/visualflow_compiler/visual/builtins.py +1376 -0
- abstractruntime/visualflow_compiler/visual/code_executor.py +214 -0
- abstractruntime/visualflow_compiler/visual/executor.py +2804 -0
- abstractruntime/visualflow_compiler/visual/models.py +211 -0
- abstractruntime/workflow_bundle/__init__.py +52 -0
- abstractruntime/workflow_bundle/models.py +236 -0
- abstractruntime/workflow_bundle/packer.py +317 -0
- abstractruntime/workflow_bundle/reader.py +87 -0
- abstractruntime/workflow_bundle/registry.py +587 -0
- abstractruntime-0.4.1.dist-info/METADATA +177 -0
- abstractruntime-0.4.1.dist-info/RECORD +86 -0
- abstractruntime-0.4.1.dist-info/entry_points.txt +2 -0
- abstractruntime-0.2.0.dist-info/METADATA +0 -163
- abstractruntime-0.2.0.dist-info/RECORD +0 -32
- {abstractruntime-0.2.0.dist-info → abstractruntime-0.4.1.dist-info}/WHEEL +0 -0
- {abstractruntime-0.2.0.dist-info → abstractruntime-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Flow definition classes for AbstractFlow.
|
|
2
|
+
|
|
3
|
+
This module provides the core data structures for defining flows:
|
|
4
|
+
- Flow: A directed graph of nodes connected by edges
|
|
5
|
+
- FlowNode: A node in the flow (agent, function, or nested flow)
|
|
6
|
+
- FlowEdge: An edge connecting two nodes
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
# Avoid circular import - only used for type hints
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class FlowNode:
|
|
21
|
+
"""A node in a flow graph.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
id: Unique identifier for the node within the flow
|
|
25
|
+
handler: The handler for this node - can be an agent, function, or nested flow
|
|
26
|
+
input_key: Key in run.vars to read input from (optional)
|
|
27
|
+
output_key: Key in run.vars to write output to (optional)
|
|
28
|
+
effect_type: Effect type for effect nodes (ask_user, wait_until, etc.)
|
|
29
|
+
effect_config: Additional configuration for effect nodes
|
|
30
|
+
"""
|
|
31
|
+
id: str
|
|
32
|
+
handler: Any # Union[BaseAgent, Callable, Flow] - Any to avoid circular imports
|
|
33
|
+
input_key: Optional[str] = None
|
|
34
|
+
output_key: Optional[str] = None
|
|
35
|
+
effect_type: Optional[str] = None # e.g., "ask_user", "wait_until", etc.
|
|
36
|
+
effect_config: Optional[Dict[str, Any]] = None # Effect-specific configuration
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class FlowEdge:
|
|
41
|
+
"""An edge connecting two nodes in a flow.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
source: ID of the source node
|
|
45
|
+
target: ID of the target node
|
|
46
|
+
condition: Optional condition function for conditional routing (future)
|
|
47
|
+
source_handle: Optional execution output handle id (visual flows).
|
|
48
|
+
"""
|
|
49
|
+
source: str
|
|
50
|
+
target: str
|
|
51
|
+
condition: Optional[Callable[[Dict[str, Any]], bool]] = None
|
|
52
|
+
source_handle: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Flow:
|
|
56
|
+
"""Declarative flow definition that compiles to WorkflowSpec.
|
|
57
|
+
|
|
58
|
+
A Flow represents a directed graph of nodes (agents, functions, or nested flows)
|
|
59
|
+
connected by edges. Flows can be compiled to AbstractRuntime WorkflowSpec for
|
|
60
|
+
durable execution.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> flow = Flow("my_flow")
|
|
64
|
+
>>> flow.add_node("start", my_function, output_key="data")
|
|
65
|
+
>>> flow.add_node("process", process_function, input_key="data")
|
|
66
|
+
>>> flow.add_edge("start", "process")
|
|
67
|
+
>>> flow.set_entry("start")
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, flow_id: str):
|
|
71
|
+
"""Initialize a new flow.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
flow_id: Unique identifier for this flow
|
|
75
|
+
"""
|
|
76
|
+
self.flow_id = flow_id
|
|
77
|
+
self.nodes: Dict[str, FlowNode] = {}
|
|
78
|
+
self.edges: List[FlowEdge] = []
|
|
79
|
+
self.entry_node: Optional[str] = None
|
|
80
|
+
self.exit_node: Optional[str] = None
|
|
81
|
+
|
|
82
|
+
def add_node(
|
|
83
|
+
self,
|
|
84
|
+
node_id: str,
|
|
85
|
+
handler: Any,
|
|
86
|
+
*,
|
|
87
|
+
input_key: Optional[str] = None,
|
|
88
|
+
output_key: Optional[str] = None,
|
|
89
|
+
effect_type: Optional[str] = None,
|
|
90
|
+
effect_config: Optional[Dict[str, Any]] = None,
|
|
91
|
+
) -> "Flow":
|
|
92
|
+
"""Add a node to the flow.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
node_id: Unique identifier for this node
|
|
96
|
+
handler: The handler - an agent, callable function, or nested Flow
|
|
97
|
+
input_key: Key in run.vars to read input from
|
|
98
|
+
output_key: Key in run.vars to write output to
|
|
99
|
+
effect_type: Effect type for effect nodes (ask_user, wait_until, etc.)
|
|
100
|
+
effect_config: Additional configuration for effect nodes
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Self for method chaining
|
|
104
|
+
"""
|
|
105
|
+
if node_id in self.nodes:
|
|
106
|
+
raise ValueError(f"Node '{node_id}' already exists in flow '{self.flow_id}'")
|
|
107
|
+
|
|
108
|
+
self.nodes[node_id] = FlowNode(
|
|
109
|
+
id=node_id,
|
|
110
|
+
handler=handler,
|
|
111
|
+
input_key=input_key,
|
|
112
|
+
output_key=output_key,
|
|
113
|
+
effect_type=effect_type,
|
|
114
|
+
effect_config=effect_config,
|
|
115
|
+
)
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def add_edge(
|
|
119
|
+
self,
|
|
120
|
+
source: str,
|
|
121
|
+
target: str,
|
|
122
|
+
*,
|
|
123
|
+
condition: Optional[Callable[[Dict[str, Any]], bool]] = None,
|
|
124
|
+
source_handle: Optional[str] = None,
|
|
125
|
+
) -> "Flow":
|
|
126
|
+
"""Add an edge between nodes.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
source: ID of the source node
|
|
130
|
+
target: ID of the target node
|
|
131
|
+
condition: Optional condition function for conditional routing
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Self for method chaining
|
|
135
|
+
"""
|
|
136
|
+
self.edges.append(
|
|
137
|
+
FlowEdge(
|
|
138
|
+
source=source,
|
|
139
|
+
target=target,
|
|
140
|
+
condition=condition,
|
|
141
|
+
source_handle=source_handle,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def set_entry(self, node_id: str) -> "Flow":
|
|
147
|
+
"""Set the entry node for the flow.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
node_id: ID of the entry node
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Self for method chaining
|
|
154
|
+
"""
|
|
155
|
+
if node_id not in self.nodes:
|
|
156
|
+
raise ValueError(f"Entry node '{node_id}' not found in flow '{self.flow_id}'")
|
|
157
|
+
self.entry_node = node_id
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
def set_exit(self, node_id: str) -> "Flow":
|
|
161
|
+
"""Set the exit node for the flow (optional, can be inferred).
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
node_id: ID of the exit node
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Self for method chaining
|
|
168
|
+
"""
|
|
169
|
+
if node_id not in self.nodes:
|
|
170
|
+
raise ValueError(f"Exit node '{node_id}' not found in flow '{self.flow_id}'")
|
|
171
|
+
self.exit_node = node_id
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def validate(self) -> List[str]:
|
|
175
|
+
"""Validate the flow definition.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of validation error messages (empty if valid)
|
|
179
|
+
"""
|
|
180
|
+
errors = []
|
|
181
|
+
|
|
182
|
+
if not self.entry_node:
|
|
183
|
+
errors.append("Flow must have an entry node")
|
|
184
|
+
elif self.entry_node not in self.nodes:
|
|
185
|
+
errors.append(f"Entry node '{self.entry_node}' not found")
|
|
186
|
+
|
|
187
|
+
# Check that all edge endpoints exist
|
|
188
|
+
for edge in self.edges:
|
|
189
|
+
if edge.source not in self.nodes:
|
|
190
|
+
errors.append(f"Edge source '{edge.source}' not found")
|
|
191
|
+
if edge.target not in self.nodes:
|
|
192
|
+
errors.append(f"Edge target '{edge.target}' not found")
|
|
193
|
+
|
|
194
|
+
# Check for unreachable nodes
|
|
195
|
+
if self.entry_node:
|
|
196
|
+
reachable = self._find_reachable_nodes(self.entry_node)
|
|
197
|
+
for node_id in self.nodes:
|
|
198
|
+
if node_id not in reachable:
|
|
199
|
+
errors.append(f"Node '{node_id}' is unreachable from entry")
|
|
200
|
+
|
|
201
|
+
return errors
|
|
202
|
+
|
|
203
|
+
def _find_reachable_nodes(self, start: str) -> set:
|
|
204
|
+
"""Find all nodes reachable from a starting node."""
|
|
205
|
+
reachable = set()
|
|
206
|
+
to_visit = [start]
|
|
207
|
+
|
|
208
|
+
while to_visit:
|
|
209
|
+
current = to_visit.pop()
|
|
210
|
+
if current in reachable:
|
|
211
|
+
continue
|
|
212
|
+
reachable.add(current)
|
|
213
|
+
|
|
214
|
+
# Find outgoing edges
|
|
215
|
+
for edge in self.edges:
|
|
216
|
+
if edge.source == current and edge.target not in reachable:
|
|
217
|
+
to_visit.append(edge.target)
|
|
218
|
+
|
|
219
|
+
return reachable
|
|
220
|
+
|
|
221
|
+
def get_next_nodes(self, node_id: str) -> List[str]:
|
|
222
|
+
"""Get the next nodes from a given node.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
node_id: ID of the current node
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
List of target node IDs
|
|
229
|
+
"""
|
|
230
|
+
return [edge.target for edge in self.edges if edge.source == node_id]
|
|
231
|
+
|
|
232
|
+
def get_terminal_nodes(self) -> List[str]:
|
|
233
|
+
"""Get nodes with no outgoing edges (terminal nodes).
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of terminal node IDs
|
|
237
|
+
"""
|
|
238
|
+
sources = {edge.source for edge in self.edges}
|
|
239
|
+
return [node_id for node_id in self.nodes if node_id not in sources]
|
|
240
|
+
|
|
241
|
+
def __repr__(self) -> str:
|
|
242
|
+
return (
|
|
243
|
+
f"Flow(id={self.flow_id!r}, "
|
|
244
|
+
f"nodes={len(self.nodes)}, "
|
|
245
|
+
f"edges={len(self.edges)}, "
|
|
246
|
+
f"entry={self.entry_node!r})"
|
|
247
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""VisualFlow DSL support for the runtime VisualFlow compiler."""
|
|
2
|
+
|
|
3
|
+
from .models import VisualEdge, VisualFlow, VisualNode, load_visualflow_json
|
|
4
|
+
from .executor import visual_to_flow
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"VisualFlow",
|
|
8
|
+
"VisualNode",
|
|
9
|
+
"VisualEdge",
|
|
10
|
+
"load_visualflow_json",
|
|
11
|
+
"visual_to_flow",
|
|
12
|
+
]
|
|
13
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Deterministic workflow IDs for VisualFlow Agent nodes.
|
|
2
|
+
|
|
3
|
+
Visual Agent nodes are compiled into `START_SUBWORKFLOW` effects that reference
|
|
4
|
+
an AbstractAgent ReAct workflow registered in the runtime's WorkflowRegistry.
|
|
5
|
+
|
|
6
|
+
IDs must be stable across hosts so a VisualFlow JSON document can be executed
|
|
7
|
+
outside the web editor (CLI, AbstractCode, third-party apps).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_SAFE_ID_RE = re.compile(r"[^a-zA-Z0-9_-]+")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _sanitize(value: str) -> str:
|
|
19
|
+
value = str(value or "").strip()
|
|
20
|
+
if not value:
|
|
21
|
+
return "unknown"
|
|
22
|
+
value = _SAFE_ID_RE.sub("_", value)
|
|
23
|
+
return value or "unknown"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def visual_react_workflow_id(*, flow_id: str, node_id: str) -> str:
|
|
27
|
+
"""Return the workflow_id used for a VisualFlow Agent node's ReAct subworkflow."""
|
|
28
|
+
return f"visual_react_agent_{_sanitize(flow_id)}_{_sanitize(node_id)}"
|
|
29
|
+
|