agnt5 0.2.8a2__cp310-abi3-manylinux_2_34_x86_64.whl → 0.2.8a4__cp310-abi3-manylinux_2_34_x86_64.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.

Potentially problematic release.


This version of agnt5 might be problematic. Click here for more details.

agnt5/__init__.py CHANGED
@@ -14,7 +14,7 @@ from .workflow import WorkflowContext
14
14
  from .entity import (
15
15
  Entity,
16
16
  EntityRegistry,
17
- EntityStateManager,
17
+ EntityStateAdapter,
18
18
  EntityType,
19
19
  create_entity_context,
20
20
  with_entity_context,
@@ -54,7 +54,7 @@ __all__ = [
54
54
  "Entity",
55
55
  "EntityType",
56
56
  "EntityRegistry",
57
- "EntityStateManager",
57
+ "EntityStateAdapter",
58
58
  "with_entity_context",
59
59
  "create_entity_context",
60
60
  "workflow",
agnt5/_core.abi3.so CHANGED
Binary file
agnt5/agent.py CHANGED
@@ -79,78 +79,109 @@ class AgentContext(Context):
79
79
  self._agent_name = agent_name
80
80
  self._session_id = session_id or run_id
81
81
 
82
- # Determine state manager based on parent context
83
- from .entity import EntityStateManager, _get_state_manager
82
+ # Determine state adapter based on parent context
83
+ from .entity import EntityStateAdapter, _get_state_adapter
84
84
 
85
85
  if state_manager:
86
- # Explicit state manager provided
87
- self._state_manager = state_manager
88
- logger.debug(f"AgentContext using provided state manager")
86
+ # Explicit state adapter provided (parameter name kept for backward compat)
87
+ self._state_adapter = state_manager
88
+ logger.debug(f"AgentContext using provided state adapter")
89
89
  elif parent_context:
90
- # Try to inherit state manager from parent
90
+ # Try to inherit state adapter from parent
91
91
  try:
92
92
  # Check if parent is WorkflowContext or AgentContext
93
93
  if hasattr(parent_context, '_workflow_entity'):
94
- # WorkflowContext - get state manager from worker context
95
- self._state_manager = _get_state_manager()
94
+ # WorkflowContext - get state adapter from worker context
95
+ self._state_adapter = _get_state_adapter()
96
96
  logger.debug(f"AgentContext inheriting state from WorkflowContext")
97
- elif hasattr(parent_context, '_state_manager'):
98
- # Parent AgentContext - share state manager
99
- self._state_manager = parent_context._state_manager
97
+ elif hasattr(parent_context, '_state_adapter'):
98
+ # Parent AgentContext - share state adapter
99
+ self._state_adapter = parent_context._state_adapter
100
100
  logger.debug(f"AgentContext inheriting state from parent AgentContext")
101
+ elif hasattr(parent_context, '_state_manager'):
102
+ # Backward compatibility: parent has old _state_manager
103
+ self._state_adapter = parent_context._state_manager
104
+ logger.debug(f"AgentContext inheriting state from parent (legacy)")
101
105
  else:
102
- # FunctionContext or base Context - create new state manager
103
- self._state_manager = EntityStateManager()
104
- logger.debug(f"AgentContext created new state manager (parent has no state)")
105
- except RuntimeError:
106
- # _get_state_manager() failed (not in worker context) - create standalone
107
- self._state_manager = EntityStateManager()
108
- logger.debug(f"AgentContext created standalone state manager")
106
+ # FunctionContext or base Context - create new state adapter
107
+ self._state_adapter = EntityStateAdapter()
108
+ logger.debug(f"AgentContext created new state adapter (parent has no state)")
109
+ except RuntimeError as e:
110
+ # _get_state_adapter() failed (not in worker context) - create standalone
111
+ self._state_adapter = EntityStateAdapter()
112
+ logger.debug(f"AgentContext created standalone state adapter (not in worker context)")
109
113
  else:
110
- # Standalone - create new state manager
111
- self._state_manager = EntityStateManager()
112
- logger.debug(f"AgentContext created standalone state manager")
113
-
114
- # Conversation key for state storage
114
+ # Try to get from worker context first
115
+ try:
116
+ self._state_adapter = _get_state_adapter()
117
+ logger.debug(f"AgentContext got state adapter from worker context")
118
+ except RuntimeError as e:
119
+ # Standalone - create new state adapter
120
+ self._state_adapter = EntityStateAdapter()
121
+ logger.debug(f"AgentContext created standalone state adapter")
122
+
123
+ # Conversation key for state storage (used for in-memory state)
115
124
  self._conversation_key = f"agent:{agent_name}:{self._session_id}:messages"
125
+ # Entity key for database persistence (without :messages suffix to match API expectations)
126
+ self._entity_key = f"agent:{agent_name}:{self._session_id}"
127
+ logger.debug(f"AgentContext initialized - session_id={self._session_id}")
116
128
 
117
129
  @property
118
130
  def state(self):
119
131
  """
120
132
  Get state interface for agent state management.
121
133
 
134
+ Note: This is a simplified in-memory state interface for agent-specific data.
135
+ Conversation history is managed separately via get_conversation_history() and
136
+ save_conversation_history() which use the Rust-backed persistence layer.
137
+
122
138
  Returns:
123
- EntityState instance for state operations
139
+ Dict-like object for state operations
124
140
 
125
141
  Example:
126
- # Store conversation history
127
- messages = ctx.state.get(f"agent:{agent_name}:{session_id}:messages", [])
128
- messages.append({"role": "user", "content": "Hello"})
129
- ctx.state.set(f"agent:{agent_name}:{session_id}:messages", messages)
130
-
131
- # Store agent-specific data
132
- ctx.state.set("research_results", data)
142
+ # Store agent-specific data (in-memory only)
143
+ ctx.state["research_results"] = data
144
+ ctx.state["iteration_count"] = 5
133
145
  """
134
- from .entity import EntityState
135
-
136
- # Use agent's conversation key as the state key
137
- state_key = ("agent", self._conversation_key)
138
- state_dict = self._state_manager.get_or_create_state(state_key)
139
- return EntityState(state_dict)
146
+ # Simple dict-based state for agent-specific data
147
+ # This is in-memory only and not persisted to platform
148
+ if not hasattr(self, '_agent_state'):
149
+ self._agent_state = {}
150
+ return self._agent_state
140
151
 
141
152
  @property
142
153
  def session_id(self) -> str:
143
154
  """Get session identifier for this agent context."""
144
155
  return self._session_id
145
156
 
146
- def get_conversation_history(self) -> List[Message]:
157
+ async def get_conversation_history(self) -> List[Message]:
147
158
  """
148
- Retrieve conversation history from state.
159
+ Retrieve conversation history from state, loading from database if needed.
160
+
161
+ Uses the EntityStateAdapter which delegates to Rust core for cache-first loading.
149
162
 
150
163
  Returns:
151
164
  List of Message objects from conversation history
152
165
  """
153
- messages_data = self.state.get(self._conversation_key, [])
166
+ entity_type = "AgentSession"
167
+ entity_key = self._entity_key
168
+
169
+ # Load session data via adapter (Rust handles cache + platform load)
170
+ session_data = await self._state_adapter.load_state(entity_type, entity_key)
171
+
172
+ # Extract messages from session object
173
+ if isinstance(session_data, dict) and "messages" in session_data:
174
+ # New format with session metadata
175
+ messages_data = session_data["messages"]
176
+ logger.debug(f"Loaded {len(messages_data)} messages from session {entity_key}")
177
+ elif isinstance(session_data, list):
178
+ # Old format - just messages array
179
+ messages_data = session_data
180
+ logger.debug(f"Loaded {len(messages_data)} messages (legacy format)")
181
+ else:
182
+ # No messages found
183
+ messages_data = []
184
+ logger.debug(f"No conversation history found for {entity_key}")
154
185
 
155
186
  # Convert dict representations back to Message objects
156
187
  messages = []
@@ -174,13 +205,17 @@ class AgentContext(Context):
174
205
 
175
206
  return messages
176
207
 
177
- def save_conversation_history(self, messages: List[Message]) -> None:
208
+ async def save_conversation_history(self, messages: List[Message]) -> None:
178
209
  """
179
- Save conversation history to state.
210
+ Save conversation history to state and persist to database.
211
+
212
+ Uses the EntityStateAdapter which delegates to Rust core for version-checked saves.
180
213
 
181
214
  Args:
182
215
  messages: List of Message objects to persist
183
216
  """
217
+ logger.debug(f"Saving {len(messages)} messages to conversation history")
218
+
184
219
  # Convert Message objects to dict for JSON serialization
185
220
  messages_data = []
186
221
  for msg in messages:
@@ -189,7 +224,126 @@ class AgentContext(Context):
189
224
  "content": msg.content
190
225
  })
191
226
 
192
- self.state.set(self._conversation_key, messages_data)
227
+ import time
228
+ entity_type = "AgentSession"
229
+ entity_key = self._entity_key
230
+
231
+ # Load current state with version for optimistic locking
232
+ current_state, current_version = await self._state_adapter.load_with_version(
233
+ entity_type, entity_key
234
+ )
235
+
236
+ # Build session object with metadata
237
+ now = time.time()
238
+
239
+ # Get custom metadata from instance variable or preserve from loaded state
240
+ custom_metadata = getattr(self, '_custom_metadata', current_state.get("metadata", {}))
241
+
242
+ session_data = {
243
+ "session_id": self._session_id,
244
+ "agent_name": self._agent_name,
245
+ "created_at": current_state.get("created_at", now), # Preserve existing or set new
246
+ "last_message_time": now,
247
+ "message_count": len(messages_data),
248
+ "messages": messages_data,
249
+ "metadata": custom_metadata # Save custom metadata
250
+ }
251
+
252
+ # Save to platform via adapter (Rust handles optimistic locking)
253
+ try:
254
+ new_version = await self._state_adapter.save_state(
255
+ entity_type,
256
+ entity_key,
257
+ session_data,
258
+ current_version
259
+ )
260
+ logger.info(
261
+ f"Persisted conversation history: {entity_key} (version {current_version} -> {new_version})"
262
+ )
263
+ except Exception as e:
264
+ logger.error(f"Failed to persist conversation history to database: {e}")
265
+ # Don't fail - conversation is still in memory for this execution
266
+
267
+ async def get_metadata(self) -> Dict[str, Any]:
268
+ """
269
+ Get conversation session metadata.
270
+
271
+ Returns session metadata including:
272
+ - created_at: Timestamp of first message (float, Unix timestamp)
273
+ - last_activity: Timestamp of last message (float, Unix timestamp)
274
+ - message_count: Number of messages in conversation (int)
275
+ - custom: Dict of user-provided custom metadata
276
+
277
+ Returns:
278
+ Dictionary with metadata. If no conversation exists yet, returns defaults.
279
+
280
+ Example:
281
+ ```python
282
+ metadata = await context.get_metadata()
283
+ print(f"Session created: {metadata['created_at']}")
284
+ print(f"User ID: {metadata['custom'].get('user_id')}")
285
+ ```
286
+ """
287
+ entity_type = "AgentSession"
288
+ entity_key = self._entity_key
289
+
290
+ # Load session data
291
+ session_data = await self._state_adapter.load_state(entity_type, entity_key)
292
+
293
+ if not session_data:
294
+ # No conversation exists yet - return defaults
295
+ return {
296
+ "created_at": None,
297
+ "last_activity": None,
298
+ "message_count": 0,
299
+ "custom": getattr(self, '_custom_metadata', {})
300
+ }
301
+
302
+ messages = session_data.get("messages", [])
303
+
304
+ # Derive timestamps from messages if available
305
+ created_at = session_data.get("created_at")
306
+ last_activity = session_data.get("last_message_time")
307
+
308
+ return {
309
+ "created_at": created_at,
310
+ "last_activity": last_activity,
311
+ "message_count": len(messages),
312
+ "custom": session_data.get("metadata", {})
313
+ }
314
+
315
+ def update_metadata(self, **kwargs) -> None:
316
+ """
317
+ Update custom session metadata.
318
+
319
+ Metadata will be persisted alongside conversation history on next save.
320
+ Use this to store application-specific data like user_id, preferences, etc.
321
+
322
+ Args:
323
+ **kwargs: Key-value pairs to store as metadata
324
+
325
+ Example:
326
+ ```python
327
+ # Store user identification and preferences
328
+ context.update_metadata(
329
+ user_id="user-123",
330
+ subscription_tier="premium",
331
+ preferences={"theme": "dark", "language": "en"}
332
+ )
333
+
334
+ # Later retrieve it
335
+ metadata = await context.get_metadata()
336
+ user_id = metadata["custom"]["user_id"]
337
+ ```
338
+
339
+ Note:
340
+ - Metadata is merged with existing metadata (doesn't replace)
341
+ - Changes persist on next save_conversation_history() call
342
+ - Use simple JSON-serializable types (str, int, float, dict, list)
343
+ """
344
+ if not hasattr(self, '_custom_metadata'):
345
+ self._custom_metadata = {}
346
+ self._custom_metadata.update(kwargs)
193
347
 
194
348
 
195
349
  class Handoff:
@@ -665,11 +819,11 @@ class Agent:
665
819
 
666
820
  # Load conversation history from state (if AgentContext)
667
821
  if isinstance(context, AgentContext):
668
- messages: List[Message] = context.get_conversation_history()
822
+ messages: List[Message] = await context.get_conversation_history()
669
823
  # Add new user message
670
824
  messages.append(Message.user(user_message))
671
825
  # Save updated conversation
672
- context.save_conversation_history(messages)
826
+ await context.save_conversation_history(messages)
673
827
  else:
674
828
  # Fallback for non-AgentContext (shouldn't happen with code above)
675
829
  messages = [Message.user(user_message)]
@@ -795,7 +949,7 @@ class Agent:
795
949
  )
796
950
  # Save conversation before returning
797
951
  if isinstance(context, AgentContext):
798
- context.save_conversation_history(messages)
952
+ await context.save_conversation_history(messages)
799
953
  # Return immediately with handoff result
800
954
  return AgentResult(
801
955
  output=result["output"],
@@ -835,7 +989,7 @@ class Agent:
835
989
  self.logger.debug(f"Agent completed after {iteration + 1} iterations")
836
990
  # Save conversation before returning
837
991
  if isinstance(context, AgentContext):
838
- context.save_conversation_history(messages)
992
+ await context.save_conversation_history(messages)
839
993
  return AgentResult(
840
994
  output=response.text,
841
995
  tool_calls=all_tool_calls,
@@ -847,7 +1001,7 @@ class Agent:
847
1001
  final_output = messages[-1].content if messages else "No output generated"
848
1002
  # Save conversation before returning
849
1003
  if isinstance(context, AgentContext):
850
- context.save_conversation_history(messages)
1004
+ await context.save_conversation_history(messages)
851
1005
  return AgentResult(
852
1006
  output=final_output,
853
1007
  tool_calls=all_tool_calls,
agnt5/entity.py CHANGED
@@ -15,156 +15,245 @@ from ._telemetry import setup_module_logger
15
15
 
16
16
  logger = setup_module_logger(__name__)
17
17
 
18
- # Context variable for worker-scoped state manager
18
+ # Context variable for worker-scoped state adapter
19
19
  # This is set by Worker before entity execution and accessed by Entity instances
20
- _entity_state_manager_ctx: contextvars.ContextVar[Optional["EntityStateManager"]] = \
21
- contextvars.ContextVar('_entity_state_manager', default=None)
20
+ _entity_state_adapter_ctx: contextvars.ContextVar[Optional["EntityStateAdapter"]] = \
21
+ contextvars.ContextVar('_entity_state_adapter', default=None)
22
22
 
23
23
  # Global entity registry
24
24
  _ENTITY_REGISTRY: Dict[str, "EntityType"] = {}
25
25
 
26
26
 
27
- class EntityStateManager:
27
+ class EntityStateAdapter:
28
28
  """
29
- Worker-scoped state and lock management for entities.
30
-
31
- This class provides isolated state management per Worker instance,
32
- replacing the global dict approach. Each Worker gets its own state manager,
33
- which provides:
34
- - State storage per entity (type, key)
35
- - Single-writer locks per entity
36
- - Version tracking for optimistic locking
37
- - Platform state loading/saving via Rust EntityStateManager
29
+ Thin Python adapter providing Pythonic interface to Rust EntityStateManager core.
30
+
31
+ This adapter provides language-specific concerns only:
32
+ - Worker-local asyncio.Lock for coarse-grained coordination
33
+ - Type conversions between Python dict and JSON bytes
34
+ - Pythonic async/await API over Rust core
35
+
36
+ All business logic (caching, version tracking, retry logic, gRPC) lives in the Rust core.
37
+ This keeps the Python layer simple (~150 LOC) and enables sharing business logic across SDKs.
38
38
  """
39
39
 
40
- def __init__(self, rust_entity_state_manager=None):
40
+ def __init__(self, rust_core=None):
41
41
  """
42
- Initialize empty state manager.
42
+ Initialize entity state adapter.
43
43
 
44
44
  Args:
45
- rust_entity_state_manager: Optional Rust EntityStateManager for gRPC communication.
46
- TODO: Wire this up once PyO3 bindings are complete.
45
+ rust_core: Rust EntityStateManager instance (from _core module).
46
+ If None, operates in standalone/testing mode with in-memory state.
47
47
  """
48
- self._states: Dict[Tuple[str, str], Dict[str, Any]] = {}
49
- self._locks: Dict[Tuple[str, str], asyncio.Lock] = {}
50
- self._versions: Dict[Tuple[str, str], int] = {}
51
- self._rust_manager = rust_entity_state_manager # TODO: Use for load/save
52
- logger.debug("Created EntityStateManager")
53
-
54
- def get_or_create_state(self, state_key: Tuple[str, str]) -> Dict[str, Any]:
48
+ self._rust_core = rust_core
49
+ # Worker-local locks for coarse-grained coordination within this worker
50
+ self._local_locks: Dict[Tuple[str, str], asyncio.Lock] = {}
51
+
52
+ # Standalone mode: in-memory state storage when no Rust core
53
+ # This enables testing without the full platform stack
54
+ if rust_core is None:
55
+ self._standalone_states: Dict[Tuple[str, str], Dict[str, Any]] = {}
56
+ self._standalone_versions: Dict[Tuple[str, str], int] = {}
57
+ logger.debug("Created EntityStateAdapter in standalone mode (in-memory state)")
58
+ else:
59
+ logger.debug("Created EntityStateAdapter with Rust core")
60
+
61
+ def get_local_lock(self, state_key: Tuple[str, str]) -> asyncio.Lock:
55
62
  """
56
- Get or create state dict for entity instance.
63
+ Get worker-local asyncio.Lock for single-writer guarantee within this worker.
64
+
65
+ This provides coarse-grained coordination for operations within the same worker.
66
+ Cross-worker conflicts are handled by the Rust core via optimistic concurrency.
57
67
 
58
68
  Args:
59
69
  state_key: Tuple of (entity_type, entity_key)
60
70
 
61
71
  Returns:
62
- State dict for the entity instance
72
+ asyncio.Lock for this worker-local operation
63
73
  """
64
- if state_key not in self._states:
65
- self._states[state_key] = {}
66
- return self._states[state_key]
74
+ if state_key not in self._local_locks:
75
+ self._local_locks[state_key] = asyncio.Lock()
76
+ return self._local_locks[state_key]
67
77
 
68
- def get_or_create_lock(self, state_key: Tuple[str, str]) -> asyncio.Lock:
78
+ async def load_state(self, entity_type: str, entity_key: str) -> Dict[str, Any]:
69
79
  """
70
- Get or create async lock for entity instance.
80
+ Load entity state (Rust handles cache-first logic and platform load).
81
+
82
+ In standalone mode (no Rust core), uses in-memory state storage.
71
83
 
72
84
  Args:
73
- state_key: Tuple of (entity_type, entity_key)
85
+ entity_type: Type of entity (e.g., "ShoppingCart", "Counter")
86
+ entity_key: Unique key for entity instance
74
87
 
75
88
  Returns:
76
- Async lock for single-writer guarantee
89
+ State dictionary (empty dict if not found)
77
90
  """
78
- if state_key not in self._locks:
79
- self._locks[state_key] = asyncio.Lock()
80
- return self._locks[state_key]
91
+ if not self._rust_core:
92
+ # Standalone mode - return from in-memory storage
93
+ state_key = (entity_type, entity_key)
94
+ return self._standalone_states.get(state_key, {}).copy()
81
95
 
82
- def load_state_from_platform(
96
+ try:
97
+ # Rust checks cache first, loads from platform if needed
98
+ state_json_bytes, version = await self._rust_core.py_get_cached_or_load(entity_type, entity_key)
99
+
100
+ # Convert bytes to dict
101
+ if state_json_bytes:
102
+ state_json = state_json_bytes.decode('utf-8') if isinstance(state_json_bytes, bytes) else state_json_bytes
103
+ return json.loads(state_json)
104
+ else:
105
+ return {}
106
+ except Exception as e:
107
+ logger.warning(f"Failed to load state for {entity_type}:{entity_key}: {e}")
108
+ return {}
109
+
110
+ async def save_state(
83
111
  self,
84
- state_key: Tuple[str, str],
85
- platform_state_json: str,
86
- version: int = 0
87
- ) -> None:
112
+ entity_type: str,
113
+ entity_key: str,
114
+ state: Dict[str, Any],
115
+ expected_version: int
116
+ ) -> int:
88
117
  """
89
- Load state from platform for entity persistence.
118
+ Save entity state (Rust handles version check and platform save).
119
+
120
+ In standalone mode (no Rust core), stores in-memory with version tracking.
90
121
 
91
122
  Args:
92
- state_key: Tuple of (entity_type, entity_key)
93
- platform_state_json: JSON string of state from platform
94
- version: Current version from platform
123
+ entity_type: Type of entity
124
+ entity_key: Unique key for entity instance
125
+ state: State dictionary to save
126
+ expected_version: Expected current version (for optimistic locking)
127
+
128
+ Returns:
129
+ New version number after save
130
+
131
+ Raises:
132
+ RuntimeError: If version conflict or platform error
95
133
  """
96
- import json
97
- try:
98
- state = json.loads(platform_state_json)
99
- self._states[state_key] = state
100
- self._versions[state_key] = version
101
- logger.debug(
102
- f"Loaded platform state: {state_key[0]}/{state_key[1]} (version {version})"
103
- )
104
- except json.JSONDecodeError as e:
105
- logger.warning(f"Failed to parse platform state: {e}")
106
- self._states[state_key] = {}
107
- self._versions[state_key] = 0
134
+ if not self._rust_core:
135
+ # Standalone mode - store in memory with version tracking
136
+ state_key = (entity_type, entity_key)
137
+ current_version = self._standalone_versions.get(state_key, 0)
138
+
139
+ # Optimistic locking check (even in standalone mode for consistency)
140
+ if current_version != expected_version:
141
+ raise RuntimeError(
142
+ f"Version conflict: expected {expected_version}, got {current_version}"
143
+ )
108
144
 
109
- def get_state_for_persistence(
110
- self,
111
- state_key: Tuple[str, str]
112
- ) -> tuple[Dict[str, Any], int, int]:
145
+ # Store state and increment version
146
+ new_version = expected_version + 1
147
+ self._standalone_states[state_key] = state.copy()
148
+ self._standalone_versions[state_key] = new_version
149
+ return new_version
150
+
151
+ # Convert dict to JSON bytes
152
+ state_json = json.dumps(state).encode('utf-8')
153
+
154
+ # Rust handles optimistic locking and platform save
155
+ new_version = await self._rust_core.py_save_state(
156
+ entity_type,
157
+ entity_key,
158
+ state_json,
159
+ expected_version
160
+ )
161
+
162
+ return new_version
163
+
164
+ async def load_with_version(self, entity_type: str, entity_key: str) -> Tuple[Dict[str, Any], int]:
113
165
  """
114
- Get state and version info for platform persistence.
166
+ Load entity state with version (for update operations).
167
+
168
+ In standalone mode (no Rust core), loads from in-memory storage with version.
115
169
 
116
170
  Args:
117
- state_key: Tuple of (entity_type, entity_key)
171
+ entity_type: Type of entity
172
+ entity_key: Unique key for entity instance
118
173
 
119
174
  Returns:
120
- Tuple of (state_dict, expected_version, new_version)
175
+ Tuple of (state_dict, version)
176
+ """
177
+ if not self._rust_core:
178
+ # Standalone mode - return from in-memory storage with version
179
+ state_key = (entity_type, entity_key)
180
+ state = self._standalone_states.get(state_key, {}).copy()
181
+ version = self._standalone_versions.get(state_key, 0)
182
+ return state, version
183
+
184
+ try:
185
+ state_json_bytes, version = await self._rust_core.py_get_cached_or_load(entity_type, entity_key)
186
+
187
+ if state_json_bytes:
188
+ state_json = state_json_bytes.decode('utf-8') if isinstance(state_json_bytes, bytes) else state_json_bytes
189
+ state = json.loads(state_json)
190
+ else:
191
+ state = {}
192
+
193
+ return state, version
194
+ except Exception as e:
195
+ logger.warning(f"Failed to load state with version for {entity_type}:{entity_key}: {e}")
196
+ return {}, 0
197
+
198
+ async def invalidate_cache(self, entity_type: str, entity_key: str) -> None:
121
199
  """
122
- state_dict = self._states.get(state_key, {})
123
- expected_version = self._versions.get(state_key, 0)
124
- new_version = expected_version + 1
200
+ Invalidate cache entry for specific entity.
125
201
 
126
- # Update version for next execution
127
- self._versions[state_key] = new_version
202
+ Args:
203
+ entity_type: Type of entity
204
+ entity_key: Unique key for entity instance
205
+ """
206
+ if self._rust_core:
207
+ await self._rust_core.py_invalidate_cache(entity_type, entity_key)
128
208
 
129
- return state_dict, expected_version, new_version
209
+ async def clear_cache(self) -> None:
210
+ """Clear entire cache (useful for testing)."""
211
+ if self._rust_core:
212
+ await self._rust_core.py_clear_cache()
130
213
 
131
214
  def clear_all(self) -> None:
132
- """Clear all state, locks, and versions (for testing)."""
133
- self._states.clear()
134
- self._locks.clear()
135
- self._versions.clear()
136
- logger.debug("Cleared EntityStateManager")
215
+ """Clear all local locks (for testing)."""
216
+ self._local_locks.clear()
217
+ logger.debug("Cleared EntityStateAdapter local locks")
137
218
 
138
- def get_state(self, entity_type: str, key: str) -> Optional[Dict[str, Any]]:
219
+ async def get_state(self, entity_type: str, key: str) -> Optional[Dict[str, Any]]:
139
220
  """Get state for debugging/testing."""
140
- state_key = (entity_type, key)
141
- return self._states.get(state_key)
221
+ state, _ = await self.load_with_version(entity_type, key)
222
+ return state if state else None
142
223
 
143
224
  def get_all_keys(self, entity_type: str) -> list[str]:
144
- """Get all keys for entity type (for debugging/testing)."""
145
- return [
146
- key for (etype, key) in self._states.keys()
147
- if etype == entity_type
148
- ]
225
+ """
226
+ Get all keys for an entity type (testing/debugging only).
227
+
228
+ Only works in standalone mode. Returns empty list in production mode.
229
+ """
230
+ if not hasattr(self, '_standalone_states'):
231
+ return []
149
232
 
233
+ keys = []
234
+ for (etype, ekey) in self._standalone_states.keys():
235
+ if etype == entity_type:
236
+ keys.append(ekey)
237
+ return keys
150
238
 
151
- def _get_state_manager() -> EntityStateManager:
239
+
240
+ def _get_state_adapter() -> EntityStateAdapter:
152
241
  """
153
- Get the current entity state manager from context.
242
+ Get the current entity state adapter from context.
154
243
 
155
- The state manager must be set by Worker before entity execution.
244
+ The state adapter must be set by Worker before entity execution.
156
245
  This ensures proper worker-scoped state isolation.
157
246
 
158
247
  Returns:
159
- EntityStateManager instance
248
+ EntityStateAdapter instance
160
249
 
161
250
  Raises:
162
- RuntimeError: If called outside of Worker context (state manager not set)
251
+ RuntimeError: If called outside of Worker context (state adapter not set)
163
252
  """
164
- manager = _entity_state_manager_ctx.get()
165
- if manager is None:
253
+ adapter = _entity_state_adapter_ctx.get()
254
+ if adapter is None:
166
255
  raise RuntimeError(
167
- "Entity requires state manager context.\n\n"
256
+ "Entity requires state adapter context.\n\n"
168
257
  "In production:\n"
169
258
  " Entities run automatically through Worker.\n\n"
170
259
  "In tests, use one of:\n"
@@ -179,7 +268,9 @@ def _get_state_manager() -> EntityStateManager:
179
268
  " await cart.add_item(...)\n\n"
180
269
  "See: https://docs.agnt5.dev/sdk/entities#testing"
181
270
  )
182
- return manager
271
+ return adapter
272
+
273
+
183
274
 
184
275
 
185
276
  # ============================================================================
@@ -188,7 +279,7 @@ def _get_state_manager() -> EntityStateManager:
188
279
 
189
280
  def with_entity_context(func):
190
281
  """
191
- Decorator that sets up entity state manager for tests.
282
+ Decorator that sets up entity state adapter for tests.
192
283
 
193
284
  Usage:
194
285
  @with_entity_context
@@ -199,13 +290,13 @@ def with_entity_context(func):
199
290
  """
200
291
  @functools.wraps(func)
201
292
  async def wrapper(*args, **kwargs):
202
- manager = EntityStateManager()
203
- token = _entity_state_manager_ctx.set(manager)
293
+ adapter = EntityStateAdapter()
294
+ token = _entity_state_adapter_ctx.set(adapter)
204
295
  try:
205
296
  return await func(*args, **kwargs)
206
297
  finally:
207
- _entity_state_manager_ctx.reset(token)
208
- manager.clear_all()
298
+ _entity_state_adapter_ctx.reset(token)
299
+ adapter.clear_all()
209
300
  return wrapper
210
301
 
211
302
 
@@ -219,16 +310,16 @@ def create_entity_context():
219
310
 
220
311
  @pytest.fixture
221
312
  def entity_context():
222
- manager, token = create_entity_context()
223
- yield manager
313
+ adapter, token = create_entity_context()
314
+ yield adapter
224
315
  # Cleanup happens automatically
225
316
 
226
317
  Returns:
227
- Tuple of (EntityStateManager, context_token)
318
+ Tuple of (EntityStateAdapter, context_token)
228
319
  """
229
- manager = EntityStateManager()
230
- token = _entity_state_manager_ctx.set(manager)
231
- return manager, token
320
+ adapter = EntityStateAdapter()
321
+ token = _entity_state_adapter_ctx.set(adapter)
322
+ return adapter, token
232
323
 
233
324
 
234
325
  def extract_state_schema(entity_class: type) -> Optional[Dict[str, Any]]:
@@ -473,12 +564,12 @@ def _create_entity_method_wrapper(entity_type: str, method):
473
564
  """
474
565
  Create a wrapper for an entity method that provides single-writer consistency.
475
566
 
476
- This wrapper:
477
- 1. Acquires a lock for the entity instance (single-writer guarantee)
478
- 2. Sets up EntityState with the state dict
479
- 3. Executes the method
480
- 4. Cleans up state reference
481
- 5. Handles errors appropriately
567
+ This wrapper implements hybrid locking:
568
+ 1. Local lock (asyncio.Lock) for worker-scoped single-writer guarantee
569
+ 2. Optimistic concurrency (via Rust) for cross-worker conflicts
570
+ 3. Loads state via adapter (Rust handles cache + platform)
571
+ 4. Executes the method with clean EntityState interface
572
+ 5. Saves state via adapter (Rust handles version check + retry)
482
573
 
483
574
  Args:
484
575
  entity_type: Name of the entity type (class name)
@@ -489,26 +580,23 @@ def _create_entity_method_wrapper(entity_type: str, method):
489
580
  """
490
581
  @functools.wraps(method)
491
582
  async def entity_method_wrapper(self, *args, **kwargs):
492
- """Execute entity method with single-writer guarantee."""
583
+ """Execute entity method with hybrid locking (local + optimistic)."""
493
584
  state_key = (entity_type, self._key)
494
585
 
495
- # Get state manager and lock (single-writer guarantee)
496
- state_manager = _get_state_manager()
497
- lock = state_manager.get_or_create_lock(state_key)
586
+ # Get state adapter
587
+ adapter = _get_state_adapter()
588
+
589
+ # Local lock for worker-scoped single-writer guarantee
590
+ lock = adapter.get_local_lock(state_key)
498
591
 
499
592
  async with lock:
500
- # TODO: Load state from platform if not in memory
501
- # if state_key not in state_manager._states and state_manager._rust_manager:
502
- # result = await state_manager._rust_manager.load_state(
503
- # tenant_id, entity_type, self._key
504
- # )
505
- # if result.found:
506
- # state_manager.load_state_from_platform(
507
- # state_key, result.state_json, result.version
508
- # )
509
-
510
- # Get or create state for this entity instance
511
- state_dict = state_manager.get_or_create_state(state_key)
593
+ # Load state with version (Rust handles cache-first + platform load)
594
+ state_dict, current_version = await adapter.load_with_version(entity_type, self._key)
595
+
596
+ logger.debug(
597
+ "Loaded state for %s:%s (version %d)",
598
+ entity_type, self._key, current_version
599
+ )
512
600
 
513
601
  # Set up EntityState on instance for method access
514
602
  self._state = EntityState(state_dict)
@@ -519,16 +607,26 @@ def _create_entity_method_wrapper(entity_type: str, method):
519
607
  result = await method(self, *args, **kwargs)
520
608
  logger.debug("Completed %s:%s.%s", entity_type, self._key, method.__name__)
521
609
 
522
- # TODO: Save state to platform after successful execution
523
- # if state_manager._rust_manager:
524
- # state_dict, expected_version, new_version = \
525
- # state_manager.get_state_for_persistence(state_key)
526
- # import json
527
- # state_json = json.dumps(state_dict).encode('utf-8')
528
- # save_result = await state_manager._rust_manager.save_state(
529
- # tenant_id, entity_type, self._key, state_json, expected_version
530
- # )
531
- # state_manager._versions[state_key] = save_result.new_version
610
+ # Save state after successful execution
611
+ # Rust handles optimistic locking (version check)
612
+ try:
613
+ new_version = await adapter.save_state(
614
+ entity_type,
615
+ self._key,
616
+ state_dict,
617
+ current_version
618
+ )
619
+ logger.info(
620
+ "Saved state for %s:%s (version %d -> %d)",
621
+ entity_type, self._key, current_version, new_version
622
+ )
623
+ except Exception as e:
624
+ logger.error(
625
+ "Failed to save state for %s:%s: %s",
626
+ entity_type, self._key, e
627
+ )
628
+ # Don't fail the method execution just because persistence failed
629
+ # The state is still in the local dict for this execution
532
630
 
533
631
  return result
534
632
 
agnt5/worker.py CHANGED
@@ -126,6 +126,10 @@ class Worker:
126
126
  self.runtime = runtime
127
127
  self.metadata = metadata or {}
128
128
 
129
+ # Get tenant_id from environment (required for entity state management)
130
+ import os
131
+ self._tenant_id = os.getenv("AGNT5_TENANT_ID", "default-tenant")
132
+
129
133
  # Import Rust worker
130
134
  try:
131
135
  from ._core import PyWorker, PyWorkerConfig, PyComponentInfo
@@ -148,9 +152,17 @@ class Worker:
148
152
  # Create Rust worker instance
149
153
  self._rust_worker = self._PyWorker(self._rust_config)
150
154
 
151
- # Create worker-scoped entity state manager
152
- from .entity import EntityStateManager
153
- self._entity_state_manager = EntityStateManager()
155
+ # Create worker-scoped entity state adapter with Rust core
156
+ from .entity import EntityStateAdapter
157
+ from ._core import EntityStateManager as RustEntityStateManager
158
+
159
+ # Create Rust core for entity state management
160
+ rust_core = RustEntityStateManager(tenant_id=self._tenant_id)
161
+
162
+ # Create Python adapter (thin wrapper around Rust core)
163
+ self._entity_state_adapter = EntityStateAdapter(rust_core=rust_core)
164
+
165
+ logger.info("Created EntityStateAdapter with Rust core for state management")
154
166
 
155
167
  # Component registration: auto-discover or explicit
156
168
  if auto_register:
@@ -746,7 +758,7 @@ class Worker:
746
758
  """Execute a workflow handler with automatic replay support."""
747
759
  import json
748
760
  from .workflow import WorkflowEntity, WorkflowContext
749
- from .entity import _get_state_manager
761
+ from .entity import _get_state_adapter
750
762
  from ._core import PyExecuteComponentResponse
751
763
 
752
764
  try:
@@ -787,10 +799,15 @@ class Worker:
787
799
  logger.debug(f"Loaded {len(completed_steps)} completed steps into workflow entity")
788
800
 
789
801
  if initial_state:
790
- # Load initial state into entity's state manager
791
- state_manager = _get_state_manager()
792
- state_manager._states[workflow_entity._state_key] = initial_state
793
- logger.debug(f"Loaded initial state with {len(initial_state)} keys into workflow entity")
802
+ # Load initial state into entity's state adapter
803
+ state_adapter = _get_state_adapter()
804
+ if hasattr(state_adapter, '_standalone_states'):
805
+ # Standalone mode - set state directly
806
+ state_adapter._standalone_states[workflow_entity._state_key] = initial_state
807
+ logger.debug(f"Loaded initial state with {len(initial_state)} keys into workflow entity (standalone)")
808
+ else:
809
+ # Production mode - state is managed by Rust core
810
+ logger.debug(f"Initial state will be loaded from platform (production mode)")
794
811
 
795
812
  # Create WorkflowContext with entity and runtime_context for trace correlation
796
813
  ctx = WorkflowContext(
@@ -925,11 +942,11 @@ class Worker:
925
942
  """Execute an entity method."""
926
943
  import json
927
944
  from .context import Context
928
- from .entity import EntityType, Entity, _entity_state_manager_ctx
945
+ from .entity import EntityType, Entity, _entity_state_adapter_ctx
929
946
  from ._core import PyExecuteComponentResponse
930
947
 
931
- # Set entity state manager in context for Entity instances to access
932
- _entity_state_manager_ctx.set(self._entity_state_manager)
948
+ # Set entity state adapter in context for Entity instances to access
949
+ _entity_state_adapter_ctx.set(self._entity_state_adapter)
933
950
 
934
951
  try:
935
952
  # Parse input data
@@ -944,23 +961,8 @@ class Worker:
944
961
  if not method_name:
945
962
  raise ValueError("Entity invocation requires 'method' parameter")
946
963
 
947
- # Load state from platform if provided in request metadata
948
- state_key = (entity_type.name, entity_key)
949
- if hasattr(request, 'metadata') and request.metadata:
950
- if "entity_state" in request.metadata:
951
- platform_state_json = request.metadata["entity_state"]
952
- platform_version = int(request.metadata.get("state_version", "0"))
953
-
954
- # Load platform state into state manager
955
- self._entity_state_manager.load_state_from_platform(
956
- state_key,
957
- platform_state_json,
958
- platform_version
959
- )
960
- logger.info(
961
- f"Loaded entity state from platform: {entity_type.name}/{entity_key} "
962
- f"(version {platform_version})"
963
- )
964
+ # Note: State loading is now handled automatically by the entity method wrapper
965
+ # via EntityStateAdapter which uses the Rust core for cache + platform persistence
964
966
 
965
967
  # Create entity instance using the stored class reference
966
968
  entity_instance = entity_type.entity_class(key=entity_key)
@@ -971,32 +973,15 @@ class Worker:
971
973
 
972
974
  method = getattr(entity_instance, method_name)
973
975
 
974
- # Execute method
976
+ # Execute method (entity method wrapper handles state load/save automatically)
975
977
  result = await method(**input_dict)
976
978
 
977
979
  # Serialize result
978
980
  output_data = json.dumps(result).encode("utf-8")
979
981
 
980
- # Capture entity state after execution with version tracking
981
- state_dict, expected_version, new_version = \
982
- self._entity_state_manager.get_state_for_persistence(state_key)
983
-
982
+ # Note: State persistence is now handled automatically by the entity method wrapper
983
+ # via EntityStateAdapter which uses Rust core for optimistic locking + version tracking
984
984
  metadata = {}
985
- if state_dict:
986
- # Serialize state as JSON string for platform persistence
987
- state_json = json.dumps(state_dict)
988
- # Pass in metadata for Worker Coordinator to publish
989
- metadata = {
990
- "entity_state": state_json,
991
- "entity_type": entity_type.name,
992
- "entity_key": entity_key,
993
- "expected_version": str(expected_version),
994
- "new_version": str(new_version),
995
- }
996
- logger.info(
997
- f"Captured entity state: {entity_type.name}/{entity_key} "
998
- f"(version {expected_version} → {new_version})"
999
- )
1000
985
 
1001
986
  return PyExecuteComponentResponse(
1002
987
  invocation_id=request.invocation_id,
@@ -1027,11 +1012,16 @@ class Worker:
1027
1012
  )
1028
1013
 
1029
1014
  async def _execute_agent(self, agent, input_data: bytes, request):
1030
- """Execute an agent."""
1015
+ """Execute an agent with session support for multi-turn conversations."""
1031
1016
  import json
1032
- from .context import Context
1017
+ import uuid
1018
+ from .agent import AgentContext
1019
+ from .entity import _entity_state_adapter_ctx
1033
1020
  from ._core import PyExecuteComponentResponse
1034
1021
 
1022
+ # Set entity state adapter in context so AgentContext can access it
1023
+ _entity_state_adapter_ctx.set(self._entity_state_adapter)
1024
+
1035
1025
  try:
1036
1026
  # Parse input data
1037
1027
  input_dict = json.loads(input_data.decode("utf-8")) if input_data else {}
@@ -1041,16 +1031,30 @@ class Worker:
1041
1031
  if not user_message:
1042
1032
  raise ValueError("Agent invocation requires 'message' parameter")
1043
1033
 
1044
- # Create context with runtime_context for trace correlation
1045
- ctx = Context(
1046
- run_id=f"{self.service_name}:{agent.name}",
1034
+ # Extract or generate session_id for multi-turn conversation support
1035
+ # If session_id is provided, the agent will load previous conversation history
1036
+ # If not provided, a new session is created with auto-generated ID
1037
+ session_id = input_dict.get("session_id")
1038
+
1039
+ if not session_id:
1040
+ session_id = str(uuid.uuid4())
1041
+ logger.info(f"Created new agent session: {session_id}")
1042
+ else:
1043
+ logger.info(f"Using existing agent session: {session_id}")
1044
+
1045
+ # Create AgentContext with session support for conversation persistence
1046
+ # AgentContext automatically loads/saves conversation history based on session_id
1047
+ ctx = AgentContext(
1048
+ run_id=request.invocation_id,
1049
+ agent_name=agent.name,
1050
+ session_id=session_id,
1047
1051
  runtime_context=request.runtime_context,
1048
1052
  )
1049
1053
 
1050
- # Execute agent
1054
+ # Execute agent - conversation history is automatically included
1051
1055
  agent_result = await agent.run(user_message, context=ctx)
1052
1056
 
1053
- # Build response
1057
+ # Build response with agent output and tool calls
1054
1058
  result = {
1055
1059
  "output": agent_result.output,
1056
1060
  "tool_calls": agent_result.tool_calls,
@@ -1059,13 +1063,16 @@ class Worker:
1059
1063
  # Serialize result
1060
1064
  output_data = json.dumps(result).encode("utf-8")
1061
1065
 
1066
+ # Return session_id in metadata so UI can persist it
1067
+ metadata = {"session_id": session_id}
1068
+
1062
1069
  return PyExecuteComponentResponse(
1063
1070
  invocation_id=request.invocation_id,
1064
1071
  success=True,
1065
1072
  output_data=output_data,
1066
1073
  state_update=None,
1067
1074
  error_message=None,
1068
- metadata=None,
1075
+ metadata=metadata,
1069
1076
  is_chunk=False,
1070
1077
  done=True,
1071
1078
  chunk_index=0,
@@ -1127,6 +1134,13 @@ class Worker:
1127
1134
  if self.metadata:
1128
1135
  self._rust_worker.set_service_metadata(self.metadata)
1129
1136
 
1137
+ # Configure entity state manager on Rust worker for database persistence
1138
+ logger.info("Configuring Rust EntityStateManager for database persistence")
1139
+ # Access the Rust core from the adapter
1140
+ if hasattr(self._entity_state_adapter, '_rust_core') and self._entity_state_adapter._rust_core:
1141
+ self._rust_worker.set_entity_state_manager(self._entity_state_adapter._rust_core)
1142
+ logger.info("Successfully configured Rust EntityStateManager")
1143
+
1130
1144
  # Get the current event loop to pass to Rust for concurrent Python async execution
1131
1145
  # This allows Rust to execute Python async functions on the same event loop
1132
1146
  # without spawn_blocking overhead, enabling true concurrency
agnt5/workflow.py CHANGED
@@ -11,7 +11,7 @@ from typing import Any, Callable, Dict, Optional, TypeVar, cast
11
11
 
12
12
  from ._schema_utils import extract_function_metadata, extract_function_schemas
13
13
  from .context import Context
14
- from .entity import Entity, EntityState, _get_state_manager
14
+ from .entity import Entity, EntityState, _get_state_adapter
15
15
  from .function import FunctionContext
16
16
  from .types import HandlerFunc, WorkflowConfig
17
17
  from ._telemetry import setup_module_logger
@@ -346,10 +346,8 @@ class WorkflowEntity(Entity):
346
346
  for debugging and replay of AI workflows.
347
347
  """
348
348
  if self._state is None:
349
- # Get state dict from state manager
350
- state_manager = _get_state_manager()
351
- state_dict = state_manager.get_or_create_state(self._state_key)
352
- self._state = WorkflowState(state_dict, self)
349
+ # Initialize with empty state dict - will be populated by entity system
350
+ self._state = WorkflowState({}, self)
353
351
  return self._state
354
352
 
355
353
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agnt5
3
- Version: 0.2.8a2
3
+ Version: 0.2.8a4
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,15 +1,15 @@
1
- agnt5-0.2.8a2.dist-info/METADATA,sha256=1Lj5nOz64iKpWdMoxTRlt_DB4QavSlWDrqqjZXzH11c,996
2
- agnt5-0.2.8a2.dist-info/WHEEL,sha256=AdMozAxftELsa3nYun92mL1tYO-R1ewuDPju53zvoK0,107
3
- agnt5/__init__.py,sha256=Cscbhye6pA8Jp-sKBGfP4kYXElUdF6aOSHVf-7ph4Dg,2045
1
+ agnt5-0.2.8a4.dist-info/METADATA,sha256=Dp8yJo_dx5B79OM0ptGgR80TjCUbyovpSc4Pt57dfuY,996
2
+ agnt5-0.2.8a4.dist-info/WHEEL,sha256=AdMozAxftELsa3nYun92mL1tYO-R1ewuDPju53zvoK0,107
3
+ agnt5/__init__.py,sha256=ACkK91EPdnv5tYip09QCZ9rfV4iBKzNjGfYVLJD1XGg,2045
4
4
  agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
5
- agnt5/_core.abi3.so,sha256=8fEfT968EuzIbg2BlRpUuzmnKbeB5VCwhI7L9BeSOes,15141600
5
+ agnt5/_core.abi3.so,sha256=CNmJC1AYLy8dABLguVfaWM6YqaTswjRnUZwM_07MXyk,15859720
6
6
  agnt5/_retry_utils.py,sha256=loHsWY5BR4wZy57IzcDEjQAy88DHVwVIr25Cn1d9GPA,5801
7
7
  agnt5/_schema_utils.py,sha256=MR67RW757T4Oq2Jqf4kB61H_b51zwaf3CLWELnkngRo,9572
8
8
  agnt5/_telemetry.py,sha256=bIY9AvBRjJBTHoBPbfR6X1OgaiUf-T0vCoi0_snsWXA,5957
9
- agnt5/agent.py,sha256=cz9gDB6c-eRJhBihEIuvTnNBwonpH6G8lbm44j_DKgA,36704
9
+ agnt5/agent.py,sha256=aBrhtPaUAHOHv3-h_Yb2UMqFHertr1P2hJ7fA_4IXcw,43225
10
10
  agnt5/client.py,sha256=kXksazgxdVXWaG9OkjJA4cWruNtcS-ENhtnkrIdw-Nk,23212
11
11
  agnt5/context.py,sha256=S2OzPkhn_jnqSWfT21mSYOux8vHaLKQxcAvggZDHQek,2378
12
- agnt5/entity.py,sha256=jMnSRTv6MNlK05cJ0FWYQR89ZTz_ywtVuwv-Sjr2Jfc,24925
12
+ agnt5/entity.py,sha256=AlHmSHVxQD5EYBvkmERKUkwv0ERrKaT8rvRK611hv_I,28941
13
13
  agnt5/exceptions.py,sha256=mZ0q-NK6OKhYxgwBJpIbgpgzk-CJaFIHDbp1EE-pS7I,925
14
14
  agnt5/function.py,sha256=f1vaAlJRwuo8cxCOGEd8XPido00mOhlPS8UJJx-6hJI,11041
15
15
  agnt5/lm.py,sha256=9dFjd6eQ3f3lFZe7H7rWZherYiP_58MT1F5xpwD8PCg,23195
@@ -17,6 +17,6 @@ agnt5/tool.py,sha256=uc4L-Q9QyLzQDe-MZKk2Wo3o5e-mK8tfaQwVDgQdouQ,13133
17
17
  agnt5/tracing.py,sha256=Mh2-OfnQM61lM_P8gxJstafdsUA8Gxoo1lP-Joxhub8,5980
18
18
  agnt5/types.py,sha256=Zb71ZMwvrt1p4SH18cAKunp2y5tao_W5_jGYaPDejQo,2840
19
19
  agnt5/version.py,sha256=rOq1mObLihnnKgKqBrwZA0zwOPudEKVFcW1a48ynkqc,573
20
- agnt5/worker.py,sha256=gIbYOdmOczNAqCgErzfLqIukbCpOutdOTZSWv_BatkU,46777
21
- agnt5/workflow.py,sha256=sU8Gk7unxE_Gla7Fe-KlXfcBvYa2326GciuoR26CCr0,19585
22
- agnt5-0.2.8a2.dist-info/RECORD,,
20
+ agnt5/worker.py,sha256=NflbueeL2LT8NGywTQnEv1r-N8f54AENWcZARJ5wO8o,47975
21
+ agnt5/workflow.py,sha256=3s9CY6a4UkJZ9YyHv2SAkY3UeCVBlfVi7jxJMFi8Dhg,19488
22
+ agnt5-0.2.8a4.dist-info/RECORD,,