agnt5 0.2.8a3__cp310-abi3-manylinux_2_34_x86_64.whl → 0.2.8a5__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
@@ -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,
@@ -27,9 +26,10 @@ from .exceptions import (
27
26
  ExecutionError,
28
27
  RetryError,
29
28
  StateError,
29
+ WaitingForUserInputException,
30
30
  )
31
31
  from .function import FunctionRegistry, function
32
- from .tool import Tool, ToolRegistry, tool
32
+ from .tool import AskUserTool, RequestApprovalTool, Tool, ToolRegistry, tool
33
33
  from .types import BackoffPolicy, BackoffType, FunctionConfig, RetryPolicy, WorkflowConfig
34
34
  from .version import _get_version
35
35
  from .worker import Worker
@@ -55,7 +55,7 @@ __all__ = [
55
55
  "Entity",
56
56
  "EntityType",
57
57
  "EntityRegistry",
58
- "EntityStateManager",
58
+ "EntityStateAdapter",
59
59
  "with_entity_context",
60
60
  "create_entity_context",
61
61
  "workflow",
@@ -63,11 +63,12 @@ __all__ = [
63
63
  "tool",
64
64
  "Tool",
65
65
  "ToolRegistry",
66
+ "AskUserTool",
67
+ "RequestApprovalTool",
66
68
  "agent",
67
69
  "Agent",
68
70
  "AgentRegistry",
69
71
  "AgentResult",
70
- "AgentSession",
71
72
  "Handoff",
72
73
  "handoff",
73
74
  # Types
@@ -83,6 +84,7 @@ __all__ = [
83
84
  "RetryError",
84
85
  "StateError",
85
86
  "CheckpointError",
87
+ "WaitingForUserInputException",
86
88
  "RunError",
87
89
  # Language Model (Simplified API)
88
90
  "lm",
agnt5/_core.abi3.so CHANGED
Binary file
agnt5/agent.py CHANGED
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
  import functools
10
10
  import json
11
11
  import logging
12
+ import time
12
13
  from typing import Any, Callable, Dict, List, Optional
13
14
 
14
15
  from .context import Context
@@ -79,42 +80,46 @@ class AgentContext(Context):
79
80
  self._agent_name = agent_name
80
81
  self._session_id = session_id or run_id
81
82
 
82
- # Determine state manager based on parent context
83
- from .entity import EntityStateManager, _get_state_manager
83
+ # Determine state adapter based on parent context
84
+ from .entity import EntityStateAdapter, _get_state_adapter
84
85
 
85
86
  if state_manager:
86
- # Explicit state manager provided
87
- self._state_manager = state_manager
88
- logger.debug(f"AgentContext using provided state manager")
87
+ # Explicit state adapter provided (parameter name kept for backward compat)
88
+ self._state_adapter = state_manager
89
+ logger.debug(f"AgentContext using provided state adapter")
89
90
  elif parent_context:
90
- # Try to inherit state manager from parent
91
+ # Try to inherit state adapter from parent
91
92
  try:
92
93
  # Check if parent is WorkflowContext or AgentContext
93
94
  if hasattr(parent_context, '_workflow_entity'):
94
- # WorkflowContext - get state manager from worker context
95
- self._state_manager = _get_state_manager()
95
+ # WorkflowContext - get state adapter from worker context
96
+ self._state_adapter = _get_state_adapter()
96
97
  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
98
+ elif hasattr(parent_context, '_state_adapter'):
99
+ # Parent AgentContext - share state adapter
100
+ self._state_adapter = parent_context._state_adapter
100
101
  logger.debug(f"AgentContext inheriting state from parent AgentContext")
102
+ elif hasattr(parent_context, '_state_manager'):
103
+ # Backward compatibility: parent has old _state_manager
104
+ self._state_adapter = parent_context._state_manager
105
+ logger.debug(f"AgentContext inheriting state from parent (legacy)")
101
106
  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)")
107
+ # FunctionContext or base Context - create new state adapter
108
+ self._state_adapter = EntityStateAdapter()
109
+ logger.debug(f"AgentContext created new state adapter (parent has no state)")
105
110
  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)")
111
+ # _get_state_adapter() failed (not in worker context) - create standalone
112
+ self._state_adapter = EntityStateAdapter()
113
+ logger.debug(f"AgentContext created standalone state adapter (not in worker context)")
109
114
  else:
110
115
  # Try to get from worker context first
111
116
  try:
112
- self._state_manager = _get_state_manager()
113
- logger.debug(f"AgentContext got state manager from worker context")
117
+ self._state_adapter = _get_state_adapter()
118
+ logger.debug(f"AgentContext got state adapter from worker context")
114
119
  except RuntimeError as e:
115
- # Standalone - create new state manager
116
- self._state_manager = EntityStateManager()
117
- logger.debug(f"AgentContext created standalone state manager")
120
+ # Standalone - create new state adapter
121
+ self._state_adapter = EntityStateAdapter()
122
+ logger.debug(f"AgentContext created standalone state adapter")
118
123
 
119
124
  # Conversation key for state storage (used for in-memory state)
120
125
  self._conversation_key = f"agent:{agent_name}:{self._session_id}:messages"
@@ -127,24 +132,23 @@ class AgentContext(Context):
127
132
  """
128
133
  Get state interface for agent state management.
129
134
 
135
+ Note: This is a simplified in-memory state interface for agent-specific data.
136
+ Conversation history is managed separately via get_conversation_history() and
137
+ save_conversation_history() which use the Rust-backed persistence layer.
138
+
130
139
  Returns:
131
- EntityState instance for state operations
140
+ Dict-like object for state operations
132
141
 
133
142
  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)
143
+ # Store agent-specific data (in-memory only)
144
+ ctx.state["research_results"] = data
145
+ ctx.state["iteration_count"] = 5
141
146
  """
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)
147
+ # Simple dict-based state for agent-specific data
148
+ # This is in-memory only and not persisted to platform
149
+ if not hasattr(self, '_agent_state'):
150
+ self._agent_state = {}
151
+ return self._agent_state
148
152
 
149
153
  @property
150
154
  def session_id(self) -> str:
@@ -155,45 +159,30 @@ class AgentContext(Context):
155
159
  """
156
160
  Retrieve conversation history from state, loading from database if needed.
157
161
 
162
+ Uses the EntityStateAdapter which delegates to Rust core for cache-first loading.
163
+
158
164
  Returns:
159
165
  List of Message objects from conversation history
160
166
  """
161
- # Try to load from database first if not in memory
162
167
  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")
168
+ entity_key = self._entity_key
169
+
170
+ # Load session data via adapter (Rust handles cache + platform load)
171
+ session_data = await self._state_adapter.load_state(entity_type, entity_key)
172
+
173
+ # Extract messages from session object
174
+ if isinstance(session_data, dict) and "messages" in session_data:
175
+ # New format with session metadata
176
+ messages_data = session_data["messages"]
177
+ logger.debug(f"Loaded {len(messages_data)} messages from session {entity_key}")
178
+ elif isinstance(session_data, list):
179
+ # Old format - just messages array
180
+ messages_data = session_data
181
+ logger.debug(f"Loaded {len(messages_data)} messages (legacy format)")
182
+ else:
183
+ # No messages found
184
+ messages_data = []
185
+ logger.debug(f"No conversation history found for {entity_key}")
197
186
 
198
187
  # Convert dict representations back to Message objects
199
188
  messages = []
@@ -221,6 +210,8 @@ class AgentContext(Context):
221
210
  """
222
211
  Save conversation history to state and persist to database.
223
212
 
213
+ Uses the EntityStateAdapter which delegates to Rust core for version-checked saves.
214
+
224
215
  Args:
225
216
  messages: List of Message objects to persist
226
217
  """
@@ -231,49 +222,129 @@ class AgentContext(Context):
231
222
  for msg in messages:
232
223
  messages_data.append({
233
224
  "role": msg.role.value if hasattr(msg.role, 'value') else str(msg.role),
234
- "content": msg.content
225
+ "content": msg.content,
226
+ "timestamp": time.time() # Add timestamp for each message
235
227
  })
236
228
 
237
- # Save to in-memory state
238
- self.state.set(self._conversation_key, messages_data)
229
+ entity_type = "AgentSession"
230
+ entity_key = self._entity_key
239
231
 
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
- }
232
+ # Load current state with version for optimistic locking
233
+ current_state, current_version = await self._state_adapter.load_with_version(
234
+ entity_type, entity_key
235
+ )
263
236
 
264
- # Serialize to JSON bytes
265
- state_json = json.dumps(session_data).encode('utf-8')
237
+ # Build session object with metadata
238
+ now = time.time()
266
239
 
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
- )
240
+ # Get custom metadata from instance variable or preserve from loaded state
241
+ custom_metadata = getattr(self, '_custom_metadata', current_state.get("metadata", {}))
242
+
243
+ session_data = {
244
+ "session_id": self._session_id,
245
+ "agent_name": self._agent_name,
246
+ "created_at": current_state.get("created_at", now), # Preserve existing or set new
247
+ "last_message_time": now,
248
+ "message_count": len(messages_data),
249
+ "messages": messages_data,
250
+ "metadata": custom_metadata # Save custom metadata
251
+ }
252
+
253
+ # Save to platform via adapter (Rust handles optimistic locking)
254
+ try:
255
+ new_version = await self._state_adapter.save_state(
256
+ entity_type,
257
+ entity_key,
258
+ session_data,
259
+ current_version
260
+ )
261
+ logger.info(
262
+ f"Persisted conversation history: {entity_key} (version {current_version} -> {new_version})"
263
+ )
264
+ except Exception as e:
265
+ logger.error(f"Failed to persist conversation history to database: {e}")
266
+ # Don't fail - conversation is still in memory for this execution
267
+
268
+ async def get_metadata(self) -> Dict[str, Any]:
269
+ """
270
+ Get conversation session metadata.
271
+
272
+ Returns session metadata including:
273
+ - created_at: Timestamp of first message (float, Unix timestamp)
274
+ - last_activity: Timestamp of last message (float, Unix timestamp)
275
+ - message_count: Number of messages in conversation (int)
276
+ - custom: Dict of user-provided custom metadata
271
277
 
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}")
278
+ Returns:
279
+ Dictionary with metadata. If no conversation exists yet, returns defaults.
280
+
281
+ Example:
282
+ ```python
283
+ metadata = await context.get_metadata()
284
+ print(f"Session created: {metadata['created_at']}")
285
+ print(f"User ID: {metadata['custom'].get('user_id')}")
286
+ ```
287
+ """
288
+ entity_type = "AgentSession"
289
+ entity_key = self._entity_key
290
+
291
+ # Load session data
292
+ session_data = await self._state_adapter.load_state(entity_type, entity_key)
293
+
294
+ if not session_data:
295
+ # No conversation exists yet - return defaults
296
+ return {
297
+ "created_at": None,
298
+ "last_activity": None,
299
+ "message_count": 0,
300
+ "custom": getattr(self, '_custom_metadata', {})
301
+ }
302
+
303
+ messages = session_data.get("messages", [])
304
+
305
+ # Derive timestamps from messages if available
306
+ created_at = session_data.get("created_at")
307
+ last_activity = session_data.get("last_message_time")
308
+
309
+ return {
310
+ "created_at": created_at,
311
+ "last_activity": last_activity,
312
+ "message_count": len(messages),
313
+ "custom": session_data.get("metadata", {})
314
+ }
315
+
316
+ def update_metadata(self, **kwargs) -> None:
317
+ """
318
+ Update custom session metadata.
319
+
320
+ Metadata will be persisted alongside conversation history on next save.
321
+ Use this to store application-specific data like user_id, preferences, etc.
322
+
323
+ Args:
324
+ **kwargs: Key-value pairs to store as metadata
325
+
326
+ Example:
327
+ ```python
328
+ # Store user identification and preferences
329
+ context.update_metadata(
330
+ user_id="user-123",
331
+ subscription_tier="premium",
332
+ preferences={"theme": "dark", "language": "en"}
333
+ )
334
+
335
+ # Later retrieve it
336
+ metadata = await context.get_metadata()
337
+ user_id = metadata["custom"]["user_id"]
338
+ ```
339
+
340
+ Note:
341
+ - Metadata is merged with existing metadata (doesn't replace)
342
+ - Changes persist on next save_conversation_history() call
343
+ - Use simple JSON-serializable types (str, int, float, dict, list)
344
+ """
345
+ if not hasattr(self, '_custom_metadata'):
346
+ self._custom_metadata = {}
347
+ self._custom_metadata.update(kwargs)
277
348
 
278
349
 
279
350
  class Handoff: