agnt5 0.2.8a3__cp310-abi3-manylinux_2_34_aarch64.whl → 0.2.8a4__cp310-abi3-manylinux_2_34_aarch64.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
@@ -7,7 +7,6 @@ with built-in durability guarantees and state management.
7
7
 
8
8
  from ._compat import _import_error, _rust_available
9
9
  from .agent import Agent, AgentContext, AgentRegistry, AgentResult, Handoff, agent, handoff
10
- from .agent_session import AgentSession
11
10
  from .client import Client, RunError
12
11
  from .context import Context
13
12
  from .function import FunctionContext
@@ -15,7 +14,7 @@ from .workflow import WorkflowContext
15
14
  from .entity import (
16
15
  Entity,
17
16
  EntityRegistry,
18
- EntityStateManager,
17
+ EntityStateAdapter,
19
18
  EntityType,
20
19
  create_entity_context,
21
20
  with_entity_context,
@@ -55,7 +54,7 @@ __all__ = [
55
54
  "Entity",
56
55
  "EntityType",
57
56
  "EntityRegistry",
58
- "EntityStateManager",
57
+ "EntityStateAdapter",
59
58
  "with_entity_context",
60
59
  "create_entity_context",
61
60
  "workflow",
@@ -67,7 +66,6 @@ __all__ = [
67
66
  "Agent",
68
67
  "AgentRegistry",
69
68
  "AgentResult",
70
- "AgentSession",
71
69
  "Handoff",
72
70
  "handoff",
73
71
  # Types
agnt5/_core.abi3.so CHANGED
Binary file
agnt5/agent.py CHANGED
@@ -79,42 +79,46 @@ 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)")
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)")
105
109
  except RuntimeError as e:
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 (not in worker context)")
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
114
  # Try to get from worker context first
111
115
  try:
112
- self._state_manager = _get_state_manager()
113
- logger.debug(f"AgentContext got state manager from worker context")
116
+ self._state_adapter = _get_state_adapter()
117
+ logger.debug(f"AgentContext got state adapter from worker context")
114
118
  except RuntimeError as e:
115
- # Standalone - create new state manager
116
- self._state_manager = EntityStateManager()
117
- logger.debug(f"AgentContext created standalone state manager")
119
+ # Standalone - create new state adapter
120
+ self._state_adapter = EntityStateAdapter()
121
+ logger.debug(f"AgentContext created standalone state adapter")
118
122
 
119
123
  # Conversation key for state storage (used for in-memory state)
120
124
  self._conversation_key = f"agent:{agent_name}:{self._session_id}:messages"
@@ -127,24 +131,23 @@ class AgentContext(Context):
127
131
  """
128
132
  Get state interface for agent state management.
129
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
+
130
138
  Returns:
131
- EntityState instance for state operations
139
+ Dict-like object for state operations
132
140
 
133
141
  Example:
134
- # Store conversation history
135
- messages = ctx.state.get(f"agent:{agent_name}:{session_id}:messages", [])
136
- messages.append({"role": "user", "content": "Hello"})
137
- ctx.state.set(f"agent:{agent_name}:{session_id}:messages", messages)
138
-
139
- # Store agent-specific data
140
- 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
141
145
  """
142
- from .entity import EntityState
143
-
144
- # Use agent's conversation key as the state key
145
- state_key = ("agent", self._conversation_key)
146
- state_dict = self._state_manager.get_or_create_state(state_key)
147
- 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
148
151
 
149
152
  @property
150
153
  def session_id(self) -> str:
@@ -155,45 +158,30 @@ class AgentContext(Context):
155
158
  """
156
159
  Retrieve conversation history from state, loading from database if needed.
157
160
 
161
+ Uses the EntityStateAdapter which delegates to Rust core for cache-first loading.
162
+
158
163
  Returns:
159
164
  List of Message objects from conversation history
160
165
  """
161
- # Try to load from database first if not in memory
162
166
  entity_type = "AgentSession"
163
- entity_key = self._entity_key # Use entity_key without :messages suffix
164
- state_key = (entity_type, entity_key)
165
-
166
- # Check if we need to load from platform
167
- if state_key not in self._state_manager._states and self._state_manager._rust_manager:
168
- try:
169
- result = await self._state_manager._rust_manager.py_load_state(
170
- entity_type, entity_key
171
- )
172
- found, state_json_bytes, version = result
173
- if found:
174
- import json
175
- state_json = state_json_bytes.decode('utf-8') if isinstance(state_json_bytes, bytes) else state_json_bytes
176
- session_data = json.loads(state_json)
177
-
178
- # Extract messages from session object (might be old format or new format)
179
- if isinstance(session_data, dict) and "messages" in session_data:
180
- # New format with session metadata
181
- messages_data = session_data["messages"]
182
- elif isinstance(session_data, list):
183
- # Old format - just messages array
184
- messages_data = session_data
185
- else:
186
- messages_data = []
187
-
188
- # Load messages into in-memory state (store just messages, not full session object)
189
- self._state_manager.load_state_from_platform(state_key, json.dumps(messages_data), version)
190
- logger.info(f"Loaded conversation history from database: {entity_key} (version {version})")
191
- except Exception as e:
192
- logger.warning(f"Failed to load conversation history from database: {e}")
193
-
194
- # Get from in-memory state
195
- messages_data = self.state.get(self._conversation_key, [])
196
- logger.debug(f"Loaded {len(messages_data)} messages from conversation history")
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}")
197
185
 
198
186
  # Convert dict representations back to Message objects
199
187
  messages = []
@@ -221,6 +209,8 @@ class AgentContext(Context):
221
209
  """
222
210
  Save conversation history to state and persist to database.
223
211
 
212
+ Uses the EntityStateAdapter which delegates to Rust core for version-checked saves.
213
+
224
214
  Args:
225
215
  messages: List of Message objects to persist
226
216
  """
@@ -234,46 +224,126 @@ class AgentContext(Context):
234
224
  "content": msg.content
235
225
  })
236
226
 
237
- # Save to in-memory state
238
- self.state.set(self._conversation_key, messages_data)
227
+ import time
228
+ entity_type = "AgentSession"
229
+ entity_key = self._entity_key
239
230
 
240
- # Persist to database via Rust EntityStateManager
241
- if self._state_manager._rust_manager:
242
- try:
243
- import json
244
- import time
245
- entity_type = "AgentSession"
246
- entity_key = self._entity_key # Use entity_key without :messages suffix
247
- state_key = (entity_type, entity_key)
248
-
249
- # Get current version
250
- expected_version = self._state_manager._versions.get(state_key, 0)
251
-
252
- # Build session object with metadata for the sessions API
253
- now = time.time()
254
- session_data = {
255
- "session_id": self._session_id,
256
- "agent_name": self._agent_name,
257
- "created_at": now if expected_version == 0 else None, # Only set on first save
258
- "last_message_time": now,
259
- "message_count": len(messages_data),
260
- "messages": messages_data,
261
- "metadata": {}
262
- }
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
+ )
263
235
 
264
- # Serialize to JSON bytes
265
- state_json = json.dumps(session_data).encode('utf-8')
236
+ # Build session object with metadata
237
+ now = time.time()
266
238
 
267
- # Save to platform
268
- new_version = await self._state_manager._rust_manager.py_save_state(
269
- entity_type, entity_key, state_json, expected_version
270
- )
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
271
276
 
272
- # Update version tracking
273
- self._state_manager._versions[state_key] = new_version
274
- logger.info(f"Persisted conversation history to database: {entity_key} (version {expected_version} -> {new_version})")
275
- except Exception as e:
276
- logger.error(f"Failed to persist conversation history to database: {e}")
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)
277
347
 
278
348
 
279
349
  class Handoff:
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 []
232
+
233
+ keys = []
234
+ for (etype, ekey) in self._standalone_states.keys():
235
+ if etype == entity_type:
236
+ keys.append(ekey)
237
+ return keys
149
238
 
150
239
 
151
- def _get_state_manager() -> EntityStateManager:
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,36 +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
- # Load state from platform if not in memory
501
- if state_key not in state_manager._states and state_manager._rust_manager:
502
- try:
503
- # Call Rust manager to load state from platform
504
- result = await state_manager._rust_manager.py_load_state(
505
- entity_type, self._key
506
- )
507
- # result is a tuple: (found, state_json, version)
508
- found, state_json_bytes, version = result
509
- if found:
510
- # Decode and load state
511
- state_json = state_json_bytes.decode('utf-8') if isinstance(state_json_bytes, bytes) else state_json_bytes
512
- state_manager.load_state_from_platform(
513
- state_key, state_json, version
514
- )
515
- logger.info(f"Loaded entity state from platform: {entity_type}:{self._key} (version {version})")
516
- except Exception as e:
517
- logger.warning(f"Failed to load entity state from platform: {e}")
518
- # Continue with empty state
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)
519
595
 
520
- # Get or create state for this entity instance
521
- state_dict = state_manager.get_or_create_state(state_key)
596
+ logger.debug(
597
+ "Loaded state for %s:%s (version %d)",
598
+ entity_type, self._key, current_version
599
+ )
522
600
 
523
601
  # Set up EntityState on instance for method access
524
602
  self._state = EntityState(state_dict)
@@ -529,25 +607,26 @@ def _create_entity_method_wrapper(entity_type: str, method):
529
607
  result = await method(self, *args, **kwargs)
530
608
  logger.debug("Completed %s:%s.%s", entity_type, self._key, method.__name__)
531
609
 
532
- # Save state to platform after successful execution
533
- if state_manager._rust_manager:
534
- try:
535
- import json
536
- # Get current version
537
- expected_version = state_manager._versions.get(state_key, 0)
538
- # Serialize state
539
- state_json = json.dumps(state_dict).encode('utf-8')
540
- # Call Rust manager to save state
541
- new_version = await state_manager._rust_manager.py_save_state(
542
- entity_type, self._key, state_json, expected_version
543
- )
544
- # Update version
545
- state_manager._versions[state_key] = new_version
546
- logger.info(f"Saved entity state to platform: {entity_type}:{self._key} (version {expected_version} -> {new_version})")
547
- except Exception as e:
548
- logger.error(f"Failed to save entity state to platform: {e}")
549
- # Don't fail the method execution just because persistence failed
550
- # The state is still in memory
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
551
630
 
552
631
  return result
553
632
 
agnt5/worker.py CHANGED
@@ -126,13 +126,16 @@ 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
- from ._core import PyWorker, PyWorkerConfig, PyComponentInfo, EntityStateManager as RustEntityStateManager
135
+ from ._core import PyWorker, PyWorkerConfig, PyComponentInfo
132
136
  self._PyWorker = PyWorker
133
137
  self._PyWorkerConfig = PyWorkerConfig
134
138
  self._PyComponentInfo = PyComponentInfo
135
- self._RustEntityStateManager = RustEntityStateManager
136
139
  except ImportError as e:
137
140
  raise ImportError(
138
141
  f"Failed to import Rust core worker: {e}. "
@@ -149,16 +152,17 @@ class Worker:
149
152
  # Create Rust worker instance
150
153
  self._rust_worker = self._PyWorker(self._rust_config)
151
154
 
152
- # Get tenant_id for entity state manager
153
- import os
154
- tenant_id = os.getenv("AGNT5_TENANT_ID", "00000000-0000-0000-0000-000000000001")
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)
155
161
 
156
- # Create Rust entity state manager
157
- self._rust_entity_state_manager = self._RustEntityStateManager(tenant_id)
162
+ # Create Python adapter (thin wrapper around Rust core)
163
+ self._entity_state_adapter = EntityStateAdapter(rust_core=rust_core)
158
164
 
159
- # Create worker-scoped entity state manager with Rust manager
160
- from .entity import EntityStateManager
161
- self._entity_state_manager = EntityStateManager(rust_entity_state_manager=self._rust_entity_state_manager)
165
+ logger.info("Created EntityStateAdapter with Rust core for state management")
162
166
 
163
167
  # Component registration: auto-discover or explicit
164
168
  if auto_register:
@@ -754,7 +758,7 @@ class Worker:
754
758
  """Execute a workflow handler with automatic replay support."""
755
759
  import json
756
760
  from .workflow import WorkflowEntity, WorkflowContext
757
- from .entity import _get_state_manager
761
+ from .entity import _get_state_adapter
758
762
  from ._core import PyExecuteComponentResponse
759
763
 
760
764
  try:
@@ -795,10 +799,15 @@ class Worker:
795
799
  logger.debug(f"Loaded {len(completed_steps)} completed steps into workflow entity")
796
800
 
797
801
  if initial_state:
798
- # Load initial state into entity's state manager
799
- state_manager = _get_state_manager()
800
- state_manager._states[workflow_entity._state_key] = initial_state
801
- 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)")
802
811
 
803
812
  # Create WorkflowContext with entity and runtime_context for trace correlation
804
813
  ctx = WorkflowContext(
@@ -933,11 +942,11 @@ class Worker:
933
942
  """Execute an entity method."""
934
943
  import json
935
944
  from .context import Context
936
- from .entity import EntityType, Entity, _entity_state_manager_ctx
945
+ from .entity import EntityType, Entity, _entity_state_adapter_ctx
937
946
  from ._core import PyExecuteComponentResponse
938
947
 
939
- # Set entity state manager in context for Entity instances to access
940
- _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)
941
950
 
942
951
  try:
943
952
  # Parse input data
@@ -952,23 +961,8 @@ class Worker:
952
961
  if not method_name:
953
962
  raise ValueError("Entity invocation requires 'method' parameter")
954
963
 
955
- # Load state from platform if provided in request metadata
956
- state_key = (entity_type.name, entity_key)
957
- if hasattr(request, 'metadata') and request.metadata:
958
- if "entity_state" in request.metadata:
959
- platform_state_json = request.metadata["entity_state"]
960
- platform_version = int(request.metadata.get("state_version", "0"))
961
-
962
- # Load platform state into state manager
963
- self._entity_state_manager.load_state_from_platform(
964
- state_key,
965
- platform_state_json,
966
- platform_version
967
- )
968
- logger.info(
969
- f"Loaded entity state from platform: {entity_type.name}/{entity_key} "
970
- f"(version {platform_version})"
971
- )
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
972
966
 
973
967
  # Create entity instance using the stored class reference
974
968
  entity_instance = entity_type.entity_class(key=entity_key)
@@ -979,32 +973,15 @@ class Worker:
979
973
 
980
974
  method = getattr(entity_instance, method_name)
981
975
 
982
- # Execute method
976
+ # Execute method (entity method wrapper handles state load/save automatically)
983
977
  result = await method(**input_dict)
984
978
 
985
979
  # Serialize result
986
980
  output_data = json.dumps(result).encode("utf-8")
987
981
 
988
- # Capture entity state after execution with version tracking
989
- state_dict, expected_version, new_version = \
990
- self._entity_state_manager.get_state_for_persistence(state_key)
991
-
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
992
984
  metadata = {}
993
- if state_dict:
994
- # Serialize state as JSON string for platform persistence
995
- state_json = json.dumps(state_dict)
996
- # Pass in metadata for Worker Coordinator to publish
997
- metadata = {
998
- "entity_state": state_json,
999
- "entity_type": entity_type.name,
1000
- "entity_key": entity_key,
1001
- "expected_version": str(expected_version),
1002
- "new_version": str(new_version),
1003
- }
1004
- logger.info(
1005
- f"Captured entity state: {entity_type.name}/{entity_key} "
1006
- f"(version {expected_version} → {new_version})"
1007
- )
1008
985
 
1009
986
  return PyExecuteComponentResponse(
1010
987
  invocation_id=request.invocation_id,
@@ -1039,11 +1016,11 @@ class Worker:
1039
1016
  import json
1040
1017
  import uuid
1041
1018
  from .agent import AgentContext
1042
- from .entity import _entity_state_manager_ctx
1019
+ from .entity import _entity_state_adapter_ctx
1043
1020
  from ._core import PyExecuteComponentResponse
1044
1021
 
1045
- # Set entity state manager in context so AgentContext can access it
1046
- _entity_state_manager_ctx.set(self._entity_state_manager)
1022
+ # Set entity state adapter in context so AgentContext can access it
1023
+ _entity_state_adapter_ctx.set(self._entity_state_adapter)
1047
1024
 
1048
1025
  try:
1049
1026
  # Parse input data
@@ -1157,9 +1134,12 @@ class Worker:
1157
1134
  if self.metadata:
1158
1135
  self._rust_worker.set_service_metadata(self.metadata)
1159
1136
 
1160
- # Set entity state manager on Rust worker for database persistence
1161
- logger.info("Configuring entity state manager for database persistence")
1162
- self._rust_worker.set_entity_state_manager(self._rust_entity_state_manager)
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")
1163
1143
 
1164
1144
  # Get the current event loop to pass to Rust for concurrent Python async execution
1165
1145
  # This allows Rust to execute Python async functions on the same event loop
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.8a3
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,16 +1,15 @@
1
- agnt5-0.2.8a3.dist-info/METADATA,sha256=CSD_9YZ9vmaRf7sXcVSefdJ2YeIZnwQ2PZCohtvv5TY,996
2
- agnt5-0.2.8a3.dist-info/WHEEL,sha256=ldAWRbzSJBX4WKLM9X0Sn1bQRKOUbOtmxzIIvOfIpnw,108
3
- agnt5/__init__.py,sha256=dlJ3rUXiWIZMd0LpGGhgaItkdYlcp3yuEufQb-Yqh9A,2105
1
+ agnt5-0.2.8a4.dist-info/METADATA,sha256=Dp8yJo_dx5B79OM0ptGgR80TjCUbyovpSc4Pt57dfuY,996
2
+ agnt5-0.2.8a4.dist-info/WHEEL,sha256=ldAWRbzSJBX4WKLM9X0Sn1bQRKOUbOtmxzIIvOfIpnw,108
3
+ agnt5/__init__.py,sha256=ACkK91EPdnv5tYip09QCZ9rfV4iBKzNjGfYVLJD1XGg,2045
4
4
  agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
5
- agnt5/_core.abi3.so,sha256=fKTqxOoBbGmfZINggkW0oOry0itBkNGf-bwQiJozLHE,14831112
5
+ agnt5/_core.abi3.so,sha256=GfNIIiaNKAAIzfvWH7JWNK2ujOeIlv9ttx28PEM7n2A,15276440
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=fLX8k7cJ5-OKF9OU0Ykd__62mGFrYuToGUK0UdpNtas,41171
10
- agnt5/agent_session.py,sha256=mZ61mVax6Wt1ArocwdVEx4Kju6veFYtqxtSNWeKOve0,3161
9
+ agnt5/agent.py,sha256=aBrhtPaUAHOHv3-h_Yb2UMqFHertr1P2hJ7fA_4IXcw,43225
11
10
  agnt5/client.py,sha256=kXksazgxdVXWaG9OkjJA4cWruNtcS-ENhtnkrIdw-Nk,23212
12
11
  agnt5/context.py,sha256=S2OzPkhn_jnqSWfT21mSYOux8vHaLKQxcAvggZDHQek,2378
13
- agnt5/entity.py,sha256=KXSDklsqe9PoIKGz4hZpcmHJFLJKGe0Am8-3FH72Fxk,26150
12
+ agnt5/entity.py,sha256=AlHmSHVxQD5EYBvkmERKUkwv0ERrKaT8rvRK611hv_I,28941
14
13
  agnt5/exceptions.py,sha256=mZ0q-NK6OKhYxgwBJpIbgpgzk-CJaFIHDbp1EE-pS7I,925
15
14
  agnt5/function.py,sha256=f1vaAlJRwuo8cxCOGEd8XPido00mOhlPS8UJJx-6hJI,11041
16
15
  agnt5/lm.py,sha256=9dFjd6eQ3f3lFZe7H7rWZherYiP_58MT1F5xpwD8PCg,23195
@@ -18,6 +17,6 @@ agnt5/tool.py,sha256=uc4L-Q9QyLzQDe-MZKk2Wo3o5e-mK8tfaQwVDgQdouQ,13133
18
17
  agnt5/tracing.py,sha256=Mh2-OfnQM61lM_P8gxJstafdsUA8Gxoo1lP-Joxhub8,5980
19
18
  agnt5/types.py,sha256=Zb71ZMwvrt1p4SH18cAKunp2y5tao_W5_jGYaPDejQo,2840
20
19
  agnt5/version.py,sha256=rOq1mObLihnnKgKqBrwZA0zwOPudEKVFcW1a48ynkqc,573
21
- agnt5/worker.py,sha256=ALX8g8ij4GZ465mgEa17CO9I7Ybysv8w9uJWQRSd3g8,48685
22
- agnt5/workflow.py,sha256=sU8Gk7unxE_Gla7Fe-KlXfcBvYa2326GciuoR26CCr0,19585
23
- agnt5-0.2.8a3.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,,
agnt5/agent_session.py DELETED
@@ -1,110 +0,0 @@
1
- """Agent session management for multi-turn conversations.
2
-
3
- Provides AgentSession entity to track and persist conversation metadata
4
- across multiple agent invocations. Session state is backed by the entity
5
- infrastructure for durability.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import time
11
- from typing import Optional, Dict, Any
12
-
13
- from .entity import Entity
14
-
15
-
16
- class AgentSession(Entity):
17
- """
18
- Entity representing an agent conversation session.
19
-
20
- Tracks metadata about multi-turn conversations including:
21
- - Session creation and last activity timestamps
22
- - Message count
23
- - Agent identification
24
- - Custom metadata
25
-
26
- The actual conversation messages are stored separately in AgentContext
27
- using the session_id as the key. This entity only tracks session metadata.
28
-
29
- Example:
30
- ```python
31
- # Create new session
32
- session = AgentSession("session-123")
33
- session.create(agent_name="tutor", metadata={"user_id": "user-456"})
34
-
35
- # Update on each message
36
- session.add_message()
37
-
38
- # Get session summary
39
- summary = session.get_summary()
40
- ```
41
- """
42
-
43
- def __init__(self, session_id: str):
44
- """
45
- Initialize agent session entity.
46
-
47
- Args:
48
- session_id: Unique session identifier
49
- """
50
- self.session_id = session_id
51
- self.agent_name: str = ""
52
- self.created_at: float = 0.0
53
- self.last_message_time: float = 0.0
54
- self.message_count: int = 0
55
- self.metadata: Dict[str, Any] = {}
56
-
57
- def create(self, agent_name: str, metadata: Optional[Dict[str, Any]] = None) -> None:
58
- """
59
- Initialize a new session.
60
-
61
- Args:
62
- agent_name: Name of the agent for this session
63
- metadata: Optional additional session metadata
64
- """
65
- current_time = time.time()
66
- self.agent_name = agent_name
67
- self.created_at = current_time
68
- self.last_message_time = current_time
69
- self.message_count = 0
70
- self.metadata = metadata or {}
71
-
72
- def add_message(self) -> None:
73
- """
74
- Record a new message in the session.
75
-
76
- Updates message count and last activity timestamp.
77
- """
78
- self.message_count += 1
79
- self.last_message_time = time.time()
80
-
81
- def get_summary(self) -> Dict[str, Any]:
82
- """
83
- Get session metadata summary.
84
-
85
- Returns:
86
- Dictionary with session metadata including:
87
- - session_id
88
- - agent_name
89
- - created_at
90
- - last_message_time
91
- - message_count
92
- - metadata
93
- """
94
- return {
95
- "session_id": self.session_id,
96
- "agent_name": self.agent_name,
97
- "created_at": self.created_at,
98
- "last_message_time": self.last_message_time,
99
- "message_count": self.message_count,
100
- "metadata": self.metadata,
101
- }
102
-
103
- def update_metadata(self, metadata: Dict[str, Any]) -> None:
104
- """
105
- Update session metadata.
106
-
107
- Args:
108
- metadata: Metadata to merge into existing metadata
109
- """
110
- self.metadata.update(metadata)